[Lldb-commits] [lldb] b63ea1c - [lldb][Windows] Lock the thread list when lldb-server accesses it (#226974)

via lldb-commits lldb-commits at lists.llvm.org
Wed Sep 30 04:08:35 PDT 2026


Author: Charles Zablit
Date: 2026-09-30T12:08:28+01:00
New Revision: b63ea1c16751bf205d43ecb4f9a2c010c0a4daa6

URL: https://github.com/llvm/llvm-project/commit/b63ea1c16751bf205d43ecb4f9a2c010c0a4daa6
DIFF: https://github.com/llvm/llvm-project/commit/b63ea1c16751bf205d43ecb4f9a2c010c0a4daa6.diff

LOG: [lldb][Windows] Lock the thread list when lldb-server accesses it (#226974)

`NativeProcessProtocol::Threads()` returns a `LockingAdaptedIterable`
that holds `m_threads_mutex` for the lifetime of the iteration, so
callers may hold references into `m_threads` across the loop body. On
Windows, this is only done when removing threads:

```
  // OnCreateThread
  m_threads.push_back(std::move(thread));
...
  // OnExitThread
  std::lock_guard<std::recursive_mutex> guard(m_threads_mutex);
  llvm::erase_if(m_threads, ...);
```

`m_threads` is a `std::vector<std::unique_ptr<NativeThreadProtocol>>`.
When the debug-event thread appends and the vector reallocates, every
reference held by a concurrent iteration on the main loop dangles. This
takes lldb-server down with an access violation:

```
Exception Code: 0xC0000005
 #0 NativeThreadProtocol::GetID   NativeThreadProtocol.h:40
 #1 GetJSONThreadsInfo            GDBRemoteCommunicationServerLLGS.cpp:819
 #4 Handle_jThreadsInfo
 #8 GetPacketAndSendResponse
 #9 DataAvailableCallback         :1271
```

---

This patch takes `m_threads_mutex` everywhere `NativeProcessWindows`
touches `m_threads`: when adding threads in `OnDebuggerConnected` and
`OnCreateThread`, and when reading them in `Resume`, `StopThread`,
`UpdateThreads`, the halt path of `HandleBreakpointException`, and
`OnLoadDll`/`OnUnloadDll`.

Added: 
    

Modified: 
    lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index 849931eaec372..c94421036d37a 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -108,6 +108,7 @@ Status NativeProcessWindows::Resume(const ResumeActionList &resume_actions) {
   if (state == eStateStopped || state == eStateCrashed) {
     LLDB_LOG(log, "process {0} is in state {1}.  Resuming...",
              GetDebuggedProcessId(), state);
+    std::lock_guard<std::recursive_mutex> guard(m_threads_mutex);
     LLDB_LOG(log, "resuming {0} threads.", m_threads.size());
 
     m_pending_library_events = false;
@@ -301,6 +302,7 @@ void NativeProcessWindows::StopThread(lldb::tid_t thread_id,
     return;
 
   Log *log = GetLog(WindowsLog::Thread);
+  std::lock_guard<std::recursive_mutex> guard(m_threads_mutex);
   for (uint32_t i = 0; i < m_threads.size(); ++i) {
     auto t = static_cast<NativeThreadWindows *>(m_threads[i].get());
     if (Status error = t->DoStop(); error.Fail())
@@ -309,7 +311,10 @@ void NativeProcessWindows::StopThread(lldb::tid_t thread_id,
   SetStopReasonForThread(*thread, reason, description);
 }
 
-size_t NativeProcessWindows::UpdateThreads() { return m_threads.size(); }
+size_t NativeProcessWindows::UpdateThreads() {
+  std::lock_guard<std::recursive_mutex> guard(m_threads_mutex);
+  return m_threads.size();
+}
 
 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
 NativeProcessWindows::GetAuxvData() const {
@@ -558,6 +563,7 @@ void NativeProcessWindows::OnDebuggerConnected(lldb::addr_t image_base) {
   }
 
   // The very first one shall always be the main thread.
+  std::lock_guard<std::recursive_mutex> guard(m_threads_mutex);
   assert(m_threads.empty());
   m_threads.push_back(std::make_unique<NativeThreadWindows>(
       *this, m_session_data->m_debugger->GetMainThread()));
@@ -675,12 +681,15 @@ NativeProcessWindows::HandleBreakpointException(const ExceptionRecord &record) {
     signal_info.signo = 19; // SIGSTOP on POSIX
 
     // Halt all threads at the kernel level.
-    for (uint32_t i = 0; i < m_threads.size(); ++i) {
-      auto t = static_cast<NativeThreadWindows *>(m_threads[i].get());
-      if (Status err = t->DoStop(); err.Fail()) {
-        LLDB_LOG(log, "Failed to stop thread {1:x}: {0}", t->GetID(),
-                 err.GetError());
-        exit(1);
+    {
+      std::lock_guard<std::recursive_mutex> guard(m_threads_mutex);
+      for (uint32_t i = 0; i < m_threads.size(); ++i) {
+        auto t = static_cast<NativeThreadWindows *>(m_threads[i].get());
+        if (Status err = t->DoStop(); err.Fail()) {
+          LLDB_LOG(log, "Failed to stop thread {1:x}: {0}", t->GetID(),
+                   err.GetError());
+          exit(1);
+        }
       }
     }
     SetCurrentThreadID(thread_id);
@@ -783,6 +792,7 @@ void NativeProcessWindows::OnCreateThread(const HostThread &new_thread) {
     thread->SetStopReason(stop_info, "");
   }
 
+  std::lock_guard<std::recursive_mutex> guard(m_threads_mutex);
   m_threads.push_back(std::move(thread));
 }
 
@@ -814,21 +824,25 @@ DllEventAction NativeProcessWindows::OnLoadDll(const ModuleSpec &module_spec,
   if (!resolved || ProcessDebugger::IsSystemDLL(resolved.GetPath()))
     return DllEventAction::ContinueDebugLoop;
 
-  NativeThreadWindows *loader_thread = GetThreadByID(thread_id);
-  if (!loader_thread && !m_threads.empty()) {
-    LLDB_LOG(log, "LOAD_DLL on unknown tid {0:x}. Falling back to main thread.",
-             thread_id);
-    loader_thread = static_cast<NativeThreadWindows *>(m_threads[0].get());
-  }
-  if (loader_thread) {
-    SetCurrentThreadID(loader_thread->GetID());
-    if (loader_thread->DoStop().Fail())
-      LLDB_LOG(log, "Failed to suspend thread {0} on LOAD_DLL.",
-               loader_thread->GetID());
-    ThreadStopInfo info;
-    info.reason = lldb::eStopReasonNone;
-    info.signo = 0;
-    loader_thread->SetStopReason(info, "");
+  {
+    std::lock_guard<std::recursive_mutex> guard(m_threads_mutex);
+    NativeThreadWindows *loader_thread = GetThreadByID(thread_id);
+    if (!loader_thread && !m_threads.empty()) {
+      LLDB_LOG(log,
+               "LOAD_DLL on unknown tid {0:x}. Falling back to main thread.",
+               thread_id);
+      loader_thread = static_cast<NativeThreadWindows *>(m_threads[0].get());
+    }
+    if (loader_thread) {
+      SetCurrentThreadID(loader_thread->GetID());
+      if (loader_thread->DoStop().Fail())
+        LLDB_LOG(log, "Failed to suspend thread {0} on LOAD_DLL.",
+                 loader_thread->GetID());
+      ThreadStopInfo info;
+      info.reason = lldb::eStopReasonNone;
+      info.signo = 0;
+      loader_thread->SetStopReason(info, "");
+    }
   }
   SetState(eStateStopped, true);
 
@@ -849,22 +863,25 @@ DllEventAction NativeProcessWindows::OnUnloadDll(lldb::addr_t module_addr,
   if (!unloaded_spec || ProcessDebugger::IsSystemDLL(unloaded_spec.GetPath()))
     return DllEventAction::ContinueDebugLoop;
 
-  NativeThreadWindows *unloader_thread = GetThreadByID(thread_id);
-  if (!unloader_thread && !m_threads.empty()) {
-    LLDB_LOG(log,
-             "UNLOAD_DLL on unknown tid {0:x}. Falling back to main thread.",
-             thread_id);
-    unloader_thread = static_cast<NativeThreadWindows *>(m_threads[0].get());
-  }
-  if (unloader_thread) {
-    SetCurrentThreadID(unloader_thread->GetID());
-    if (unloader_thread->DoStop().Fail())
-      LLDB_LOG(log, "Failed to suspend thread {0} on UNLOAD_DLL.",
-               unloader_thread->GetID());
-    ThreadStopInfo info;
-    info.reason = lldb::eStopReasonNone;
-    info.signo = 0;
-    unloader_thread->SetStopReason(info, "");
+  {
+    std::lock_guard<std::recursive_mutex> guard(m_threads_mutex);
+    NativeThreadWindows *unloader_thread = GetThreadByID(thread_id);
+    if (!unloader_thread && !m_threads.empty()) {
+      LLDB_LOG(log,
+               "UNLOAD_DLL on unknown tid {0:x}. Falling back to main thread.",
+               thread_id);
+      unloader_thread = static_cast<NativeThreadWindows *>(m_threads[0].get());
+    }
+    if (unloader_thread) {
+      SetCurrentThreadID(unloader_thread->GetID());
+      if (unloader_thread->DoStop().Fail())
+        LLDB_LOG(log, "Failed to suspend thread {0} on UNLOAD_DLL.",
+                 unloader_thread->GetID());
+      ThreadStopInfo info;
+      info.reason = lldb::eStopReasonNone;
+      info.signo = 0;
+      unloader_thread->SetStopReason(info, "");
+    }
   }
   SetState(eStateStopped, true);
   return DllEventAction::ParkDebugLoop;


        


More information about the lldb-commits mailing list