[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 22:55:22 PST 2025
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 <ismail at bennani.ma>
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 <ismail at bennani.ma>
---
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
More information about the lldb-commits
mailing list