[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