From a9ff5bd776e6787a923ae67a7c3821887b30d975 Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani Date: Fri, 5 Dec 2025 15:52:31 -0800 Subject: [PATCH] [lldb] Add support for PC-less scripted frames Scripted frames that materialize Python functions or other non-native code are PC-less by design, meaning they don't have valid program counter values. Previously, these frames would display invalid addresses (0xffffffffffffffff) in backtrace output. This patch updates FormatEntity to detect and suppress invalid address display for PC-less frames, adds fallback to frame methods when symbol context is unavailable, and modifies StackFrame::GetSymbolContext to skip PC-based symbol resolution for invalid addresses. The changes enable PC-less frames to display cleanly with proper function names, file paths, and line numbers, and allow for source display of foreign sources (like Python). Includes comprehensive test coverage demonstrating frames pointing to Python source files. Signed-off-by: Med Ismail Bennani --- lldb/include/lldb/Target/StackID.h | 1 + lldb/source/Core/CoreProperties.td | 4 +- lldb/source/Core/FormatEntity.cpp | 161 +++++++------ .../Process/scripted/ScriptedFrame.cpp | 163 ++++++++----- .../Plugins/Process/scripted/ScriptedFrame.h | 7 +- lldb/source/Target/StackFrame.cpp | 9 +- lldb/source/Target/StackFrameList.cpp | 5 + .../TestScriptedFrameProvider.py | 223 ++++++++++++++---- .../scripted_frame_provider/python_helper.py | 36 +++ .../test_frame_providers.py | 94 ++++++++ .../dummy_scripted_process.py | 29 +++ 11 files changed, 553 insertions(+), 179 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..a54d5538f4c0c 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..faafb5eb6e4f2 100644 --- a/lldb/source/Core/FormatEntity.cpp +++ b/lldb/source/Core/FormatEntity.cpp @@ -1684,10 +1684,9 @@ 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; - } } } return false; @@ -1808,70 +1807,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 +1918,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) @@ -1946,12 +1979,11 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, case Entry::Type::FunctionPCOffset: if (exe_ctx) { StackFrame *frame = exe_ctx->GetFramePtr(); - if (frame) { + if (frame) if (DumpAddressOffsetFromFunction(s, sc, exe_ctx, frame->GetFrameCodeAddress(), false, false, false)) return true; - } } return false; @@ -1975,11 +2007,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..70ce101c6c834 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,45 +103,20 @@ ScriptedFrame::Create(ThreadSP thread_sp, std::optional 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(); - - if (!reg_info) - return llvm::createStringError( - "failed to get scripted frame registers info"); - - std::shared_ptr register_info_sp = - DynamicRegisterInfo::Create(*reg_info, - process_sp->GetTarget().GetArchitecture()); lldb::RegisterContextSP reg_ctx_sp; - - std::optional reg_data = - scripted_frame_interface->GetRegisterContext(); - if (reg_data) { - DataBufferSP data_sp( - std::make_shared(reg_data->c_str(), reg_data->size())); - - if (!data_sp->GetByteSize()) - return llvm::createStringError("failed to copy raw registers data"); - - std::shared_ptr reg_ctx_memory = - std::make_shared( - *thread_sp, frame_id, *register_info_sp, LLDB_INVALID_ADDRESS); - if (!reg_ctx_memory) - return llvm::createStringError("failed to create a register context"); - - reg_ctx_memory->SetAllRegisterData(data_sp); - reg_ctx_sp = reg_ctx_memory; - } - - return std::make_shared( - thread_sp, scripted_frame_interface, frame_id, pc, sc, reg_ctx_sp, - register_info_sp, owned_script_object_sp); + auto regs_or_err = + CreateRegisterContext(*scripted_frame_interface, *thread_sp, frame_id); + if (!regs_or_err) + LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), regs_or_err.takeError(), "{0}"); + else + reg_ctx_sp = *regs_or_err; + + return std::make_shared(thread_sp, scripted_frame_interface, + frame_id, pc, sc, reg_ctx_sp, + owned_script_object_sp); } ScriptedFrame::ScriptedFrame(ThreadSP thread_sp, @@ -144,14 +124,13 @@ ScriptedFrame::ScriptedFrame(ThreadSP thread_sp, lldb::user_id_t id, lldb::addr_t pc, SymbolContext &sym_ctx, lldb::RegisterContextSP reg_ctx_sp, - std::shared_ptr reg_info_sp, StructuredData::GenericSP script_object_sp) : StackFrame(thread_sp, /*frame_idx=*/id, /*concrete_frame_idx=*/id, /*reg_context_sp=*/reg_ctx_sp, /*cfa=*/0, /*pc=*/pc, /*behaves_like_zeroth_frame=*/!id, /*symbol_ctx=*/&sym_ctx), m_scripted_frame_interface_sp(interface_sp), - m_script_object_sp(script_object_sp), m_register_info_sp(reg_info_sp) { + m_script_object_sp(script_object_sp) { // FIXME: This should be part of the base class constructor. m_stack_frame_kind = StackFrame::Kind::Synthetic; } @@ -162,7 +141,7 @@ const char *ScriptedFrame::GetFunctionName() { CheckInterpreterAndScriptObject(); std::optional function_name = GetInterface()->GetFunctionName(); if (!function_name) - return nullptr; + return StackFrame::GetFunctionName(); return ConstString(function_name->c_str()).AsCString(); } @@ -171,7 +150,7 @@ const char *ScriptedFrame::GetDisplayFunctionName() { std::optional function_name = GetInterface()->GetDisplayFunctionName(); if (!function_name) - return nullptr; + return StackFrame::GetDisplayFunctionName(); return ConstString(function_name->c_str()).AsCString(); } @@ -190,35 +169,99 @@ lldb::ScriptedFrameInterfaceSP ScriptedFrame::GetInterface() const { std::shared_ptr ScriptedFrame::GetDynamicRegisterInfo() { CheckInterpreterAndScriptObject(); - if (!m_register_info_sp) { - StructuredData::DictionarySP reg_info = GetInterface()->GetRegisterInfo(); + StructuredData::DictionarySP reg_info = GetInterface()->GetRegisterInfo(); + + Status error; + if (!reg_info) + return ScriptedInterface::ErrorWithMessage< + std::shared_ptr>( + LLVM_PRETTY_FUNCTION, "failed to get scripted frame registers info", + error, LLDBLog::Thread); + + ThreadSP thread_sp = m_thread_wp.lock(); + if (!thread_sp || !thread_sp->IsValid()) + return ScriptedInterface::ErrorWithMessage< + std::shared_ptr>( + LLVM_PRETTY_FUNCTION, + "failed to get scripted frame registers info: invalid thread", error, + LLDBLog::Thread); + + ProcessSP process_sp = thread_sp->GetProcess(); + if (!process_sp || !process_sp->IsValid()) + return ScriptedInterface::ErrorWithMessage< + std::shared_ptr>( + LLVM_PRETTY_FUNCTION, + "failed to get scripted frame registers info: invalid process", error, + LLDBLog::Thread); + + return DynamicRegisterInfo::Create(*reg_info, + process_sp->GetTarget().GetArchitecture()); +} +llvm::Expected +ScriptedFrame::CreateRegisterContext(ScriptedFrameInterface &interface, + Thread &thread, lldb::user_id_t frame_id) { + StructuredData::DictionarySP reg_info = interface.GetRegisterInfo(); + + if (!reg_info) + return llvm::createStringError( + "failed to get scripted frame registers info"); + + std::shared_ptr register_info_sp = + DynamicRegisterInfo::Create( + *reg_info, thread.GetProcess()->GetTarget().GetArchitecture()); + + lldb::RegisterContextSP reg_ctx_sp; + + std::optional reg_data = interface.GetRegisterContext(); + if (!reg_data) + return llvm::createStringError( + "failed to get scripted frame registers data"); + + DataBufferSP data_sp( + std::make_shared(reg_data->c_str(), reg_data->size())); + + if (!data_sp->GetByteSize()) + return llvm::createStringError("failed to copy raw registers data"); + + std::shared_ptr reg_ctx_memory = + std::make_shared( + thread, frame_id, *register_info_sp, LLDB_INVALID_ADDRESS); + + reg_ctx_memory->SetAllRegisterData(data_sp); + reg_ctx_sp = reg_ctx_memory; + + return reg_ctx_sp; +} + +lldb::RegisterContextSP ScriptedFrame::GetRegisterContext() { + if (!m_reg_context_sp) { Status error; - if (!reg_info) - return ScriptedInterface::ErrorWithMessage< - std::shared_ptr>( - LLVM_PRETTY_FUNCTION, "failed to get scripted frame registers info", + if (!m_scripted_frame_interface_sp) + return ScriptedInterface::ErrorWithMessage( + LLVM_PRETTY_FUNCTION, + "failed to get scripted frame registers context: invalid interface", error, LLDBLog::Thread); - ThreadSP thread_sp = m_thread_wp.lock(); - if (!thread_sp || !thread_sp->IsValid()) - return ScriptedInterface::ErrorWithMessage< - std::shared_ptr>( + ThreadSP thread_sp = GetThread(); + if (!thread_sp) + return ScriptedInterface::ErrorWithMessage( LLVM_PRETTY_FUNCTION, - "failed to get scripted frame registers info: invalid thread", error, - LLDBLog::Thread); + "failed to get scripted frame registers context: invalid thread", + error, LLDBLog::Thread); - ProcessSP process_sp = thread_sp->GetProcess(); - if (!process_sp || !process_sp->IsValid()) - return ScriptedInterface::ErrorWithMessage< - std::shared_ptr>( + auto regs_or_err = CreateRegisterContext(*m_scripted_frame_interface_sp, + *thread_sp, GetFrameIndex()); + if (!regs_or_err) { + error = Status::FromError(regs_or_err.takeError()); + return ScriptedInterface::ErrorWithMessage( LLVM_PRETTY_FUNCTION, - "failed to get scripted frame registers info: invalid process", error, + "failed to get scripted frame registers context", error, LLDBLog::Thread); + } - m_register_info_sp = DynamicRegisterInfo::Create( - *reg_info, process_sp->GetTarget().GetArchitecture()); + m_reg_context_sp = *regs_or_err; } - return m_register_info_sp; + return m_reg_context_sp; } diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h index d1cbd429d4979..0545548e912e6 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h +++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h @@ -26,7 +26,6 @@ class ScriptedFrame : public lldb_private::StackFrame { lldb::ScriptedFrameInterfaceSP interface_sp, lldb::user_id_t frame_idx, lldb::addr_t pc, SymbolContext &sym_ctx, lldb::RegisterContextSP reg_ctx_sp, - std::shared_ptr reg_info_sp, StructuredData::GenericSP script_object_sp = nullptr); ~ScriptedFrame() override; @@ -62,6 +61,8 @@ class ScriptedFrame : public lldb_private::StackFrame { const char *GetFunctionName() override; const char *GetDisplayFunctionName() override; + lldb::RegisterContextSP GetRegisterContext() override; + bool isA(const void *ClassID) const override { return ClassID == &ID || StackFrame::isA(ClassID); } @@ -70,6 +71,9 @@ class ScriptedFrame : public lldb_private::StackFrame { private: void CheckInterpreterAndScriptObject() const; lldb::ScriptedFrameInterfaceSP GetInterface() const; + static llvm::Expected + CreateRegisterContext(ScriptedFrameInterface &interface, Thread &thread, + lldb::user_id_t frame_id); ScriptedFrame(const ScriptedFrame &) = delete; const ScriptedFrame &operator=(const ScriptedFrame &) = delete; @@ -78,7 +82,6 @@ class ScriptedFrame : public lldb_private::StackFrame { lldb::ScriptedFrameInterfaceSP m_scripted_frame_interface_sp; lldb_private::StructuredData::GenericSP m_script_object_sp; - std::shared_ptr m_register_info_sp; static char ID; }; 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..896a760f61d26 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,9 @@ 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..922cb7f326f33 100644 --- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py +++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py @@ -25,11 +25,11 @@ def test_replace_all_frames(self): self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False ) - # Import the test frame provider + # Import the test frame provider. script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") self.runCmd("command script import " + script_path) - # Attach the Replace provider + # Attach the Replace provider. error = lldb.SBError() provider_id = target.RegisterScriptedFrameProvider( "test_frame_providers.ReplaceFrameProvider", @@ -39,10 +39,10 @@ def test_replace_all_frames(self): self.assertTrue(error.Success(), f"Failed to register provider: {error}") self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") - # Verify we have exactly 3 synthetic frames + # Verify we have exactly 3 synthetic frames. self.assertEqual(thread.GetNumFrames(), 3, "Should have 3 synthetic frames") - # Verify frame indices and PCs (dictionary-based frames don't have custom function names) + # Verify frame indices and PCs (dictionary-based frames don't have custom function names). frame0 = thread.GetFrameAtIndex(0) self.assertIsNotNone(frame0) self.assertEqual(frame0.GetPC(), 0x1000) @@ -62,13 +62,13 @@ def test_prepend_frames(self): self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False ) - # Get original frame count and PC + # Get original frame count and PC. original_frame_count = thread.GetNumFrames() self.assertGreaterEqual( original_frame_count, 2, "Should have at least 2 real frames" ) - # Import and attach Prepend provider + # Import and attach Prepend provider. script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") self.runCmd("command script import " + script_path) @@ -81,18 +81,18 @@ def test_prepend_frames(self): self.assertTrue(error.Success(), f"Failed to register provider: {error}") self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") - # Verify we have 2 more frames + # Verify we have 2 more frames. new_frame_count = thread.GetNumFrames() self.assertEqual(new_frame_count, original_frame_count + 2) - # Verify first 2 frames are synthetic (check PCs, not function names) + # Verify first 2 frames are synthetic (check PCs, not function names). frame0 = thread.GetFrameAtIndex(0) self.assertEqual(frame0.GetPC(), 0x9000) frame1 = thread.GetFrameAtIndex(1) self.assertEqual(frame1.GetPC(), 0xA000) - # Verify frame 2 is the original real frame 0 + # Verify frame 2 is the original real frame 0. frame2 = thread.GetFrameAtIndex(2) self.assertIn("thread_func", frame2.GetFunctionName()) @@ -103,10 +103,10 @@ def test_append_frames(self): self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False ) - # Get original frame count + # Get original frame count. original_frame_count = thread.GetNumFrames() - # Import and attach Append provider + # Import and attach Append provider. script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") self.runCmd("command script import " + script_path) @@ -119,11 +119,11 @@ def test_append_frames(self): self.assertTrue(error.Success(), f"Failed to register provider: {error}") self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") - # Verify we have 1 more frame + # Verify we have 1 more frame. new_frame_count = thread.GetNumFrames() self.assertEqual(new_frame_count, original_frame_count + 1) - # Verify first frames are still real + # Verify first frames are still real. frame0 = thread.GetFrameAtIndex(0) self.assertIn("thread_func", frame0.GetFunctionName()) @@ -137,7 +137,7 @@ def test_scripted_frame_objects(self): self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False ) - # Import the provider that returns ScriptedFrame objects + # Import the provider that returns ScriptedFrame objects. script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") self.runCmd("command script import " + script_path) @@ -150,12 +150,12 @@ def test_scripted_frame_objects(self): self.assertTrue(error.Success(), f"Failed to register provider: {error}") self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") - # Verify we have 5 frames + # Verify we have 5 frames. self.assertEqual( thread.GetNumFrames(), 5, "Should have 5 custom scripted frames" ) - # Verify frame properties from CustomScriptedFrame + # Verify frame properties from CustomScriptedFrame. frame0 = thread.GetFrameAtIndex(0) self.assertIsNotNone(frame0) self.assertEqual(frame0.GetFunctionName(), "custom_scripted_frame_0") @@ -179,17 +179,17 @@ def test_applies_to_thread(self): self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False ) - # We should have at least 2 threads (worker threads) at the breakpoint + # We should have at least 2 threads (worker threads) at the breakpoint. num_threads = process.GetNumThreads() self.assertGreaterEqual( num_threads, 2, "Should have at least 2 threads at breakpoint" ) - # Import the test frame provider + # Import the test frame provider. script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") self.runCmd("command script import " + script_path) - # Collect original thread info before applying provider + # Collect original thread info before applying provider. thread_info = {} for i in range(num_threads): t = process.GetThreadAtIndex(i) @@ -198,7 +198,7 @@ def test_applies_to_thread(self): "pc": t.GetFrameAtIndex(0).GetPC(), } - # Register the ThreadFilterFrameProvider which only applies to thread ID 1 + # Register the ThreadFilterFrameProvider which only applies to thread ID 1. error = lldb.SBError() provider_id = target.RegisterScriptedFrameProvider( "test_frame_providers.ThreadFilterFrameProvider", @@ -208,9 +208,9 @@ def test_applies_to_thread(self): self.assertTrue(error.Success(), f"Failed to register provider: {error}") self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") - # Check each thread + # Check each thread. thread_id_1_found = False - # On ARM32, FixCodeAddress clears bit 0, so synthetic PCs get modified + # On ARM32, FixCodeAddress clears bit 0, so synthetic PCs get modified. is_arm_32bit = lldbplatformutil.getArchitecture() == "arm" expected_synthetic_pc = 0xFFFE if is_arm_32bit else 0xFFFF @@ -219,7 +219,7 @@ def test_applies_to_thread(self): thread_id = t.GetIndexID() if thread_id == 1: - # Thread with ID 1 should have synthetic frame + # Thread with ID 1 should have synthetic frame. thread_id_1_found = True self.assertEqual( t.GetNumFrames(), @@ -232,7 +232,7 @@ def test_applies_to_thread(self): f"Thread with ID 1 should have synthetic PC {expected_synthetic_pc:#x}", ) else: - # Other threads should keep their original frames + # Other threads should keep their original frames. self.assertEqual( t.GetNumFrames(), thread_info[thread_id]["frame_count"], @@ -244,7 +244,7 @@ def test_applies_to_thread(self): f"Thread with ID {thread_id} should have its original PC", ) - # We should have found at least one thread with ID 1 + # We should have found at least one thread with ID 1. self.assertTrue( thread_id_1_found, "Should have found a thread with ID 1 to test filtering", @@ -257,15 +257,15 @@ def test_remove_frame_provider_by_id(self): self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False ) - # Import the test frame providers + # Import the test frame providers. script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") self.runCmd("command script import " + script_path) - # Get original frame count + # Get original frame count. original_frame_count = thread.GetNumFrames() original_pc = thread.GetFrameAtIndex(0).GetPC() - # Register the first provider and get its ID + # Register the first provider and get its ID. error = lldb.SBError() provider_id_1 = target.RegisterScriptedFrameProvider( "test_frame_providers.ReplaceFrameProvider", @@ -274,13 +274,13 @@ def test_remove_frame_provider_by_id(self): ) self.assertTrue(error.Success(), f"Failed to register provider 1: {error}") - # Verify first provider is active (3 synthetic frames) + # Verify first provider is active (3 synthetic frames). self.assertEqual(thread.GetNumFrames(), 3, "Should have 3 synthetic frames") self.assertEqual( thread.GetFrameAtIndex(0).GetPC(), 0x1000, "Should have first provider's PC" ) - # Register a second provider and get its ID + # Register a second provider and get its ID. provider_id_2 = target.RegisterScriptedFrameProvider( "test_frame_providers.PrependFrameProvider", lldb.SBStructuredData(), @@ -299,10 +299,10 @@ def test_remove_frame_provider_by_id(self): result, f"Should successfully remove provider with ID {provider_id_1}" ) - # After removing the first provider, the second provider should still be active - # The PrependFrameProvider adds 2 frames before the real stack + # After removing the first provider, the second provider should still be + # active. The PrependFrameProvider adds 2 frames before the real stack. # Since ReplaceFrameProvider had 3 frames, and we removed it, we should now - # have the original frames (from real stack) with PrependFrameProvider applied + # have the original frames (from real stack) with PrependFrameProvider applied. new_frame_count = thread.GetNumFrames() self.assertEqual( new_frame_count, @@ -310,7 +310,7 @@ def test_remove_frame_provider_by_id(self): "Should have original frames + 2 prepended frames", ) - # First two frames should be from PrependFrameProvider + # First two frames should be from PrependFrameProvider. self.assertEqual( thread.GetFrameAtIndex(0).GetPC(), 0x9000, @@ -322,13 +322,13 @@ def test_remove_frame_provider_by_id(self): "Second frame should be from PrependFrameProvider", ) - # Remove the second provider + # Remove the second provider. result = target.RemoveScriptedFrameProvider(provider_id_2) self.assertSuccess( result, f"Should successfully remove provider with ID {provider_id_2}" ) - # After removing both providers, frames should be back to original + # After removing both providers, frames should be back to original. self.assertEqual( thread.GetNumFrames(), original_frame_count, @@ -340,7 +340,7 @@ def test_remove_frame_provider_by_id(self): "Should restore original PC", ) - # Try to remove a provider that doesn't exist + # Try to remove a provider that doesn't exist. result = target.RemoveScriptedFrameProvider(999999) self.assertTrue(result.Fail(), "Should fail to remove non-existent provider") @@ -364,19 +364,19 @@ def test_circular_dependency_fix(self): self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False ) - # Get original frame count and PC + # Get original frame count and PC. original_frame_count = thread.GetNumFrames() original_pc = thread.GetFrameAtIndex(0).GetPC() self.assertGreaterEqual( original_frame_count, 2, "Should have at least 2 real frames" ) - # Import the provider that accesses input frames in __init__ + # Import the provider that accesses input frames in __init__. script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") self.runCmd("command script import " + script_path) - # Register the CircularDependencyTestProvider - # Before the fix, this would crash or hang due to circular dependency + # Register the CircularDependencyTestProvider. + # Before the fix, this would crash or hang due to circular dependency. error = lldb.SBError() provider_id = target.RegisterScriptedFrameProvider( "test_frame_providers.CircularDependencyTestProvider", @@ -388,8 +388,8 @@ def test_circular_dependency_fix(self): self.assertTrue(error.Success(), f"Failed to register provider: {error}") self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") - # Verify the provider worked correctly - # Should have 1 synthetic frame + all original frames + # Verify the provider worked correctly, + # Should have 1 synthetic frame + all original frames. new_frame_count = thread.GetNumFrames() self.assertEqual( new_frame_count, @@ -397,11 +397,11 @@ def test_circular_dependency_fix(self): "Should have original frames + 1 synthetic frame", ) - # On ARM32, FixCodeAddress clears bit 0, so synthetic PCs get modified + # On ARM32, FixCodeAddress clears bit 0, so synthetic PCs get modified. is_arm_32bit = lldbplatformutil.getArchitecture() == "arm" expected_synthetic_pc = 0xDEADBEEE if is_arm_32bit else 0xDEADBEEF - # First frame should be synthetic + # First frame should be synthetic. frame0 = thread.GetFrameAtIndex(0) self.assertIsNotNone(frame0) self.assertEqual( @@ -410,7 +410,7 @@ def test_circular_dependency_fix(self): f"First frame should be synthetic frame with PC {expected_synthetic_pc:#x}", ) - # Second frame should be the original first frame + # Second frame should be the original first frame. frame1 = thread.GetFrameAtIndex(1) self.assertIsNotNone(frame1) self.assertEqual( @@ -419,10 +419,137 @@ def test_circular_dependency_fix(self): "Second frame should be original first frame", ) - # Verify we can still call methods on frames (no circular dependency!) + # Verify we can still call methods on frames (no circular dependency!). for i in range(min(3, new_frame_count)): frame = thread.GetFrameAtIndex(i) self.assertIsNotNone(frame) - # These calls should not trigger circular dependency + # 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 and not crash. + self.assertEqual( + frame0.GetPC(), + lldb.LLDB_INVALID_ADDRESS, + "PC-less frame should have LLDB_INVALID_ADDRESS", + ) + + self.assertEqual( + frame0.GetFP(), + lldb.LLDB_INVALID_ADDRESS, + "PC-less frame FP should return LLDB_INVALID_ADDRESS", + ) + self.assertEqual( + frame0.GetSP(), + lldb.LLDB_INVALID_ADDRESS, + "PC-less frame SP should return LLDB_INVALID_ADDRESS", + ) + self.assertEqual( + frame0.GetCFA(), + 0, + "PC-less frame CFA should return 0", + ) + + 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..76f859760bf4f 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,96 @@ 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..f676cfbf875ef 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,26 @@ 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