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

Reply via email to