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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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); } _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
