[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