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