[Lldb-commits] [lldb] [lldb][Windows] Synchronize on LOAD_DLL_DEBUG_EVENT in lldb-server (PR #203334)

Charles Zablit via lldb-commits lldb-commits at lists.llvm.org
Fri Jun 12 08:00:11 PDT 2026


https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/203334

>From 012f0712d1c750ed961ccc8e7fe09fa149e99d13 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Fri, 12 Jun 2026 14:58:27 +0100
Subject: [PATCH 1/2] [lldb][Windows] Synchronize on LOAD_DLL_DEBUG_EVENT in
 lldb-server

---
 .../Process/Windows/Common/DebuggerThread.cpp | 27 ++++++++
 .../Process/Windows/Common/DebuggerThread.h   | 18 ++++++
 .../Windows/Common/NativeProcessWindows.cpp   | 61 ++++++++++++++++++-
 .../Windows/Common/NativeProcessWindows.h     | 20 +++++-
 .../GDBRemoteCommunicationServerLLGS.cpp      |  5 ++
 5 files changed, 128 insertions(+), 3 deletions(-)

diff --git a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
index ce679d905a734..548107b67b7d6 100644
--- a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
@@ -208,6 +208,8 @@ Status DebuggerThread::StopDebugging(bool terminate) {
     ContinueAsyncException(ExceptionResult::MaskException);
   }
 
+  ContinueAsyncDllEvent();
+
   if (!terminate) {
     // Indicate that we want to detach.
     m_pid_to_detach = GetProcess().GetProcessId();
@@ -249,6 +251,31 @@ void DebuggerThread::ContinueAsyncException(ExceptionResult result) {
   m_exception_pred.SetValue(result, eBroadcastAlways);
 }
 
+void DebuggerThread::ContinueAsyncDllEvent() {
+  Log *log = GetLog(WindowsLog::Process | WindowsLog::Event);
+  LLDB_LOG(log, "releasing parked DLL event for inferior process {0}.",
+           m_process.GetProcessId());
+
+  m_dll_event_pred.SetValue(true, eBroadcastAlways);
+}
+
+void DebuggerThread::WaitForResumeAfterDllEvent() {
+  Log *log = GetLog(WindowsLog::Process | WindowsLog::Event);
+
+  m_dll_event_pred.SetValue(false, eBroadcastNever);
+  m_pending_dll_event.store(true);
+  if (m_is_shutting_down.load()) {
+    m_pending_dll_event.store(false);
+    return;
+  }
+  LLDB_LOG(log, "parking inferior {0} on DLL event until next resume",
+           m_process.GetProcessId());
+  m_dll_event_pred.WaitForValueEqualTo(true);
+  m_pending_dll_event.store(false);
+  LLDB_LOG(log, "inferior {0} released from DLL event wait",
+           m_process.GetProcessId());
+}
+
 void DebuggerThread::FreeProcessHandles() {
   m_process = HostProcess();
   m_main_thread = HostThread();
diff --git a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.h b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.h
index e0a6e9207bcd7..677d594324c27 100644
--- a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.h
+++ b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.h
@@ -42,6 +42,18 @@ class DebuggerThread : public std::enable_shared_from_this<DebuggerThread> {
 
   void ContinueAsyncException(ExceptionResult result);
 
+  /// Whether HandleLoadDllEvent / HandleUnloadDllEvent is currently parked
+  /// waiting for ContinueAsyncDllEvent.
+  bool HasPendingDllEvent() const { return m_pending_dll_event; }
+
+  /// Block the current debugger thread until ContinueAsyncDllEvent is
+  /// invoked.
+  void WaitForResumeAfterDllEvent();
+
+  /// Release a HandleLoadDllEvent / HandleUnloadDllEvent that is waiting on
+  /// the DLL-event predicate.
+  void ContinueAsyncDllEvent();
+
 private:
   void FreeProcessHandles();
   void DebugLoop();
@@ -76,6 +88,12 @@ class DebuggerThread : public std::enable_shared_from_this<DebuggerThread> {
   // and the debug loop can be continued.
   Predicate<ExceptionResult> m_exception_pred;
 
+  // Predicate which gets signalled when ContinueAsyncDllEvent is called.
+  Predicate<bool> m_dll_event_pred;
+
+  // True while a HandleLoadDllEvent / HandleUnloadDllEvent is parked.
+  std::atomic<bool> m_pending_dll_event{false};
+
   // An event which gets signalled by the debugger thread when it exits the
   // debugger loop and is detached from the inferior.
   HANDLE m_debugging_ended_event = nullptr;
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index 80ecb321209ed..d022198c6c984 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -155,6 +155,8 @@ Status NativeProcessWindows::Resume(const ResumeActionList &resume_actions) {
       // anything happened.
       m_session_data->m_debugger->ContinueAsyncException(
           ExceptionResult::MaskException);
+    } else if (m_session_data->m_debugger->HasPendingDllEvent()) {
+      m_session_data->m_debugger->ContinueAsyncDllEvent();
     }
   } else {
     LLDB_LOG(log, "error: process {0} is in state {1}.  Returning...",
@@ -478,6 +480,15 @@ void NativeProcessWindows::OnDebuggerConnected(lldb::addr_t image_base) {
     SetArchitecture(process_info.GetArchitecture());
   }
 
+  ProcessInstanceInfo info;
+  if (Host::GetProcessInfo(GetDebuggedProcessId(), info)) {
+    FileSpec exe = info.GetExecutableFile();
+    if (exe) {
+      FileSystem::Instance().Resolve(exe);
+      m_loaded_modules[exe] = image_base;
+    }
+  }
+
   // The very first one shall always be the main thread.
   assert(m_threads.empty());
   m_threads.push_back(std::make_unique<NativeThreadWindows>(
@@ -701,13 +712,59 @@ void NativeProcessWindows::OnExitThread(lldb::tid_t thread_id,
 
 void NativeProcessWindows::OnLoadDll(const ModuleSpec &module_spec,
                                      lldb::addr_t module_addr) {
-  m_loaded_modules.clear();
+  Log *log = GetLog(WindowsLog::Process);
+
+  if (module_spec.GetFileSpec()) {
+    FileSpec resolved = module_spec.GetFileSpec();
+    FileSystem::Instance().Resolve(resolved);
+    m_loaded_modules[resolved] = module_addr;
+  }
   m_pending_library_events = true;
+
+  if (!m_initial_stop_seen || !m_client_supports_libraries_read)
+    return;
+
+  if (!m_threads.empty()) {
+    auto first = static_cast<NativeThreadWindows *>(m_threads[0].get());
+    SetCurrentThreadID(first->GetID());
+    if (first->DoStop().Fail())
+      LLDB_LOG(log, "failed to suspend thread {0} on LOAD_DLL", first->GetID());
+    ThreadStopInfo info;
+    info.reason = lldb::eStopReasonNone;
+    info.signo = 0;
+    first->SetStopReason(info, "");
+  }
+  SetState(eStateStopped, true);
+
+  m_session_data->m_debugger->WaitForResumeAfterDllEvent();
 }
 
 void NativeProcessWindows::OnUnloadDll(lldb::addr_t module_addr) {
-  m_loaded_modules.clear();
+  Log *log = GetLog(WindowsLog::Process);
+  for (auto it = m_loaded_modules.begin(); it != m_loaded_modules.end();) {
+    if (it->second == module_addr)
+      it = m_loaded_modules.erase(it);
+    else
+      ++it;
+  }
   m_pending_library_events = true;
+
+  if (!m_initial_stop_seen || m_client_supports_libraries_read)
+    return;
+
+  if (!m_threads.empty()) {
+    auto first = static_cast<NativeThreadWindows *>(m_threads[0].get());
+    SetCurrentThreadID(first->GetID());
+    if (first->DoStop().Fail())
+      LLDB_LOG(log, "failed to suspend thread {0} on UNLOAD_DLL",
+               first->GetID());
+    ThreadStopInfo info;
+    info.reason = lldb::eStopReasonNone;
+    info.signo = 0;
+    first->SetStopReason(info, "");
+  }
+  SetState(eStateStopped, true);
+  m_session_data->m_debugger->WaitForResumeAfterDllEvent();
 }
 
 llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
index d1c57fc01e06b..d9efe1c23c2b7 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
@@ -107,6 +107,20 @@ class NativeProcessWindows : public NativeProcessProtocol,
 
   bool HasPendingLibraryEvents() override;
 
+  /// Records whether the gdb-remote client advertised
+  /// `qXfer:libraries:read+` (or `qXfer:libraries-svr4:read+`) in its
+  /// qSupported reply.
+  void SetClientSupportsLibrariesRead(bool v) {
+    m_client_supports_libraries_read = v;
+  }
+
+  /// Forwards to NativeProcessProtocol's bookkeeping.
+  void SetEnabledExtensions(Extension flags) override {
+    NativeProcessProtocol::SetEnabledExtensions(flags);
+    SetClientSupportsLibrariesRead(
+        bool(flags & (Extension::libraries | Extension::libraries_svr4)));
+  }
+
   /// Forward bytes from the gdb-remote `I` packet into the inferior's
   /// ConPTY-backed stdin via `m_stdio_communication.Write` →
   /// `ConnectionConPTY::Write` → `WriteFile` on the parent-side STDIN
@@ -161,7 +175,7 @@ class NativeProcessWindows : public NativeProcessProtocol,
 
   /// Set whenever an OS DLL load/unload event has been seen since the last stop
   /// reply.
-  bool m_pending_library_events = true;
+  bool m_pending_library_events = false;
 
   /// Whether we've seen the loader breakpoint that fires once per process at
   /// launch / attach.
@@ -170,6 +184,10 @@ class NativeProcessWindows : public NativeProcessProtocol,
   /// Set when Halt() / Interrupt() schedules a DebugBreakProcess injection.
   bool m_pending_halt = false;
 
+  /// Mirrors the client-side qXfer:libraries[-svr4]:read+ qSupported feature
+  /// reported by GDBRemoteCommunicationServerLLGS::HandleFeatures.
+  bool m_client_supports_libraries_read = false;
+
   /// PseudoConsole for the lldb-server stdio-forwarding path.
   std::shared_ptr<PseudoConsole> m_pty;
 
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
index 0f14231cadbd5..9596eaf31c4fc 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -4496,6 +4496,11 @@ std::vector<std::string> GDBRemoteCommunicationServerLLGS::HandleFeatures(
             .Case("vfork-events+", Extension::vfork)
             .Default({});
 
+  if (bool(plugin_features & Extension::libraries))
+    m_extensions_supported |= Extension::libraries;
+  if (bool(plugin_features & Extension::libraries_svr4))
+    m_extensions_supported |= Extension::libraries_svr4;
+
   // We consume lldb's swbreak/hwbreak feature, but it doesn't change the
   // behaviour of lldb-server. We always adjust the program counter for targets
   // like x86

>From b1f6af3b2db34de7c57b483a7287e02545273f46 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Fri, 12 Jun 2026 15:59:59 +0100
Subject: [PATCH 2/2] fixup! [lldb][Windows] Synchronize on
 LOAD_DLL_DEBUG_EVENT in lldb-server

---
 .../Process/Windows/Common/NativeProcessWindows.cpp      | 9 +++------
 .../Process/Windows/Common/NativeProcessWindows.h        | 3 ---
 2 files changed, 3 insertions(+), 9 deletions(-)

diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index d022198c6c984..5171d528c062e 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -741,12 +741,9 @@ void NativeProcessWindows::OnLoadDll(const ModuleSpec &module_spec,
 
 void NativeProcessWindows::OnUnloadDll(lldb::addr_t module_addr) {
   Log *log = GetLog(WindowsLog::Process);
-  for (auto it = m_loaded_modules.begin(); it != m_loaded_modules.end();) {
-    if (it->second == module_addr)
-      it = m_loaded_modules.erase(it);
-    else
-      ++it;
-  }
+  llvm::erase_if(m_loaded_modules, [module_addr](const auto &entry) {
+    return entry.second == module_addr;
+  });
   m_pending_library_events = true;
 
   if (!m_initial_stop_seen || m_client_supports_libraries_read)
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
index d9efe1c23c2b7..fefefbd6f1060 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
@@ -107,9 +107,6 @@ class NativeProcessWindows : public NativeProcessProtocol,
 
   bool HasPendingLibraryEvents() override;
 
-  /// Records whether the gdb-remote client advertised
-  /// `qXfer:libraries:read+` (or `qXfer:libraries-svr4:read+`) in its
-  /// qSupported reply.
   void SetClientSupportsLibrariesRead(bool v) {
     m_client_supports_libraries_read = v;
   }



More information about the lldb-commits mailing list