[Lldb-commits] [lldb] [lldb] Add support for PC-less scripted frames (PR #170762)
Med Ismail Bennani via lldb-commits
lldb-commits at lists.llvm.org
Thu Dec 4 17:13:12 PST 2025
https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/170762
>From cb00990f113e8c9a87c749fcab4cbb7adf7a4069 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Thu, 4 Dec 2025 13:02:18 -0800
Subject: [PATCH 1/2] [lldb] Add support for PC-less scripted frames
This adds support for scripted frames without valid PC addresses, allowing
them to display properly without showing 0xffffffffffffffff.
Changes include:
- Make StackFrame::GetSymbolContext() resilient to PC-less frames by adding
an early return when the lookup address is invalid, preserving any
already-populated SymbolContext fields.
- Populate module and compile unit for scripted frames by searching existing
modules for matching LineEntry files, falling back to the scripted module
- Create synthetic CompileUnits with language type deduced from the script
- Fix frame formatting to avoid extra spaces for PC-less frames by removing
the space from the frame format string and adding it conditionally in
FormatEntity::Format only when the frame has a valid PC.
- Add fallbacks in FormatEntity for FunctionName, FunctionNameNoArgs, and
FunctionNameWithArgs to use StackFrame methods when SymbolContext lookup
fails, enabling proper function name display for scripted frames.
- Update test to include a scripted frame pointing to Python source with a
LineEntry referencing the Python file containing my_python_function().
Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
lldb/source/Core/FormatEntity.cpp | 161 +++++++++++-------
.../Process/scripted/ScriptedFrame.cpp | 107 +++++++++++-
lldb/source/Target/StackFrame.cpp | 7 +
.../dummy_scripted_process.py | 25 +++
4 files changed, 239 insertions(+), 61 deletions(-)
diff --git a/lldb/source/Core/FormatEntity.cpp b/lldb/source/Core/FormatEntity.cpp
index c528a14fa76d0..0b7025409d952 100644
--- a/lldb/source/Core/FormatEntity.cpp
+++ b/lldb/source/Core/FormatEntity.cpp
@@ -1636,6 +1636,14 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
if (sc) {
Module *module = sc->module_sp.get();
if (module) {
+ // Add a space before module name if frame has a valid PC
+ // (so "PC module" but ": module" for PC-less frames)
+ if (exe_ctx) {
+ StackFrame *frame = exe_ctx->GetFramePtr();
+ if (frame && frame->GetFrameCodeAddress().IsValid()) {
+ s.PutChar(' ');
+ }
+ }
if (DumpFile(s, module->GetFileSpec(), (FileKind)entry.number))
return true;
}
@@ -1684,10 +1692,11 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
StackFrame *frame = exe_ctx->GetFramePtr();
if (frame) {
const Address &pc_addr = frame->GetFrameCodeAddress();
- if (pc_addr.IsValid() || frame->IsSynthetic()) {
+ if (pc_addr.IsValid()) {
if (DumpAddressAndContent(s, sc, exe_ctx, pc_addr, false))
return true;
- }
+ } else if (frame->IsSynthetic())
+ return true;
}
}
return false;
@@ -1808,70 +1817,91 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
return initial_function;
case Entry::Type::FunctionName: {
- if (!sc)
- return false;
+ if (sc) {
+ Language *language_plugin = nullptr;
+ bool language_plugin_handled = false;
+ StreamString ss;
- Language *language_plugin = nullptr;
- bool language_plugin_handled = false;
- StreamString ss;
+ if (sc->function)
+ language_plugin = Language::FindPlugin(sc->function->GetLanguage());
+ else if (sc->symbol)
+ language_plugin = Language::FindPlugin(sc->symbol->GetLanguage());
- if (sc->function)
- language_plugin = Language::FindPlugin(sc->function->GetLanguage());
- else if (sc->symbol)
- language_plugin = Language::FindPlugin(sc->symbol->GetLanguage());
+ if (language_plugin)
+ language_plugin_handled = language_plugin->GetFunctionDisplayName(
+ *sc, exe_ctx, Language::FunctionNameRepresentation::eName, ss);
- if (language_plugin)
- language_plugin_handled = language_plugin->GetFunctionDisplayName(
- *sc, exe_ctx, Language::FunctionNameRepresentation::eName, ss);
+ if (language_plugin_handled) {
+ s << ss.GetString();
+ return true;
+ }
- if (language_plugin_handled) {
- s << ss.GetString();
- return true;
+ const char *name = sc->GetPossiblyInlinedFunctionName()
+ .GetName(Mangled::NamePreference::ePreferDemangled)
+ .AsCString();
+ if (name) {
+ s.PutCString(name);
+ return true;
+ }
}
- const char *name = sc->GetPossiblyInlinedFunctionName()
- .GetName(Mangled::NamePreference::ePreferDemangled)
- .AsCString();
- if (!name)
- return false;
-
- s.PutCString(name);
-
- return true;
+ // Fallback to frame methods if available
+ if (exe_ctx) {
+ StackFrame *frame = exe_ctx->GetFramePtr();
+ if (frame) {
+ const char *name = frame->GetFunctionName();
+ if (name) {
+ s.PutCString(name);
+ return true;
+ }
+ }
+ }
+ return false;
}
case Entry::Type::FunctionNameNoArgs: {
- if (!sc)
- return false;
-
- Language *language_plugin = nullptr;
- bool language_plugin_handled = false;
- StreamString ss;
- if (sc->function)
- language_plugin = Language::FindPlugin(sc->function->GetLanguage());
- else if (sc->symbol)
- language_plugin = Language::FindPlugin(sc->symbol->GetLanguage());
-
- if (language_plugin)
- language_plugin_handled = language_plugin->GetFunctionDisplayName(
- *sc, exe_ctx, Language::FunctionNameRepresentation::eNameWithNoArgs,
- ss);
+ if (sc) {
+ Language *language_plugin = nullptr;
+ bool language_plugin_handled = false;
+ StreamString ss;
+ if (sc->function)
+ language_plugin = Language::FindPlugin(sc->function->GetLanguage());
+ else if (sc->symbol)
+ language_plugin = Language::FindPlugin(sc->symbol->GetLanguage());
+
+ if (language_plugin)
+ language_plugin_handled = language_plugin->GetFunctionDisplayName(
+ *sc, exe_ctx, Language::FunctionNameRepresentation::eNameWithNoArgs,
+ ss);
+
+ if (language_plugin_handled) {
+ s << ss.GetString();
+ return true;
+ }
- if (language_plugin_handled) {
- s << ss.GetString();
- return true;
+ const char *name =
+ sc->GetPossiblyInlinedFunctionName()
+ .GetName(
+ Mangled::NamePreference::ePreferDemangledWithoutArguments)
+ .AsCString();
+ if (name) {
+ s.PutCString(name);
+ return true;
+ }
}
- const char *name =
- sc->GetPossiblyInlinedFunctionName()
- .GetName(Mangled::NamePreference::ePreferDemangledWithoutArguments)
- .AsCString();
- if (!name)
- return false;
-
- s.PutCString(name);
-
- return true;
+ // Fallback to frame methods if available
+ if (exe_ctx) {
+ StackFrame *frame = exe_ctx->GetFramePtr();
+ if (frame) {
+ const char *name = frame->GetFunctionName();
+ if (name) {
+ s.PutCString(name);
+ return true;
+ }
+ }
+ }
+ return false;
}
case Entry::Type::FunctionPrefix:
@@ -1898,13 +1928,26 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
}
case Entry::Type::FunctionNameWithArgs: {
- if (!sc)
- return false;
+ if (sc) {
+ if (FormatFunctionNameForLanguage(s, exe_ctx, sc))
+ return true;
- if (FormatFunctionNameForLanguage(s, exe_ctx, sc))
- return true;
+ if (HandleFunctionNameWithArgs(s, exe_ctx, *sc))
+ return true;
+ }
- return HandleFunctionNameWithArgs(s, exe_ctx, *sc);
+ // Fallback to frame methods if available
+ if (exe_ctx) {
+ StackFrame *frame = exe_ctx->GetFramePtr();
+ if (frame) {
+ const char *name = frame->GetDisplayFunctionName();
+ if (name) {
+ s.PutCString(name);
+ return true;
+ }
+ }
+ }
+ return false;
}
case Entry::Type::FunctionMangledName: {
if (!sc)
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
index 265bc28a8957f..ee0c36a6b6edb 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
@@ -11,10 +11,15 @@
#include "lldb/Core/Address.h"
#include "lldb/Core/Debugger.h"
+#include "lldb/Core/Module.h"
+#include "lldb/Core/ModuleList.h"
+#include "lldb/Host/FileSystem.h"
#include "lldb/Interpreter/Interfaces/ScriptedFrameInterface.h"
#include "lldb/Interpreter/Interfaces/ScriptedThreadInterface.h"
#include "lldb/Interpreter/ScriptInterpreter.h"
+#include "lldb/Symbol/CompileUnit.h"
#include "lldb/Symbol/SymbolContext.h"
+#include "lldb/Symbol/SymbolFile.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
@@ -102,6 +107,104 @@ ScriptedFrame::Create(ThreadSP thread_sp,
sc = *maybe_sym_ctx;
}
+ // Ensure the symbol context has at least the scripted frame implementation
+ // for scripted frames without a valid PC or custom symbol context
+ if (!sc.module_sp) {
+ // First, check if we have a valid line entry with a file, and try to find
+ // an existing module that contains that file
+ if (sc.line_entry.IsValid() && sc.line_entry.file_sp) {
+ const FileSpec &line_file = sc.line_entry.GetFile();
+
+ // Search through all modules to find one that contains this file
+ ModuleList &module_list = process_sp->GetTarget().GetImages();
+ const size_t num_modules = module_list.GetSize();
+ for (size_t i = 0; i < num_modules; ++i) {
+ ModuleSP module_sp = module_list.GetModuleAtIndex(i);
+ if (module_sp) {
+ SymbolFile *sym_file = module_sp->GetSymbolFile();
+ if (sym_file) {
+ // Check if this module has compile units with our file
+ const uint32_t num_comp_units = sym_file->GetNumCompileUnits();
+ for (uint32_t cu_idx = 0; cu_idx < num_comp_units; ++cu_idx) {
+ CompUnitSP cu_sp = sym_file->GetCompileUnitAtIndex(cu_idx);
+ if (cu_sp &&
+ FileSpec::Equal(cu_sp->GetPrimarySupportFile()->GetSpecOnly(),
+ line_file, false)) {
+ sc.module_sp = module_sp;
+ sc.comp_unit = cu_sp.get();
+ break;
+ }
+ }
+ if (sc.module_sp)
+ break;
+ }
+ }
+ }
+ }
+
+ // If no existing module found, try to create one from the scripted module
+ // path
+ if (!sc.module_sp) {
+ auto module_path_or_err =
+ scripted_frame_interface->GetScriptedModulePath();
+ if (!module_path_or_err) {
+ LLDB_LOG_ERROR(GetLog(LLDBLog::Script), module_path_or_err.takeError(),
+ "Failed to get scripted module path: {0}.");
+ } else {
+ FileSpec module_path = *module_path_or_err;
+ if (FileSystem::Instance().Exists(module_path)) {
+ FileSpec module_file_spec = FileSpec(module_path);
+ const ArchSpec &arch_spec = process_sp->GetTarget().GetArchitecture();
+ ModuleSP script_module_sp =
+ std::make_shared<Module>(module_file_spec, arch_spec);
+ sc.module_sp = script_module_sp;
+ }
+ }
+
+ if (!sc.module_sp) {
+ // Fall back to using the executable module
+ sc.module_sp = process_sp->GetTarget()
+ .GetExecutableModulePointer()
+ ->shared_from_this();
+ }
+ }
+ }
+
+ // Create a CompileUnit for the scripted frame if we don't have one
+ if (!sc.comp_unit && sc.module_sp) {
+ const FileSpec &module_file_spec = sc.module_sp->GetFileSpec();
+ if (module_file_spec) {
+ // Get the script language and convert to LanguageType
+ ScriptInterpreter *script_interp =
+ process_sp->GetTarget().GetDebugger().GetScriptInterpreter();
+ lldb::LanguageType language = lldb::eLanguageTypeUnknown;
+ if (script_interp) {
+ switch (script_interp->GetLanguage()) {
+ case lldb::eScriptLanguagePython:
+ language = lldb::eLanguageTypePython;
+ break;
+ case lldb::eScriptLanguageLua:
+ // NOTE: Lua isn't part of the DWARF Source Languages so we cannot use
+ // it to create a compile unit.
+ language = lldb::eLanguageTypeUnknown;
+ break;
+ default:
+ language = lldb::eLanguageTypeUnknown;
+ break;
+ }
+ }
+
+ sc.comp_unit = new CompileUnit(
+ sc.module_sp,
+ nullptr, // user_data
+ std::make_shared<SupportFile>(module_file_spec), // support_file
+ frame_id, // unique ID
+ language,
+ eLazyBoolCalculate // not optimized
+ );
+ }
+ }
+
StructuredData::DictionarySP reg_info =
scripted_frame_interface->GetRegisterInfo();
@@ -162,7 +265,7 @@ const char *ScriptedFrame::GetFunctionName() {
CheckInterpreterAndScriptObject();
std::optional<std::string> function_name = GetInterface()->GetFunctionName();
if (!function_name)
- return nullptr;
+ return StackFrame::GetFunctionName();
return ConstString(function_name->c_str()).AsCString();
}
@@ -171,7 +274,7 @@ const char *ScriptedFrame::GetDisplayFunctionName() {
std::optional<std::string> function_name =
GetInterface()->GetDisplayFunctionName();
if (!function_name)
- return nullptr;
+ return StackFrame::GetDisplayFunctionName();
return ConstString(function_name->c_str()).AsCString();
}
diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp
index ca3d4a1a29b59..b0ececc7d0c19 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -331,6 +331,13 @@ StackFrame::GetSymbolContext(SymbolContextItem resolve_scope) {
// following the function call instruction...
Address lookup_addr(GetFrameCodeAddressForSymbolication());
+ // For PC-less frames (e.g., scripted frames), skip PC-based symbol
+ // resolution and preserve any already-populated SymbolContext fields.
+ if (!lookup_addr.IsValid()) {
+ m_flags.Set(resolve_scope | resolved);
+ return m_sc;
+ }
+
if (m_sc.module_sp) {
// We have something in our stack frame symbol context, lets check if we
// haven't already tried to lookup one of those things. If we haven't
diff --git a/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py b/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py
index a9459682e70a8..4ebf940708223 100644
--- a/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py
+++ b/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py
@@ -8,6 +8,15 @@
from lldb.plugins.scripted_process import ScriptedFrame
+def my_python_function(x, y):
+ """A sample Python function to demonstrate Python source display in scripted frames."""
+ result = x + y
+ if result > 100:
+ return result * 2
+ else:
+ return result
+
+
class DummyStopHook:
def __init__(self, target, args):
self.target = target
@@ -88,6 +97,22 @@ def __init__(self, process, args):
DummyScriptedFrame(self, args, len(self.frames), "baz", sym_ctx)
)
+ # Add a frame with Python source
+ code = my_python_function.__code__
+ lineno = code.co_firstlineno
+ col_offset = getattr(code, "co_firstcol_offset", 0) # Python ≥3.11 has column info
+ py_le = lldb.SBLineEntry()
+ py_le.SetFileSpec(lldb.SBFileSpec(__file__, True))
+ py_le.SetLine(lineno) # Line where my_python_function is defined
+ py_le.SetColumn(col_offset)
+
+ py_sym_ctx = lldb.SBSymbolContext()
+ py_sym_ctx.SetLineEntry(py_le)
+
+ self.frames.append(
+ DummyScriptedFrame(self, args, len(self.frames), "my_python_function", py_sym_ctx)
+ )
+
def get_thread_id(self) -> int:
return 0x19
>From 2cd56a9da96789bfc68dea608d8143d2d6282ca9 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Thu, 4 Dec 2025 17:12:42 -0800
Subject: [PATCH 2/2] [lldb] Add test for scripted frame provider producing
python frames
Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
lldb/source/Core/CoreProperties.td | 4 +-
lldb/source/Core/FormatEntity.cpp | 9 +-
.../Process/scripted/ScriptedFrame.cpp | 101 +-------------
lldb/source/Target/StackFrame.cpp | 2 +-
.../TestScriptedFrameProvider.py | 126 ++++++++++++++++++
.../scripted_frame_provider/python_helper.py | 36 +++++
.../test_frame_providers.py | 96 +++++++++++++
7 files changed, 266 insertions(+), 108 deletions(-)
create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/python_helper.py
diff --git a/lldb/source/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td
index 1be911c291703..89bd5f03711e8 100644
--- a/lldb/source/Core/CoreProperties.td
+++ b/lldb/source/Core/CoreProperties.td
@@ -59,7 +59,7 @@ let Definition = "debugger" in {
Desc<"The default disassembly format string to use when disassembling instruction sequences.">;
def FrameFormat: Property<"frame-format", "FormatEntity">,
Global,
- DefaultStringValue<"frame #${frame.index}: ${ansi.fg.cyan}${frame.pc}${ansi.normal}{ ${module.file.basename}{`${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}${frame.kind}{${function.is-optimized} [opt]}{${function.is-inlined} [inlined]}{${frame.is-artificial} [artificial]}\\\\n">,
+ DefaultStringValue<"frame #${frame.index}: ${ansi.fg.cyan}${frame.pc}${ansi.normal}{ ${module.file.basename}{`}}{${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}${frame.kind}{${function.is-optimized} [opt]}{${function.is-inlined} [inlined]}{${frame.is-artificial} [artificial]}\\\\n">,
Desc<"The default frame format string to use when displaying stack frame information for threads.">;
def NotiftVoid: Property<"notify-void", "Boolean">,
Global,
@@ -235,7 +235,7 @@ let Definition = "debugger" in {
Desc<"If true, LLDB will automatically escape non-printable and escape characters when formatting strings.">;
def FrameFormatUnique: Property<"frame-format-unique", "FormatEntity">,
Global,
- DefaultStringValue<"frame #${frame.index}: ${ansi.fg.cyan}${frame.pc}${ansi.normal}{ ${module.file.basename}{`${function.name-without-args}{${frame.no-debug}${function.pc-offset}}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}${frame.kind}{${function.is-optimized} [opt]}{${function.is-inlined} [inlined]}{${frame.is-artificial} [artificial]}\\\\n">,
+ DefaultStringValue<"frame #${frame.index}: ${ansi.fg.cyan}${frame.pc}${ansi.normal} ${module.file.basename}{`${function.name-without-args}{${frame.no-debug}${function.pc-offset}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}${frame.kind}{${function.is-optimized} [opt]}{${function.is-inlined} [inlined]}{${frame.is-artificial} [artificial]}\\\\n">,
Desc<"The default frame format string to use when displaying stack frame information for threads from thread backtrace unique.">;
def ShowAutosuggestion: Property<"show-autosuggestion", "Boolean">,
Global,
diff --git a/lldb/source/Core/FormatEntity.cpp b/lldb/source/Core/FormatEntity.cpp
index 0b7025409d952..9ddbf59ebdfe1 100644
--- a/lldb/source/Core/FormatEntity.cpp
+++ b/lldb/source/Core/FormatEntity.cpp
@@ -1994,6 +1994,8 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
frame->GetFrameCodeAddress(), false,
false, false))
return true;
+ else if (frame->IsSynthetic())
+ return true;
}
}
return false;
@@ -2018,11 +2020,8 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
case Entry::Type::LineEntryFile:
if (sc && sc->line_entry.IsValid()) {
- Module *module = sc->module_sp.get();
- if (module) {
- if (DumpFile(s, sc->line_entry.GetFile(), (FileKind)entry.number))
- return true;
- }
+ if (DumpFile(s, sc->line_entry.GetFile(), (FileKind)entry.number))
+ return true;
}
return false;
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
index ee0c36a6b6edb..748ca5cf03414 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
@@ -103,107 +103,8 @@ ScriptedFrame::Create(ThreadSP thread_sp,
std::optional<SymbolContext> maybe_sym_ctx =
scripted_frame_interface->GetSymbolContext();
- if (maybe_sym_ctx) {
+ if (maybe_sym_ctx)
sc = *maybe_sym_ctx;
- }
-
- // Ensure the symbol context has at least the scripted frame implementation
- // for scripted frames without a valid PC or custom symbol context
- if (!sc.module_sp) {
- // First, check if we have a valid line entry with a file, and try to find
- // an existing module that contains that file
- if (sc.line_entry.IsValid() && sc.line_entry.file_sp) {
- const FileSpec &line_file = sc.line_entry.GetFile();
-
- // Search through all modules to find one that contains this file
- ModuleList &module_list = process_sp->GetTarget().GetImages();
- const size_t num_modules = module_list.GetSize();
- for (size_t i = 0; i < num_modules; ++i) {
- ModuleSP module_sp = module_list.GetModuleAtIndex(i);
- if (module_sp) {
- SymbolFile *sym_file = module_sp->GetSymbolFile();
- if (sym_file) {
- // Check if this module has compile units with our file
- const uint32_t num_comp_units = sym_file->GetNumCompileUnits();
- for (uint32_t cu_idx = 0; cu_idx < num_comp_units; ++cu_idx) {
- CompUnitSP cu_sp = sym_file->GetCompileUnitAtIndex(cu_idx);
- if (cu_sp &&
- FileSpec::Equal(cu_sp->GetPrimarySupportFile()->GetSpecOnly(),
- line_file, false)) {
- sc.module_sp = module_sp;
- sc.comp_unit = cu_sp.get();
- break;
- }
- }
- if (sc.module_sp)
- break;
- }
- }
- }
- }
-
- // If no existing module found, try to create one from the scripted module
- // path
- if (!sc.module_sp) {
- auto module_path_or_err =
- scripted_frame_interface->GetScriptedModulePath();
- if (!module_path_or_err) {
- LLDB_LOG_ERROR(GetLog(LLDBLog::Script), module_path_or_err.takeError(),
- "Failed to get scripted module path: {0}.");
- } else {
- FileSpec module_path = *module_path_or_err;
- if (FileSystem::Instance().Exists(module_path)) {
- FileSpec module_file_spec = FileSpec(module_path);
- const ArchSpec &arch_spec = process_sp->GetTarget().GetArchitecture();
- ModuleSP script_module_sp =
- std::make_shared<Module>(module_file_spec, arch_spec);
- sc.module_sp = script_module_sp;
- }
- }
-
- if (!sc.module_sp) {
- // Fall back to using the executable module
- sc.module_sp = process_sp->GetTarget()
- .GetExecutableModulePointer()
- ->shared_from_this();
- }
- }
- }
-
- // Create a CompileUnit for the scripted frame if we don't have one
- if (!sc.comp_unit && sc.module_sp) {
- const FileSpec &module_file_spec = sc.module_sp->GetFileSpec();
- if (module_file_spec) {
- // Get the script language and convert to LanguageType
- ScriptInterpreter *script_interp =
- process_sp->GetTarget().GetDebugger().GetScriptInterpreter();
- lldb::LanguageType language = lldb::eLanguageTypeUnknown;
- if (script_interp) {
- switch (script_interp->GetLanguage()) {
- case lldb::eScriptLanguagePython:
- language = lldb::eLanguageTypePython;
- break;
- case lldb::eScriptLanguageLua:
- // NOTE: Lua isn't part of the DWARF Source Languages so we cannot use
- // it to create a compile unit.
- language = lldb::eLanguageTypeUnknown;
- break;
- default:
- language = lldb::eLanguageTypeUnknown;
- break;
- }
- }
-
- sc.comp_unit = new CompileUnit(
- sc.module_sp,
- nullptr, // user_data
- std::make_shared<SupportFile>(module_file_spec), // support_file
- frame_id, // unique ID
- language,
- eLazyBoolCalculate // not optimized
- );
- }
- }
StructuredData::DictionarySP reg_info =
scripted_frame_interface->GetRegisterInfo();
diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp
index b0ececc7d0c19..3bbb851b88007 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -2064,7 +2064,7 @@ bool StackFrame::GetStatus(Stream &strm, bool show_frame_info, bool show_source,
disasm_display = debugger.GetStopDisassemblyDisplay();
GetSymbolContext(eSymbolContextCompUnit | eSymbolContextLineEntry);
- if (m_sc.comp_unit && m_sc.line_entry.IsValid()) {
+ if (m_sc.comp_unit || m_sc.line_entry.IsValid()) {
have_debuginfo = true;
if (source_lines_before > 0 || source_lines_after > 0) {
SupportFileNSP source_file_sp = m_sc.line_entry.file_sp;
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
index caed94f5f93da..58adc4fb488ac 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
@@ -426,3 +426,129 @@ def test_circular_dependency_fix(self):
# These calls should not trigger circular dependency
pc = frame.GetPC()
self.assertNotEqual(pc, 0, f"Frame {i} should have valid PC")
+
+ def test_python_source_frames(self):
+ """Test that frames can point to Python source files and display properly."""
+ self.build()
+ target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+ self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False
+ )
+
+ # Get original frame count
+ original_frame_count = thread.GetNumFrames()
+ self.assertGreaterEqual(
+ original_frame_count, 2, "Should have at least 2 real frames"
+ )
+
+ # Import the provider
+ script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py")
+ self.runCmd("command script import " + script_path)
+
+ # Register the PythonSourceFrameProvider
+ error = lldb.SBError()
+ provider_id = target.RegisterScriptedFrameProvider(
+ "test_frame_providers.PythonSourceFrameProvider",
+ lldb.SBStructuredData(),
+ error,
+ )
+ self.assertTrue(error.Success(), f"Failed to register provider: {error}")
+ self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero")
+
+ # Verify we have 3 more frames (Python frames)
+ new_frame_count = thread.GetNumFrames()
+ self.assertEqual(
+ new_frame_count,
+ original_frame_count + 3,
+ "Should have original frames + 3 Python frames",
+ )
+
+ # Verify first three frames are Python source frames
+ frame0 = thread.GetFrameAtIndex(0)
+ self.assertIsNotNone(frame0)
+ self.assertEqual(
+ frame0.GetFunctionName(),
+ "compute_fibonacci",
+ "First frame should be compute_fibonacci",
+ )
+ self.assertTrue(frame0.IsSynthetic(), "Frame should be marked as synthetic")
+ # PC-less frames should show invalid address
+ self.assertEqual(
+ frame0.GetPC(),
+ lldb.LLDB_INVALID_ADDRESS,
+ "PC-less frame should have LLDB_INVALID_ADDRESS",
+ )
+
+ breakpoint()
+ frame1 = thread.GetFrameAtIndex(1)
+ self.assertIsNotNone(frame1)
+ self.assertEqual(
+ frame1.GetFunctionName(),
+ "process_data",
+ "Second frame should be process_data",
+ )
+ self.assertTrue(frame1.IsSynthetic(), "Frame should be marked as synthetic")
+
+ frame2 = thread.GetFrameAtIndex(2)
+ self.assertIsNotNone(frame2)
+ self.assertEqual(
+ frame2.GetFunctionName(), "main", "Third frame should be main"
+ )
+ self.assertTrue(frame2.IsSynthetic(), "Frame should be marked as synthetic")
+
+ # Verify line entry information is present
+ line_entry0 = frame0.GetLineEntry()
+ self.assertTrue(
+ line_entry0.IsValid(), "Frame 0 should have a valid line entry"
+ )
+ self.assertEqual(
+ line_entry0.GetLine(), 7, "Frame 0 should point to line 7"
+ )
+ file_spec0 = line_entry0.GetFileSpec()
+ self.assertTrue(file_spec0.IsValid(), "Frame 0 should have valid file spec")
+ self.assertEqual(
+ file_spec0.GetFilename(),
+ "python_helper.py",
+ "Frame 0 should point to python_helper.py",
+ )
+
+ line_entry1 = frame1.GetLineEntry()
+ self.assertTrue(
+ line_entry1.IsValid(), "Frame 1 should have a valid line entry"
+ )
+ self.assertEqual(
+ line_entry1.GetLine(), 16, "Frame 1 should point to line 16"
+ )
+
+ line_entry2 = frame2.GetLineEntry()
+ self.assertTrue(
+ line_entry2.IsValid(), "Frame 2 should have a valid line entry"
+ )
+ self.assertEqual(
+ line_entry2.GetLine(), 27, "Frame 2 should point to line 27"
+ )
+
+ # Verify the frames display properly in backtrace
+ # This tests that PC-less frames don't show 0xffffffffffffffff
+ self.runCmd("bt")
+ output = self.res.GetOutput()
+
+ # Should show function names
+ self.assertIn("compute_fibonacci", output)
+ self.assertIn("process_data", output)
+ self.assertIn("main", output)
+
+ # Should show Python file
+ self.assertIn("python_helper.py", output)
+
+ # Should show line numbers
+ self.assertIn(":7", output) # compute_fibonacci line
+ self.assertIn(":16", output) # process_data line
+ self.assertIn(":27", output) # main line
+
+ # Should NOT show invalid address (0xffffffffffffffff)
+ self.assertNotIn("0xffffffffffffffff", output.lower())
+
+ # Verify frame 3 is the original real frame 0
+ frame3 = thread.GetFrameAtIndex(3)
+ self.assertIsNotNone(frame3)
+ self.assertIn("thread_func", frame3.GetFunctionName())
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/python_helper.py b/lldb/test/API/functionalities/scripted_frame_provider/python_helper.py
new file mode 100644
index 0000000000000..27f38165608db
--- /dev/null
+++ b/lldb/test/API/functionalities/scripted_frame_provider/python_helper.py
@@ -0,0 +1,36 @@
+"""
+Sample Python module to demonstrate Python source display in scripted frames.
+"""
+
+
+def compute_fibonacci(n):
+ """Compute the nth Fibonacci number."""
+ if n <= 1:
+ return n
+ a, b = 0, 1
+ for _ in range(n - 1):
+ a, b = b, a + b
+ return b
+
+
+def process_data(data):
+ """Process some data and return result."""
+ result = []
+ for item in data:
+ if isinstance(item, int):
+ result.append(item * 2)
+ elif isinstance(item, str):
+ result.append(item.upper())
+ return result
+
+
+def main():
+ """Main entry point for testing."""
+ fib_10 = compute_fibonacci(10)
+ data = [1, 2, "hello", 3, "world"]
+ processed = process_data(data)
+ return fib_10, processed
+
+
+if __name__ == "__main__":
+ main()
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
index b9731fdc0a197..6177f4345321c 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
@@ -10,6 +10,7 @@
index to create stackframes
"""
+import os
import lldb
from lldb.plugins.scripted_process import ScriptedFrame
from lldb.plugins.scripted_frame_provider import ScriptedFrameProvider
@@ -220,3 +221,98 @@ def get_frame_at_index(self, index):
# Pass through original frames at indices 1, 2, 3, ...
return index - 1
return None
+
+
+class PythonSourceFrame(ScriptedFrame):
+ """Scripted frame that points to Python source code."""
+
+ def __init__(self, thread, idx, function_name, python_file, line_number):
+ args = lldb.SBStructuredData()
+ super().__init__(thread, args)
+
+ self.idx = idx
+ self.function_name = function_name
+ self.python_file = python_file
+ self.line_number = line_number
+
+ def get_id(self):
+ """Return the frame index."""
+ return self.idx
+
+ def get_pc(self):
+ """PC-less frame - return invalid address."""
+ return lldb.LLDB_INVALID_ADDRESS
+
+ def get_function_name(self):
+ """Return the function name."""
+ return self.function_name
+
+ def get_symbol_context(self):
+ """Return a symbol context with LineEntry pointing to Python source."""
+ # Create a LineEntry pointing to the Python source file
+ line_entry = lldb.SBLineEntry()
+ line_entry.SetFileSpec(lldb.SBFileSpec(self.python_file, True))
+ line_entry.SetLine(self.line_number)
+ line_entry.SetColumn(0)
+
+ # Create a symbol context with the line entry
+ sym_ctx = lldb.SBSymbolContext()
+ sym_ctx.SetLineEntry(line_entry)
+
+ return sym_ctx
+
+ def is_artificial(self):
+ """Not artificial."""
+ return False
+
+ def is_hidden(self):
+ """Not hidden."""
+ return False
+
+ def get_register_context(self):
+ """No register context for PC-less frames."""
+ return None
+
+
+class PythonSourceFrameProvider(ScriptedFrameProvider):
+ """
+ Provider that demonstrates Python source display in scripted frames.
+
+ This provider prepends frames pointing to Python source code, showing
+ that PC-less frames can display Python source files with proper line
+ numbers and module/compile unit information.
+ """
+
+ def __init__(self, input_frames, args):
+ super().__init__(input_frames, args)
+
+ # Find the python_helper.py file
+ current_dir = os.path.dirname(os.path.abspath(__file__))
+ self.python_file = os.path.join(current_dir, "python_helper.py")
+
+ @staticmethod
+ def get_description():
+ """Return a description of this provider."""
+ return "Provider that prepends frames pointing to Python source"
+
+ def get_frame_at_index(self, index):
+ """Return Python source frames followed by original frames."""
+ if index == 0:
+ # Frame pointing to compute_fibonacci function (line 7)
+ return PythonSourceFrame(
+ self.thread, 0, "compute_fibonacci", self.python_file, 7
+ )
+ elif index == 1:
+ # Frame pointing to process_data function (line 16)
+ return PythonSourceFrame(
+ self.thread, 1, "process_data", self.python_file, 16
+ )
+ elif index == 2:
+ # Frame pointing to main function (line 27)
+ return PythonSourceFrame(
+ self.thread, 2, "main", self.python_file, 27
+ )
+ elif index - 3 < len(self.input_frames):
+ # Pass through original frames
+ return index - 3
+ return None
More information about the lldb-commits
mailing list