Author: Adrian Vogelsgesang Date: 2026-02-09T09:52:24Z New Revision: bc604c7ed648834b430c144c02f5cb87a0b4711f
URL: https://github.com/llvm/llvm-project/commit/bc604c7ed648834b430c144c02f5cb87a0b4711f DIFF: https://github.com/llvm/llvm-project/commit/bc604c7ed648834b430c144c02f5cb87a0b4711f.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`. (cherry picked from commit 943782be5aec5db854065145dd73a618c3a775be) 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 812a638910b3b..b2aea3a6ddc39 100644 --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -776,6 +776,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 f3e058c6cbc9b..1ff115e980cf9 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 = 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> & @@ -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 (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 _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
