https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/170762
>From 24e2f000a99ad6dca8da083d087a86bf514faf62 Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani <[email protected]> Date: Thu, 4 Dec 2025 22:54:53 -0800 Subject: [PATCH] [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 <[email protected]> --- lldb/include/lldb/Target/StackID.h | 1 + lldb/source/Core/CoreProperties.td | 4 +- lldb/source/Core/FormatEntity.cpp | 170 +++++++++++------- .../Process/scripted/ScriptedFrame.cpp | 12 +- lldb/source/Target/StackFrame.cpp | 9 +- lldb/source/Target/StackFrameList.cpp | 4 + .../TestScriptedFrameProvider.py | 125 +++++++++++++ .../scripted_frame_provider/python_helper.py | 36 ++++ .../test_frame_providers.py | 96 ++++++++++ .../dummy_scripted_process.py | 25 +++ 10 files changed, 411 insertions(+), 71 deletions(-) create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/python_helper.py diff --git a/lldb/include/lldb/Target/StackID.h b/lldb/include/lldb/Target/StackID.h index 18461533d648a..3f6a83b5e2fa5 100644 --- a/lldb/include/lldb/Target/StackID.h +++ b/lldb/include/lldb/Target/StackID.h @@ -52,6 +52,7 @@ class StackID { protected: friend class StackFrame; + friend class SyntheticStackFrameList; void SetPC(lldb::addr_t pc, Process *process); void SetCFA(lldb::addr_t cfa, Process *process); 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 c528a14fa76d0..9ddbf59ebdfe1 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) @@ -1951,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; @@ -1975,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 265bc28a8957f..748ca5cf03414 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" @@ -98,9 +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; - } StructuredData::DictionarySP reg_info = scripted_frame_interface->GetRegisterInfo(); @@ -162,7 +166,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 +175,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..3bbb851b88007 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 @@ -2057,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/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index 5d1a8a8370414..50f8c47c84bb4 100644 --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -64,6 +64,8 @@ SyntheticStackFrameList::SyntheticStackFrameList( bool SyntheticStackFrameList::FetchFramesUpTo( uint32_t end_idx, InterruptionControl allow_interrupt) { + + size_t num_synthetic_frames = 0; // Check if the thread has a synthetic frame provider. if (auto provider_sp = m_thread.GetFrameProvider()) { // Use the synthetic frame provider to generate frames lazily. @@ -81,6 +83,8 @@ bool SyntheticStackFrameList::FetchFramesUpTo( break; } StackFrameSP frame_sp = *frame_or_err; + if (frame_sp->IsSynthetic()) + frame_sp->GetStackID().SetCFA(num_synthetic_frames++, GetThread().GetProcess().get()); // Set the frame list weak pointer so ExecutionContextRef can resolve // the frame without calling Thread::GetStackFrameList(). frame_sp->m_frame_list_wp = shared_from_this(); diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py index caed94f5f93da..06e55e79d538c 100644 --- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py +++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py @@ -426,3 +426,128 @@ 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", + ) + + 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 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 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
