[Lldb-commits] [lldb] 943782b - [lldb] Broadcast `eBroadcastBitStackChanged` when frame providers change (#171482)
via lldb-commits
lldb-commits at lists.llvm.org
Thu Feb 5 12:00:28 PST 2026
Author: Adrian Vogelsgesang
Date: 2026-02-05T21:00:23+01:00
New Revision: 943782be5aec5db854065145dd73a618c3a775be
URL: https://github.com/llvm/llvm-project/commit/943782be5aec5db854065145dd73a618c3a775be
DIFF: https://github.com/llvm/llvm-project/commit/943782be5aec5db854065145dd73a618c3a775be.diff
LOG: [lldb] Broadcast `eBroadcastBitStackChanged` when frame providers change (#171482)
We want to reload the call stack whenever the frame providers are
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 in
lldb-dap. So far, the new frame provider only took effect after
continuing execution. Now the backtrace in VS-Code gets refreshed
immediately upon running `target frame-provider add`.
Added:
Modified:
lldb/include/lldb/Target/Target.h
lldb/source/Target/Target.cpp
lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
Removed:
################################################################################
diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h
index f781c4dabdd9f..4f5b022765f9e 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -800,6 +800,12 @@ class Target : public std::enable_shared_from_this<Target>,
const llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> &
GetScriptedFrameProviderDescriptors() const;
+protected:
+ /// Invalidate all potentially cached frame providers for all threads
+ /// and trigger a stack changed event for all threads.
+ 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 d9e72cd5453e4..787e66bfaf7a8 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -3726,47 +3726,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 = false;
+ {
+ 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> &
@@ -3776,6 +3774,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 (thread_sp->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);
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
index 8397d60eedbb5..8c2d8f0d5ad52 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
@@ -1110,3 +1110,80 @@ def test_provider_lifecycle_with_frame_validity(self):
_ = saved_frames[1].IsValid()
except Exception as e:
self.fail(f"Accessing invalidated frames should not crash: {e}")
+
+ 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
+ )
+
+ expected_thread_ids = {
+ process.GetThreadAtIndex(i).GetIndexID()
+ for i in range(process.GetNumThreads())
+ }
+
+ def collect_stack_changed_thread_ids(count):
+ event = lldb.SBEvent()
+ thread_ids = set()
+ for _ in range(count):
+ if not listener.WaitForEvent(5, 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")
+ 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}")
+ 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}")
+ 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(
+ "test_frame_providers.ReplaceFrameProvider",
+ lldb.SBStructuredData(),
+ error,
+ )
+ # Consume registration
+ collect_stack_changed_thread_ids(len(expected_thread_ids))
+
+ self.runCmd("target frame-provider clear")
+ self.assertEqual(
+ collect_stack_changed_thread_ids(len(expected_thread_ids)),
+ expected_thread_ids,
+ "All threads should broadcast eBroadcastBitStackChanged on clear",
+ )
\ No newline at end of file
More information about the lldb-commits
mailing list