diff --git a/lldb/bindings/python/static-binding/LLDBWrapPython.cpp b/lldb/bindings/python/static-binding/LLDBWrapPython.cpp index b5ca750b44a5f..e1d2f3a91c6a1 100644 --- a/lldb/bindings/python/static-binding/LLDBWrapPython.cpp +++ b/lldb/bindings/python/static-binding/LLDBWrapPython.cpp @@ -77543,12 +77543,12 @@ SWIGINTERN PyObject *_wrap_SBThread_GetFrames(PyObject *self, PyObject *args) { swig_obj[0] = args; res1 = SWIG_ConvertPtr(swig_obj[0], &argp1,SWIGTYPE_p_lldb__SBThread, 0 | 0 ); if (!SWIG_IsOK(res1)) { - SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "SBThread_GetFrames" "', argument " "1"" of type '" "lldb::SBThread *""'"); + SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "SBThread_GetFrames" "', argument " "1"" of type '" "lldb::SBThread const *""'"); } arg1 = reinterpret_cast< lldb::SBThread * >(argp1); { SWIG_PYTHON_THREAD_BEGIN_ALLOW; - result = (arg1)->GetFrames(); + result = ((lldb::SBThread const *)arg1)->GetFrames(); SWIG_PYTHON_THREAD_END_ALLOW; } resultobj = SWIG_NewPointerObj((new lldb::SBFrameList(result)), SWIGTYPE_p_lldb__SBFrameList, SWIG_POINTER_OWN | 0 ); diff --git a/lldb/examples/python/templates/scripted_frame_provider.py b/lldb/examples/python/templates/scripted_frame_provider.py index 7a72f1a24c9da..a45ef9427a532 100644 --- a/lldb/examples/python/templates/scripted_frame_provider.py +++ b/lldb/examples/python/templates/scripted_frame_provider.py @@ -79,6 +79,34 @@ def get_description(self): """ pass + @staticmethod + def get_priority(): + """Get the priority of this frame provider. + + This static method is called to determine the evaluation order when + multiple frame providers could apply to the same thread. Lower numbers + indicate higher priority (like Unix nice values). + + Returns: + int or None: Priority value where 0 is highest priority. + Return None for default priority (UINT32_MAX - lowest priority). + + Example: + + .. code-block:: python + + @staticmethod + def get_priority(): + # High priority - runs before most providers + return 10 + + @staticmethod + def get_priority(): + # Default priority - runs last + return None + """ + return None # Default/lowest priority + def __init__(self, input_frames, args): """Construct a scripted frame provider. diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h index 49b60131399d5..b04af0c817b6e 100644 --- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h +++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h @@ -39,6 +39,22 @@ class ScriptedFrameProviderInterface : public ScriptedInterface { /// empty string if no description is available. virtual std::string GetDescription(llvm::StringRef class_name) { return {}; } + /// Get the priority of this frame provider. + /// + /// This is called by the descriptor to fetch the priority from the + /// scripted implementation. Implementations should call a static method + /// on the scripting class to retrieve the priority. Lower numbers indicate + /// higher priority (like Unix nice values). + /// + /// \param class_name The name of the scripting class implementing the + /// provider. + /// + /// \return Priority value where 0 is highest priority, or std::nullopt for + /// default priority (UINT32_MAX - lowest priority). + virtual std::optional GetPriority(llvm::StringRef class_name) { + return std::nullopt; + } + virtual StructuredData::ObjectSP GetFrameAtIndex(uint32_t index) { return {}; } diff --git a/lldb/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h index 2bd1789b27e5d..9ac0a6ca64413 100644 --- a/lldb/include/lldb/Target/StackFrameList.h +++ b/lldb/include/lldb/Target/StackFrameList.h @@ -238,7 +238,8 @@ class SyntheticStackFrameList : public StackFrameList { public: SyntheticStackFrameList(Thread &thread, lldb::StackFrameListSP input_frames, const lldb::StackFrameListSP &prev_frames_sp, - bool show_inline_frames); + bool show_inline_frames, + lldb::SyntheticFrameProviderSP provider_sp); protected: /// Override FetchFramesUpTo to lazily return frames from the provider @@ -250,6 +251,9 @@ class SyntheticStackFrameList : public StackFrameList { /// The input stack frame list that the provider transforms. /// This could be a real StackFrameList or another SyntheticStackFrameList. lldb::StackFrameListSP m_input_frames; + + /// The provider that transforms the input frames. + lldb::SyntheticFrameProviderSP m_provider; }; } // namespace lldb_private diff --git a/lldb/include/lldb/Target/SyntheticFrameProvider.h b/lldb/include/lldb/Target/SyntheticFrameProvider.h index 2d5330cb03105..bbd52b144412d 100644 --- a/lldb/include/lldb/Target/SyntheticFrameProvider.h +++ b/lldb/include/lldb/Target/SyntheticFrameProvider.h @@ -56,6 +56,16 @@ struct ScriptedFrameProviderDescriptor { /// empty string if no description is available. std::string GetDescription() const; + /// Get the priority of this frame provider. + /// + /// Priority determines the order in which providers are evaluated when + /// multiple providers could apply to the same thread. Lower numbers indicate + /// higher priority (like Unix nice values). + /// + /// \return Priority value where 0 is highest priority, or std::nullopt for + /// default priority (UINT32_MAX - lowest priority). + std::optional GetPriority() const; + /// Check if this descriptor applies to the given thread. bool AppliesToThread(Thread &thread) const { // If no thread specs specified, applies to all threads. @@ -143,6 +153,17 @@ class SyntheticFrameProvider : public PluginInterface { virtual std::string GetDescription() const = 0; + /// Get the priority of this frame provider. + /// + /// Priority determines the order in which providers are evaluated when + /// multiple providers could apply to the same thread. Lower numbers indicate + /// higher priority (like Unix nice values). + /// + /// \return + /// Priority value where 0 is highest priority, or std::nullopt for + /// default priority (UINT32_MAX - lowest priority). + virtual std::optional GetPriority() const { return std::nullopt; } + /// Get a single stack frame at the specified index. /// /// This method is called lazily - frames are only created when requested. diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h index 1ad7e8e7f6fa3..ff442fdd79a03 100644 --- a/lldb/include/lldb/Target/Thread.h +++ b/lldb/include/lldb/Target/Thread.h @@ -1334,8 +1334,9 @@ class Thread : public std::enable_shared_from_this, void ClearScriptedFrameProvider(); - lldb::SyntheticFrameProviderSP GetFrameProvider() const { - return m_frame_provider_sp; + const llvm::SmallVector & + GetFrameProviders() const { + return m_frame_providers; } protected: @@ -1441,8 +1442,8 @@ class Thread : public std::enable_shared_from_this, /// The Thread backed by this thread, if any. lldb::ThreadWP m_backed_thread; - /// The Scripted Frame Provider, if any. - lldb::SyntheticFrameProviderSP m_frame_provider_sp; + /// The Scripted Frame Providers for this thread. + llvm::SmallVector m_frame_providers; private: bool m_extended_info_fetched; // Have we tried to retrieve the m_extended_info diff --git a/lldb/source/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td index 2bd6eba5c9b07..9d2fd6d96448f 100644 --- a/lldb/source/Core/CoreProperties.td +++ b/lldb/source/Core/CoreProperties.td @@ -142,7 +142,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, @@ -316,7 +316,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/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp index 3dde5036453f4..2d87c1b10bfec 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp @@ -70,6 +70,24 @@ std::string ScriptedFrameProviderPythonInterface::GetDescription( return obj->GetStringValue().str(); } +std::optional +ScriptedFrameProviderPythonInterface::GetPriority(llvm::StringRef class_name) { + Status error; + StructuredData::ObjectSP obj = + CallStaticMethod(class_name, "get_priority", error); + + if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj, + error)) + return std::nullopt; + + // Try to extract as unsigned integer. Return nullopt if Python returned None + // or if extraction fails. + if (StructuredData::UnsignedInteger *int_obj = obj->GetAsUnsignedInteger()) + return static_cast(int_obj->GetValue()); + + return std::nullopt; +} + StructuredData::ObjectSP ScriptedFrameProviderPythonInterface::GetFrameAtIndex(uint32_t index) { Status error; diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h index 97a5cc7c669ea..884b0355a659e 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h @@ -43,6 +43,8 @@ class ScriptedFrameProviderPythonInterface std::string GetDescription(llvm::StringRef class_name) override; + std::optional GetPriority(llvm::StringRef class_name) override; + StructuredData::ObjectSP GetFrameAtIndex(uint32_t index) override; static void Initialize(); diff --git a/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp index 739963e6f0c2f..4aad8f2cb628f 100644 --- a/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp +++ b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp @@ -106,6 +106,13 @@ std::string ScriptedFrameProvider::GetDescription() const { return m_interface_sp->GetDescription(m_descriptor.GetName()); } +std::optional ScriptedFrameProvider::GetPriority() const { + if (!m_interface_sp) + return std::nullopt; + + return m_interface_sp->GetPriority(m_descriptor.GetName()); +} + llvm::Expected ScriptedFrameProvider::GetFrameAtIndex(uint32_t idx) { if (!m_interface_sp) diff --git a/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h index 3434bf26ade24..6937f9acbc9a9 100644 --- a/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h +++ b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h @@ -40,6 +40,8 @@ class ScriptedFrameProvider : public SyntheticFrameProvider { std::string GetDescription() const override; + std::optional GetPriority() const override; + /// Get a single stack frame at the specified index. llvm::Expected GetFrameAtIndex(uint32_t idx) override; diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index e9f81503a1110..c3441d8c958d5 100644 --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -58,23 +58,24 @@ StackFrameList::~StackFrameList() { SyntheticStackFrameList::SyntheticStackFrameList( Thread &thread, lldb::StackFrameListSP input_frames, - const lldb::StackFrameListSP &prev_frames_sp, bool show_inline_frames) + const lldb::StackFrameListSP &prev_frames_sp, bool show_inline_frames, + lldb::SyntheticFrameProviderSP provider_sp) : StackFrameList(thread, prev_frames_sp, show_inline_frames), - m_input_frames(std::move(input_frames)) {} + m_input_frames(std::move(input_frames)), + m_provider(std::move(provider_sp)) {} 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. + // Use the provider to generate frames lazily. + if (m_provider) { // Keep fetching until we reach end_idx or the provider returns an error. for (uint32_t idx = m_frames.size(); idx <= end_idx; idx++) { if (allow_interrupt && m_thread.GetProcess()->GetTarget().GetDebugger().InterruptRequested()) return true; - auto frame_or_err = provider_sp->GetFrameAtIndex(idx); + auto frame_or_err = m_provider->GetFrameAtIndex(idx); if (!frame_or_err) { // Provider returned error - we've reached the end. LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), frame_or_err.takeError(), diff --git a/lldb/source/Target/SyntheticFrameProvider.cpp b/lldb/source/Target/SyntheticFrameProvider.cpp index 97ff42d1ed53e..e799ad23b7512 100644 --- a/lldb/source/Target/SyntheticFrameProvider.cpp +++ b/lldb/source/Target/SyntheticFrameProvider.cpp @@ -34,6 +34,13 @@ void ScriptedFrameProviderDescriptor::Dump(Stream *s) const { if (!description.empty()) s->Printf(" Description: %s\n", description.c_str()); + // Show priority information. + std::optional priority = GetPriority(); + if (priority.has_value()) + s->Printf(" Priority: %u\n", *priority); + else + s->PutCString(" Priority: Default (no priority specified)\n"); + // Show thread filter information. if (thread_specs.empty()) { s->PutCString(" Thread Filter: (applies to all threads)\n"); @@ -62,6 +69,13 @@ std::string ScriptedFrameProviderDescriptor::GetDescription() const { return {}; } +std::optional ScriptedFrameProviderDescriptor::GetPriority() const { + // If we have an interface, call get_priority() to fetch it. + if (interface_sp && scripted_metadata_sp) + return interface_sp->GetPriority(scripted_metadata_sp->GetClassName()); + return std::nullopt; +} + llvm::Expected SyntheticFrameProvider::CreateInstance( StackFrameListSP input_frames, const ScriptedFrameProviderDescriptor &descriptor) { diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index 206192b9c407d..bd20a13cfa931 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -263,7 +263,7 @@ void Thread::DestroyThread() { std::lock_guard guard(m_frame_mutex); m_curr_frames_sp.reset(); m_prev_frames_sp.reset(); - m_frame_provider_sp.reset(); + m_frame_providers.clear(); m_prev_framezero_pc.reset(); } @@ -1500,33 +1500,50 @@ StackFrameListSP Thread::GetStackFrameList() { if (m_curr_frames_sp) return m_curr_frames_sp; - // First, try to load a frame provider if we don't have one yet. - if (!m_frame_provider_sp) { + // First, try to load frame providers if we don't have any yet. + if (m_frame_providers.empty()) { ProcessSP process_sp = GetProcess(); if (process_sp) { Target &target = process_sp->GetTarget(); const auto &descriptors = target.GetScriptedFrameProviderDescriptors(); - // Find first descriptor that applies to this thread. + // Collect all descriptors that apply to this thread. + std::vector + applicable_descriptors; for (const auto &entry : descriptors) { const ScriptedFrameProviderDescriptor &descriptor = entry.second; - if (descriptor.IsValid() && descriptor.AppliesToThread(*this)) { - if (llvm::Error error = LoadScriptedFrameProvider(descriptor)) { - LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), std::move(error), - "Failed to load scripted frame provider: {0}"); - } - break; // Use first matching descriptor (success or failure). + if (descriptor.IsValid() && descriptor.AppliesToThread(*this)) + applicable_descriptors.push_back(&descriptor); + } + + // Sort by priority (lower number = higher priority). + llvm::sort(applicable_descriptors, + [](const ScriptedFrameProviderDescriptor *a, + const ScriptedFrameProviderDescriptor *b) { + // nullopt (no priority) sorts last (UINT32_MAX). + uint32_t priority_a = a->GetPriority().value_or(UINT32_MAX); + uint32_t priority_b = b->GetPriority().value_or(UINT32_MAX); + return priority_a < priority_b; + }); + + // Load ALL matching providers in priority order. + for (const auto *descriptor : applicable_descriptors) { + if (llvm::Error error = LoadScriptedFrameProvider(*descriptor)) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), std::move(error), + "Failed to load scripted frame provider: {0}"); + continue; // Try next provider if this one fails. } } } } - // Create the frame list based on whether we have a provider. - if (m_frame_provider_sp) { - // We have a provider - create synthetic frame list. - StackFrameListSP input_frames = m_frame_provider_sp->GetInputFrames(); + // Create the frame list based on whether we have providers. + if (!m_frame_providers.empty()) { + // We have providers - use the last one in the chain. + // The last provider has already been chained with all previous providers. + StackFrameListSP input_frames = m_frame_providers.back()->GetInputFrames(); m_curr_frames_sp = std::make_shared( - *this, input_frames, m_prev_frames_sp, true); + *this, input_frames, m_prev_frames_sp, true, m_frame_providers.back()); } else { // No provider - use normal unwinder frames. m_curr_frames_sp = @@ -1540,29 +1557,39 @@ llvm::Error Thread::LoadScriptedFrameProvider( const ScriptedFrameProviderDescriptor &descriptor) { std::lock_guard guard(m_frame_mutex); - // Note: We don't create input_frames here - it will be created lazily - // by SyntheticStackFrameList when frames are first fetched. - // Creating them too early can cause crashes during thread initialization. - - // Create a temporary StackFrameList just to get the thread reference for the - // provider. The provider won't actually use this - it will get real input - // frames from SyntheticStackFrameList later. - StackFrameListSP temp_frames = - std::make_shared(*this, m_prev_frames_sp, true); + // Create input frames for this provider: + // - If no providers exist yet, use real unwinder frames. + // - If providers exist, wrap the previous provider in a + // SyntheticStackFrameList. + // This creates the chain: each provider's OUTPUT becomes the next + // provider's INPUT. + StackFrameListSP new_provider_input_frames; + if (m_frame_providers.empty()) { + // First provider gets real unwinder frames. + new_provider_input_frames = + std::make_shared(*this, m_prev_frames_sp, true); + } else { + // Subsequent providers get the previous provider's OUTPUT. + // We create a SyntheticStackFrameList that wraps the previous provider. + SyntheticFrameProviderSP prev_provider = m_frame_providers.back(); + StackFrameListSP prev_provider_frames = prev_provider->GetInputFrames(); + new_provider_input_frames = std::make_shared( + *this, prev_provider_frames, m_prev_frames_sp, true, prev_provider); + } - auto provider_or_err = - SyntheticFrameProvider::CreateInstance(temp_frames, descriptor); + auto provider_or_err = SyntheticFrameProvider::CreateInstance( + new_provider_input_frames, descriptor); if (!provider_or_err) return provider_or_err.takeError(); - ClearScriptedFrameProvider(); - m_frame_provider_sp = *provider_or_err; + // Append to the chain. + m_frame_providers.push_back(*provider_or_err); return llvm::Error::success(); } void Thread::ClearScriptedFrameProvider() { std::lock_guard guard(m_frame_mutex); - m_frame_provider_sp.reset(); + m_frame_providers.clear(); m_curr_frames_sp.reset(); m_prev_frames_sp.reset(); } @@ -1587,7 +1614,7 @@ void Thread::ClearStackFrames() { m_prev_frames_sp.swap(m_curr_frames_sp); m_curr_frames_sp.reset(); - m_frame_provider_sp.reset(); + m_frame_providers.clear(); m_extended_info.reset(); m_extended_info_fetched = false; } diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py index 922cb7f326f33..348147a277cef 100644 --- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py +++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py @@ -553,3 +553,181 @@ def test_python_source_frames(self): frame3 = thread.GetFrameAtIndex(3) self.assertIsNotNone(frame3) self.assertIn("thread_func", frame3.GetFunctionName()) + + def test_valid_pc_no_module_frames(self): + """Test that frames with valid PC but no module display correctly in backtrace.""" + 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 ValidPCNoModuleFrameProvider. + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.ValidPCNoModuleFrameProvider", + 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 2 more frames (the synthetic frames). + new_frame_count = thread.GetNumFrames() + self.assertEqual( + new_frame_count, + original_frame_count + 2, + "Should have original frames + 2 synthetic frames", + ) + + # Verify first two frames have valid PCs and function names. + frame0 = thread.GetFrameAtIndex(0) + self.assertIsNotNone(frame0) + self.assertEqual( + frame0.GetFunctionName(), + "unknown_function_1", + "First frame should be unknown_function_1", + ) + self.assertTrue(frame0.IsSynthetic(), "Frame should be marked as synthetic") + self.assertEqual( + frame0.GetPC(), 0x1234000, "First frame should have PC 0x1234000" + ) + + frame1 = thread.GetFrameAtIndex(1) + self.assertIsNotNone(frame1) + self.assertEqual( + frame1.GetFunctionName(), + "unknown_function_2", + "Second frame should be unknown_function_2", + ) + self.assertTrue(frame1.IsSynthetic(), "Frame should be marked as synthetic") + self.assertEqual( + frame1.GetPC(), 0x5678000, "Second frame should have PC 0x5678000" + ) + + # Verify the frames display properly in backtrace. + # The backtrace should show the PC values without crashing or displaying + # invalid addresses like 0xffffffffffffffff. + self.runCmd("bt") + output = self.res.GetOutput() + + # Should show function names. + self.assertIn("unknown_function_1", output) + self.assertIn("unknown_function_2", output) + + # Should show PC addresses in hex format. + self.assertIn("0x0000000001234000", output) + self.assertIn("0x0000000005678000", output) + + # Verify PC and function name are properly separated by space. + self.assertIn("0x0000000001234000 unknown_function_1", output) + self.assertIn("0x0000000005678000 unknown_function_2", output) + + # Should NOT show invalid address. + self.assertNotIn("0xffffffffffffffff", output.lower()) + + # Verify frame 2 is the original real frame 0. + frame2 = thread.GetFrameAtIndex(2) + self.assertIsNotNone(frame2) + self.assertIn("thread_func", frame2.GetFunctionName()) + + def test_chained_frame_providers(self): + """Test that multiple frame providers chain together.""" + 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 test frame providers. + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + # Register 3 providers with different priorities. + # Each provider adds 1 frame at the beginning. + error = lldb.SBError() + + # Provider 1: Priority 10 - adds "foo" frame + provider_id_1 = target.RegisterScriptedFrameProvider( + "test_frame_providers.AddFooFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register foo provider: {error}") + + # Provider 2: Priority 20 - adds "bar" frame + provider_id_2 = target.RegisterScriptedFrameProvider( + "test_frame_providers.AddBarFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register bar provider: {error}") + + # Provider 3: Priority 30 - adds "baz" frame + provider_id_3 = target.RegisterScriptedFrameProvider( + "test_frame_providers.AddBazFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register baz provider: {error}") + + # Verify we have 3 more frames (one from each provider). + new_frame_count = thread.GetNumFrames() + self.assertEqual( + new_frame_count, + original_frame_count + 3, + "Should have original frames + 3 chained frames", + ) + + # Verify the chaining order: baz, bar, foo, then real frames. + # Since priority is lower = higher, the order should be: + # Provider 1 (priority 10) transforms real frames first -> adds "foo" + # Provider 2 (priority 20) transforms Provider 1's output -> adds "bar" + # Provider 3 (priority 30) transforms Provider 2's output -> adds "baz" + # So final stack is: baz, bar, foo, real frames... + + frame0 = thread.GetFrameAtIndex(0) + self.assertIsNotNone(frame0) + self.assertEqual( + frame0.GetFunctionName(), + "baz", + "Frame 0 should be 'baz' from last provider in chain", + ) + self.assertEqual(frame0.GetPC(), 0xBAD) + + frame1 = thread.GetFrameAtIndex(1) + self.assertIsNotNone(frame1) + self.assertEqual( + frame1.GetFunctionName(), + "bar", + "Frame 1 should be 'bar' from second provider in chain", + ) + self.assertEqual(frame1.GetPC(), 0xBAB) + + frame2 = thread.GetFrameAtIndex(2) + self.assertIsNotNone(frame2) + self.assertEqual( + frame2.GetFunctionName(), + "foo", + "Frame 2 should be 'foo' from first provider in chain", + ) + self.assertEqual(frame2.GetPC(), 0xF00) + + # Frame 3 should be 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/test_frame_providers.py b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py index 76f859760bf4f..e97d11f173045 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 @@ -314,3 +314,147 @@ def get_frame_at_index(self, index): # Pass through original frames return index - 3 return None + + +class ValidPCNoModuleFrame(ScriptedFrame): + """Scripted frame with a valid PC but no associated module.""" + + def __init__(self, thread, idx, pc, function_name): + args = lldb.SBStructuredData() + super().__init__(thread, args) + + self.idx = idx + self.pc = pc + self.function_name = function_name + + def get_id(self): + """Return the frame index.""" + return self.idx + + def get_pc(self): + """Return the program counter.""" + return self.pc + + def get_function_name(self): + """Return the function name.""" + return self.function_name + + def is_artificial(self): + """Not artificial.""" + return False + + def is_hidden(self): + """Not hidden.""" + return False + + def get_register_context(self): + """No register context.""" + return None + + +class ValidPCNoModuleFrameProvider(ScriptedFrameProvider): + """ + Provider that demonstrates frames with valid PC but no module. + + This tests that backtrace output handles frames that have a valid + program counter but cannot be resolved to any loaded module. + """ + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Provider that prepends frames with valid PC but no module" + + def get_frame_at_index(self, index): + """Return frames with valid PCs but no module information.""" + if index == 0: + # Frame with valid PC (0x1234000) but no module + return ValidPCNoModuleFrame(self.thread, 0, 0x1234000, "unknown_function_1") + elif index == 1: + # Another frame with valid PC (0x5678000) but no module + return ValidPCNoModuleFrame(self.thread, 1, 0x5678000, "unknown_function_2") + elif index - 2 < len(self.input_frames): + # Pass through original frames + return index - 2 + return None + + +class AddFooFrameProvider(ScriptedFrameProvider): + """Add a single 'foo' frame at the beginning.""" + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Add 'foo' frame at beginning" + + @staticmethod + def get_priority(): + """Return priority 10 (runs first in chain).""" + return 10 + + def get_frame_at_index(self, index): + if index == 0: + # Return synthetic "foo" frame + return CustomScriptedFrame(self.thread, 0, 0xF00, "foo") + elif index - 1 < len(self.input_frames): + # Pass through input frames (shifted by 1) + return index - 1 + return None + + +class AddBarFrameProvider(ScriptedFrameProvider): + """Add a single 'bar' frame at the beginning.""" + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Add 'bar' frame at beginning" + + @staticmethod + def get_priority(): + """Return priority 20 (runs second in chain).""" + return 20 + + def get_frame_at_index(self, index): + if index == 0: + # Return synthetic "bar" frame + return CustomScriptedFrame(self.thread, 0, 0xBAB, "bar") + elif index - 1 < len(self.input_frames): + # Pass through input frames (shifted by 1) + return index - 1 + return None + + +class AddBazFrameProvider(ScriptedFrameProvider): + """Add a single 'baz' frame at the beginning.""" + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Add 'baz' frame at beginning" + + @staticmethod + def get_priority(): + """Return priority 30 (runs last in chain).""" + return 30 + + def get_frame_at_index(self, index): + if index == 0: + # Return synthetic "baz" frame + return CustomScriptedFrame(self.thread, 0, 0xBAD, "baz") + elif index - 1 < len(self.input_frames): + # Pass through input frames (shifted by 1) + return index - 1 + return None