[Lldb-commits] [lldb] [lldb] Broadcast `eBroadcastBitStackChanged` when frame providers change (PR #171482)

Adrian Vogelsgesang via lldb-commits lldb-commits at lists.llvm.org
Thu Feb 5 07:00:34 PST 2026


https://github.com/vogelsgesang updated https://github.com/llvm/llvm-project/pull/171482

>From a6457d62be7bae15ff52081a964e041235ae24d0 Mon Sep 17 00:00:00 2001
From: Adrian Vogelsgesang <avogelsgesang at salesforce.com>
Date: Tue, 9 Dec 2025 17:46:05 +0000
Subject: [PATCH 1/6] [lldb] Broadcast `eBroadcastBitStackChanged` when frame
 providers change

We want to reload the call stack whenever the frame providers were
updated. To do so, we now emit a `eBroadcastBitStackChanged` on all
threads whenever any changes to the frame providers take place.

I found this very useful while iterating on a frame provider using
lldb-dap. So far, the new frame provider only took effect after
continuing execution. Now the back trace in VS-Code gets refreshed
immediately upon running `target frame-provider add`
---
 lldb/include/lldb/Target/Target.h |  5 +++
 lldb/source/Target/Target.cpp     | 59 +++++++++++++++++++------------
 2 files changed, 41 insertions(+), 23 deletions(-)

diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 812a638910b3b..1e616a4a6d7c7 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -776,6 +776,11 @@ class Target : public std::enable_shared_from_this<Target>,
   const llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> &
   GetScriptedFrameProviderDescriptors() const;
 
+protected:
+  /// Notify all threads that the stack traces might have changed.
+  void InvalidateThreadFrameProviders();
+
+public:
   // This part handles the breakpoints.
 
   BreakpointList &GetBreakpointList(bool internal = false);
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index f3e058c6cbc9b..d3d769da01cdb 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -3725,47 +3725,45 @@ llvm::Expected<uint32_t> Target::AddScriptedFrameProviderDescriptor(
   if (!descriptor.IsValid())
     return llvm::createStringError("invalid frame provider descriptor");
 
+  uint32_t descriptor_id = descriptor.GetID();
+
   llvm::StringRef name = descriptor.GetName();
   if (name.empty())
     return llvm::createStringError(
         "frame provider descriptor has no class name");
 
-  std::lock_guard<std::recursive_mutex> guard(
-      m_frame_provider_descriptors_mutex);
-
-  uint32_t descriptor_id = descriptor.GetID();
-  m_frame_provider_descriptors[descriptor_id] = descriptor;
+  {
+    std::unique_lock<std::recursive_mutex> guard(
+        m_frame_provider_descriptors_mutex);
+    m_frame_provider_descriptors[descriptor_id] = descriptor;
+  }
 
-  // Clear frame providers on existing threads so they reload with new config.
-  if (ProcessSP process_sp = GetProcessSP())
-    for (ThreadSP thread_sp : process_sp->Threads())
-      thread_sp->ClearScriptedFrameProvider();
+  InvalidateThreadFrameProviders();
 
   return descriptor_id;
 }
 
 bool Target::RemoveScriptedFrameProviderDescriptor(uint32_t id) {
-  std::lock_guard<std::recursive_mutex> guard(
-      m_frame_provider_descriptors_mutex);
-  bool removed = m_frame_provider_descriptors.erase(id);
+  bool removed;
+  {
+    std::lock_guard<std::recursive_mutex> guard(
+        m_frame_provider_descriptors_mutex);
+    removed = m_frame_provider_descriptors.erase(id);
+  }
 
   if (removed)
-    if (ProcessSP process_sp = GetProcessSP())
-      for (ThreadSP thread_sp : process_sp->Threads())
-        thread_sp->ClearScriptedFrameProvider();
-
+    InvalidateThreadFrameProviders();
   return removed;
 }
 
 void Target::ClearScriptedFrameProviderDescriptors() {
-  std::lock_guard<std::recursive_mutex> guard(
-      m_frame_provider_descriptors_mutex);
-
-  m_frame_provider_descriptors.clear();
+  {
+    std::lock_guard<std::recursive_mutex> guard(
+        m_frame_provider_descriptors_mutex);
+    m_frame_provider_descriptors.clear();
+  }
 
-  if (ProcessSP process_sp = GetProcessSP())
-    for (ThreadSP thread_sp : process_sp->Threads())
-      thread_sp->ClearScriptedFrameProvider();
+  InvalidateThreadFrameProviders();
 }
 
 const llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> &
@@ -3775,6 +3773,21 @@ Target::GetScriptedFrameProviderDescriptors() const {
   return m_frame_provider_descriptors;
 }
 
+void Target::InvalidateThreadFrameProviders() {
+  ProcessSP process_sp = GetProcessSP();
+  if (!process_sp)
+    return;
+  for (ThreadSP thread_sp : process_sp->Threads()) {
+    // Clear frame providers on existing threads so they reload with new config.
+    thread_sp->ClearScriptedFrameProvider();
+    // Notify threads that the stack traces might have changed.
+    if (EventTypeHasListeners(Thread::eBroadcastBitStackChanged)) {
+      auto data_sp = std::make_shared<Thread::ThreadEventData>(thread_sp);
+      thread_sp->BroadcastEvent(Thread::eBroadcastBitStackChanged, data_sp);
+    }
+  }
+}
+
 void Target::FinalizeFileActions(ProcessLaunchInfo &info) {
   Log *log = GetLog(LLDBLog::Process);
 

>From 30b122a82785634a686ca6b46816a4f67c7cff66 Mon Sep 17 00:00:00 2001
From: Adrian Vogelsgesang <avogelsgesang at salesforce.com>
Date: Fri, 30 Jan 2026 01:42:25 +0000
Subject: [PATCH 2/6] Update comment

---
 lldb/include/lldb/Target/Target.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index 1e616a4a6d7c7..b2aea3a6ddc39 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -777,7 +777,8 @@ class Target : public std::enable_shared_from_this<Target>,
   GetScriptedFrameProviderDescriptors() const;
 
 protected:
-  /// Notify all threads that the stack traces might have changed.
+  /// Invalidate all potentially cached frame providers for all threads
+  /// and trigger a stack changed event for all threads.
   void InvalidateThreadFrameProviders();
 
 public:

>From 0de014db2e2031922dee4508b49e6091b507b82d Mon Sep 17 00:00:00 2001
From: Adrian Vogelsgesang <avogelsgesang at salesforce.com>
Date: Fri, 30 Jan 2026 01:53:36 +0000
Subject: [PATCH 3/6] Add test case

---
 .../TestScriptedFrameProvider.py              | 73 +++++++++++++++++++
 1 file changed, 73 insertions(+)

diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
index 964d213b16887..010876788d872 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
@@ -730,3 +730,76 @@ def test_chained_frame_providers(self):
         frame3 = thread.GetFrameAtIndex(3)
         self.assertIsNotNone(frame3)
         self.assertIn("thread_func", frame3.GetFunctionName())
+
+    def test_event_broadcasting(self):
+        """Test that adding/removing frame providers broadcasts eBroadcastBitStackChanged."""
+        self.build()
+        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+            self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False
+        )
+
+        # Create listeners for each thread.
+        listeners = []
+        for i in range(process.GetNumThreads()):
+            t = process.GetThreadAtIndex(i)
+            l = lldb.SBListener(f"listener_{t.GetIndexID()}")
+            t.GetBroadcaster().AddListener(l, lldb.SBThread.eBroadcastBitStackChanged)
+            listeners.append((t, l))
+
+        # Import the test frame provider.
+        script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py")
+        self.runCmd("command script import " + script_path)
+
+        # 1. Test registration.
+        error = lldb.SBError()
+        provider_id = target.RegisterScriptedFrameProvider(
+            "test_frame_providers.ReplaceFrameProvider",
+            lldb.SBStructuredData(),
+            error,
+        )
+        self.assertSuccess(error, f"Failed to register provider: {error}")
+
+        event = lldb.SBEvent()
+        for t, l in listeners:
+            self.assertTrue(
+                l.WaitForEvent(5, event),
+                f"Thread {t.GetIndexID()} should have broadcast eBroadcastBitStackChanged on registration",
+            )
+            self.assertEqual(event.GetType(), lldb.SBThread.eBroadcastBitStackChanged)
+            self.assertEqual(
+                lldb.SBThread.GetThreadFromEvent(event).GetIndexID(), t.GetIndexID()
+            )
+
+        # 2. Test removal.
+        result = target.RemoveScriptedFrameProvider(provider_id)
+        self.assertSuccess(result, f"Failed to remove provider: {result}")
+
+        for t, l in listeners:
+            self.assertTrue(
+                l.WaitForEvent(5, event),
+                f"Thread {t.GetIndexID()} should have broadcast eBroadcastBitStackChanged on removal",
+            )
+            self.assertEqual(event.GetType(), lldb.SBThread.eBroadcastBitStackChanged)
+            self.assertEqual(
+                lldb.SBThread.GetThreadFromEvent(event).GetIndexID(), t.GetIndexID()
+            )
+
+        # 3. Test clear.
+        target.RegisterScriptedFrameProvider(
+            "test_frame_providers.ReplaceFrameProvider",
+            lldb.SBStructuredData(),
+            error,
+        )
+        for t, l in listeners:
+            self.assertTrue(l.WaitForEvent(5, event))  # Consume registration event
+
+        self.runCmd("target frame-provider clear")
+        for t, l in listeners:
+            self.assertTrue(
+                l.WaitForEvent(5, event),
+                f"Thread {t.GetIndexID()} should have broadcast eBroadcastBitStackChanged on clear",
+            )
+            self.assertEqual(event.GetType(), lldb.SBThread.eBroadcastBitStackChanged)
+            self.assertEqual(
+                lldb.SBThread.GetThreadFromEvent(event).GetIndexID(), t.GetIndexID()
+            )

>From ddd3a9443bbcc25d1678a9649245a9b16101176f Mon Sep 17 00:00:00 2001
From: Adrian Vogelsgesang <avogelsgesang at salesforce.com>
Date: Fri, 30 Jan 2026 02:02:13 +0000
Subject: [PATCH 4/6] Initialize variable

---
 lldb/source/Target/Target.cpp                 |  2 +-
 .../TestScriptedFrameProvider.py              | 79 ++++++++++---------
 2 files changed, 41 insertions(+), 40 deletions(-)

diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index d3d769da01cdb..939304a00aed5 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -3744,7 +3744,7 @@ llvm::Expected<uint32_t> Target::AddScriptedFrameProviderDescriptor(
 }
 
 bool Target::RemoveScriptedFrameProviderDescriptor(uint32_t id) {
-  bool removed;
+  bool removed = false;
   {
     std::lock_guard<std::recursive_mutex> guard(
         m_frame_provider_descriptors_mutex);
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
index 010876788d872..5461d0f794ee1 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
@@ -738,13 +738,30 @@ def test_event_broadcasting(self):
             self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False
         )
 
-        # Create listeners for each thread.
-        listeners = []
-        for i in range(process.GetNumThreads()):
-            t = process.GetThreadAtIndex(i)
-            l = lldb.SBListener(f"listener_{t.GetIndexID()}")
-            t.GetBroadcaster().AddListener(l, lldb.SBThread.eBroadcastBitStackChanged)
-            listeners.append((t, l))
+        expected_thread_ids = {
+            process.GetThreadAtIndex(i).GetIndexID()
+            for i in range(process.GetNumThreads())
+        }
+        listener = lldb.SBListener("stack_changed_listener")
+        listener.StartListeningForEventClass(
+            self.dbg,
+            lldb.SBThread.GetBroadcasterClassName(),
+            lldb.SBThread.eBroadcastBitStackChanged,
+        )
+
+        def collect_stack_changed_thread_ids(count, timeout=5):
+            event = lldb.SBEvent()
+            thread_ids = set()
+            for _ in range(count):
+                if not listener.WaitForEvent(timeout, event):
+                    break
+                self.assertEqual(
+                    event.GetType(),
+                    lldb.SBThread.eBroadcastBitStackChanged,
+                    "Event should be stack changed",
+                )
+                thread_ids.add(lldb.SBThread.GetThreadFromEvent(event).GetIndexID())
+            return thread_ids
 
         # Import the test frame provider.
         script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py")
@@ -758,31 +775,20 @@ def test_event_broadcasting(self):
             error,
         )
         self.assertSuccess(error, f"Failed to register provider: {error}")
-
-        event = lldb.SBEvent()
-        for t, l in listeners:
-            self.assertTrue(
-                l.WaitForEvent(5, event),
-                f"Thread {t.GetIndexID()} should have broadcast eBroadcastBitStackChanged on registration",
-            )
-            self.assertEqual(event.GetType(), lldb.SBThread.eBroadcastBitStackChanged)
-            self.assertEqual(
-                lldb.SBThread.GetThreadFromEvent(event).GetIndexID(), t.GetIndexID()
-            )
+        self.assertEqual(
+            collect_stack_changed_thread_ids(len(expected_thread_ids)),
+            expected_thread_ids,
+            "All threads should broadcast eBroadcastBitStackChanged on registration",
+        )
 
         # 2. Test removal.
         result = target.RemoveScriptedFrameProvider(provider_id)
         self.assertSuccess(result, f"Failed to remove provider: {result}")
-
-        for t, l in listeners:
-            self.assertTrue(
-                l.WaitForEvent(5, event),
-                f"Thread {t.GetIndexID()} should have broadcast eBroadcastBitStackChanged on removal",
-            )
-            self.assertEqual(event.GetType(), lldb.SBThread.eBroadcastBitStackChanged)
-            self.assertEqual(
-                lldb.SBThread.GetThreadFromEvent(event).GetIndexID(), t.GetIndexID()
-            )
+        self.assertEqual(
+            collect_stack_changed_thread_ids(len(expected_thread_ids)),
+            expected_thread_ids,
+            "All threads should broadcast eBroadcastBitStackChanged on removal",
+        )
 
         # 3. Test clear.
         target.RegisterScriptedFrameProvider(
@@ -790,16 +796,11 @@ def test_event_broadcasting(self):
             lldb.SBStructuredData(),
             error,
         )
-        for t, l in listeners:
-            self.assertTrue(l.WaitForEvent(5, event))  # Consume registration event
+        collect_stack_changed_thread_ids(len(expected_thread_ids))  # Consume registration
 
         self.runCmd("target frame-provider clear")
-        for t, l in listeners:
-            self.assertTrue(
-                l.WaitForEvent(5, event),
-                f"Thread {t.GetIndexID()} should have broadcast eBroadcastBitStackChanged on clear",
-            )
-            self.assertEqual(event.GetType(), lldb.SBThread.eBroadcastBitStackChanged)
-            self.assertEqual(
-                lldb.SBThread.GetThreadFromEvent(event).GetIndexID(), t.GetIndexID()
-            )
+        self.assertEqual(
+            collect_stack_changed_thread_ids(len(expected_thread_ids)),
+            expected_thread_ids,
+            "All threads should broadcast eBroadcastBitStackChanged on clear",
+        )

>From a5c5c0a913b08d62ce5ee81d03480f3b122a05e3 Mon Sep 17 00:00:00 2001
From: Adrian Vogelsgesang <avogelsgesang at salesforce.com>
Date: Thu, 5 Feb 2026 01:25:09 +0000
Subject: [PATCH 5/6] Fix test

---
 .../TestScriptedFrameProvider.py               | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
index 5461d0f794ee1..da77ee941a2de 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
@@ -734,6 +734,14 @@ def test_chained_frame_providers(self):
     def test_event_broadcasting(self):
         """Test that adding/removing frame providers broadcasts eBroadcastBitStackChanged."""
         self.build()
+
+        listener = lldb.SBListener("stack_changed_listener")
+        listener.StartListeningForEventClass(
+            self.dbg,
+            lldb.SBThread.GetBroadcasterClassName(),
+            lldb.SBThread.eBroadcastBitStackChanged,
+        )
+
         target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
             self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False
         )
@@ -742,18 +750,12 @@ def test_event_broadcasting(self):
             process.GetThreadAtIndex(i).GetIndexID()
             for i in range(process.GetNumThreads())
         }
-        listener = lldb.SBListener("stack_changed_listener")
-        listener.StartListeningForEventClass(
-            self.dbg,
-            lldb.SBThread.GetBroadcasterClassName(),
-            lldb.SBThread.eBroadcastBitStackChanged,
-        )
 
-        def collect_stack_changed_thread_ids(count, timeout=5):
+        def collect_stack_changed_thread_ids(count):
             event = lldb.SBEvent()
             thread_ids = set()
             for _ in range(count):
-                if not listener.WaitForEvent(timeout, event):
+                if not listener.WaitForEvent(5, event):
                     break
                 self.assertEqual(
                     event.GetType(),

>From 7dde47ee515ac8f9ba7a9328450ee3ccc5eaf839 Mon Sep 17 00:00:00 2001
From: Adrian Vogelsgesang <avogelsgesang at salesforce.com>
Date: Thu, 5 Feb 2026 14:59:48 +0000
Subject: [PATCH 6/6] Fix bug

---
 lldb/source/Target/Target.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index 939304a00aed5..1ff115e980cf9 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -3781,7 +3781,7 @@ void Target::InvalidateThreadFrameProviders() {
     // Clear frame providers on existing threads so they reload with new config.
     thread_sp->ClearScriptedFrameProvider();
     // Notify threads that the stack traces might have changed.
-    if (EventTypeHasListeners(Thread::eBroadcastBitStackChanged)) {
+    if (thread_sp->EventTypeHasListeners(Thread::eBroadcastBitStackChanged)) {
       auto data_sp = std::make_shared<Thread::ThreadEventData>(thread_sp);
       thread_sp->BroadcastEvent(Thread::eBroadcastBitStackChanged, data_sp);
     }



More information about the lldb-commits mailing list