https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/172849
>From 53791c7a2c24c8402ae69f4a2611ddf88843f387 Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani <[email protected]> Date: Thu, 18 Dec 2025 14:11:57 +0100 Subject: [PATCH] [lldb] Enable chaining multiple scripted frame providers per thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch allows threads to have multiple SyntheticFrameProviderSP instances that chain together sequentially. Each provider receives the output of the previous provider as input, creating a transformation pipeline. It changes `Thread::m_frame_provider_sp` to a vector, adds provider parameter to SyntheticStackFrameList to avoid calling back into `Thread::GetFrameProvider()` during frame fetching, updated `LoadScriptedFrameProvider()` to chain providers by wrapping each previous provider's output in a `SyntheticStackFrameList` for the next provider and finally, loads ALL matching providers in priority order instead of just the first one. The chaining works as follows: ``` Real Unwinder Frames ↓ Provider 1 (priority 10) → adds/transforms frames ↓ Provider 2 (priority 20) → transforms Provider 1's output ↓ Provider 3 (priority 30) → transforms Provider 2's output ↓ Final frame list shown to user ``` This patch also adds a test for this (test_chained_frame_providers) to verify that 3 providers chain correctly: `AddFooFrameProvider`, `AddBarFrameProvider`, `AddBazFrameProvider`. Signed-off-by: Med Ismail Bennani <[email protected]> --- lldb/include/lldb/Target/StackFrameList.h | 6 +- lldb/include/lldb/Target/Thread.h | 8 +- lldb/source/Target/StackFrameList.cpp | 13 +-- lldb/source/Target/Thread.cpp | 58 +++++++----- .../TestScriptedFrameProvider.py | 92 +++++++++++++++++++ .../test_frame_providers.py | 78 ++++++++++++++++ 6 files changed, 220 insertions(+), 35 deletions(-) diff --git a/lldb/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h index 539c070ff0f4b..42a0be9b196b9 100644 --- a/lldb/include/lldb/Target/StackFrameList.h +++ b/lldb/include/lldb/Target/StackFrameList.h @@ -243,7 +243,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); protected: /// Override FetchFramesUpTo to lazily return frames from the provider @@ -255,6 +256,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/Thread.h b/lldb/include/lldb/Target/Thread.h index 46ce192556756..808bb024d4d64 100644 --- a/lldb/include/lldb/Target/Thread.h +++ b/lldb/include/lldb/Target/Thread.h @@ -1302,8 +1302,8 @@ class Thread : public std::enable_shared_from_this<Thread>, void ClearScriptedFrameProvider(); - lldb::SyntheticFrameProviderSP GetFrameProvider() const { - return m_frame_provider_sp; + const std::vector<lldb::SyntheticFrameProviderSP> &GetFrameProviders() const { + return m_frame_providers; } protected: @@ -1409,8 +1409,8 @@ class Thread : public std::enable_shared_from_this<Thread>, /// 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. + std::vector<lldb::SyntheticFrameProviderSP> m_frame_providers; private: bool m_extended_info_fetched; // Have we tried to retrieve the m_extended_info diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index 896a760f61d26..b1af2bb65494a 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) : 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)) { +} 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/Thread.cpp b/lldb/source/Target/Thread.cpp index b833918c27818..9fdd6724843e4 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -262,7 +262,7 @@ void Thread::DestroyThread() { std::lock_guard<std::recursive_mutex> 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(); } @@ -1448,8 +1448,8 @@ 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(); @@ -1474,24 +1474,24 @@ StackFrameListSP Thread::GetStackFrameList() { return priority_a < priority_b; }); - // Load the highest priority provider that successfully instantiates. + // 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. } - break; // Successfully loaded provider. } } } - // 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<SyntheticStackFrameList>( - *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 = @@ -1505,29 +1505,39 @@ llvm::Error Thread::LoadScriptedFrameProvider( const ScriptedFrameProviderDescriptor &descriptor) { std::lock_guard<std::recursive_mutex> 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<StackFrameList>(*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 input_frames; + if (m_frame_providers.empty()) { + // First provider gets real unwinder frames + input_frames = + std::make_shared<StackFrameList>(*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_input = prev_provider->GetInputFrames(); + input_frames = std::make_shared<SyntheticStackFrameList>( + *this, prev_input, m_prev_frames_sp, true, prev_provider); + } auto provider_or_err = - SyntheticFrameProvider::CreateInstance(temp_frames, descriptor); + SyntheticFrameProvider::CreateInstance(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<std::recursive_mutex> guard(m_frame_mutex); - m_frame_provider_sp.reset(); + m_frame_providers.clear(); m_curr_frames_sp.reset(); m_prev_frames_sp.reset(); } @@ -1552,7 +1562,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 0a5b9d9b83951..ceca64a450686 100644 --- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py +++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py @@ -638,3 +638,95 @@ def test_valid_pc_no_module_frames(self): 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 e4367192af50d..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 @@ -380,3 +380,81 @@ def get_frame_at_index(self, index): # 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 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
