[Lldb-commits] [lldb] [lldb][windows] Plumb Windows DLL load/unload through lldb-server (PR #197901)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Fri May 15 03:31:14 PDT 2026
https://github.com/charles-zablit created https://github.com/llvm/llvm-project/pull/197901
`lldb-server.exe` currently never sends DLLs the inferior loads to the client. `NativeProcessWindows::OnLoadDll` just invalidates a cache and the client's module list stays empty. Tests like `lang/c/shared_lib/TestSharedLib` that call a function defined in a loaded DLL fail with `error: Couldn't look up symbols`.
# Protocol additions on `NativeProcessProtocol`
* New`Extension::libraries` flag, separate from the existing SVR4-only `Extension::libraries_svr4`.
* `LoadedLibraryInfo { name, base_addr }` struct, mirroring `SVR4LibraryInfo` but without any of the SVR4-specific fields (link_map, ld_addr, next) that don't apply to PE.
* `GetLoadedLibraries()` virtual that returns the current
libraries.
* `HasPendingLibraryEvents()` virtual, used by the stop-reply builder to decide whether to include `library:1;` in the packet.
# End-to-end expectation
1. Client does `continue`.
2. Windows inferior loads a DLL.
3. lldb-server's debug loop takes the `LOAD_DLL_DEBUG_EVENT`, `NativeProcessWindows::OnLoadDll` sets the pending flag and clears the module cache.
4. process eventually stops (breakpoint, signal,
etc.).
5. stop reply includes `library:1;`.
6. client parses it and calls `ProcessGDBRemote::LoadModules`.
7. that issues `qXfer:libraries:read`.
8. server serves the current snapshot.
9. client feeds the result into `DynamicLoaderWindowsDYLD` and the module list is up to date.
## Requirements
This requires libxml2.
>From c4df8e0c32f0469d0a8ced0f8f171c51acd8ab08 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Fri, 15 May 2026 12:25:25 +0200
Subject: [PATCH] [lldb][windows] Plumb Windows DLL load/unload through
lldb-server
---
.../lldb/Host/common/NativeProcessProtocol.h | 21 ++++++++++++-
.../Windows/Common/NativeProcessWindows.cpp | 31 ++++++++++++++++---
.../Windows/Common/NativeProcessWindows.h | 12 +++++++
.../GDBRemoteCommunicationServerLLGS.cpp | 23 ++++++++++++++
4 files changed, 81 insertions(+), 6 deletions(-)
diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h
index b9c0120016a2d..3b133bd218316 100644
--- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h
+++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h
@@ -45,6 +45,13 @@ struct SVR4LibraryInfo {
lldb::addr_t next;
};
+/// Generic loaded-library entry used by the non-SVR4 `qXfer:libraries:read`
+/// form of the GDB remote library-list protocol (PE on Windows).
+struct LoadedLibraryInfo {
+ std::string name;
+ lldb::addr_t base_addr;
+};
+
// NativeProcessProtocol
class NativeProcessProtocol {
public:
@@ -146,6 +153,17 @@ class NativeProcessProtocol {
"Not implemented");
}
+ /// Return the currently loaded libraries of the target in the
+ /// `qXfer:libraries:read` form (generic name + base address pairs; used on
+ /// Windows, where the inferior is not SVR4 and the module list comes from
+ /// the PE loader).
+ virtual llvm::Expected<std::vector<LoadedLibraryInfo>> GetLoadedLibraries() {
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "Not implemented");
+ }
+
+ virtual bool HasPendingLibraryEvents() { return false; }
+
virtual bool IsAlive() const;
virtual size_t UpdateThreads() = 0;
@@ -268,8 +286,9 @@ class NativeProcessProtocol {
memory_tagging = (1u << 6),
savecore = (1u << 7),
siginfo_read = (1u << 8),
+ libraries = (1u << 9),
- LLVM_MARK_AS_BITMASK_ENUM(siginfo_read)
+ LLVM_MARK_AS_BITMASK_ENUM(libraries)
};
class Manager {
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index 4fdb286c93e1e..b6985116f9ed6 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -96,6 +96,8 @@ Status NativeProcessWindows::Resume(const ResumeActionList &resume_actions) {
GetDebuggedProcessId(), state);
LLDB_LOG(log, "resuming {0} threads.", m_threads.size());
+ m_pending_library_events = false;
+
bool failed = false;
for (uint32_t i = 0; i < m_threads.size(); ++i) {
auto thread = static_cast<NativeThreadWindows *>(m_threads[i].get());
@@ -408,6 +410,26 @@ NativeProcessWindows::GetFileLoadAddress(const llvm::StringRef &file_name,
file_spec.GetPath().c_str(), GetID());
}
+llvm::Expected<std::vector<LoadedLibraryInfo>>
+NativeProcessWindows::GetLoadedLibraries() {
+ if (Status error = CacheLoadedModules(); error.Fail())
+ return error.ToError();
+
+ std::vector<LoadedLibraryInfo> libs;
+ libs.reserve(m_loaded_modules.size());
+ for (const auto &[file_spec, base] : m_loaded_modules) {
+ LoadedLibraryInfo info;
+ info.name = file_spec.GetPath();
+ info.base_addr = base;
+ libs.push_back(std::move(info));
+ }
+ return libs;
+}
+
+bool NativeProcessWindows::HasPendingLibraryEvents() {
+ return m_pending_library_events;
+}
+
void NativeProcessWindows::OnExitProcess(uint32_t exit_code) {
Log *log = GetLog(WindowsLog::Process);
LLDB_LOG(log, "Process {0} exited with code {1}", GetID(), exit_code);
@@ -641,14 +663,13 @@ void NativeProcessWindows::OnExitThread(lldb::tid_t thread_id,
void NativeProcessWindows::OnLoadDll(const ModuleSpec &module_spec,
lldb::addr_t module_addr) {
- // Simply invalidate the cached loaded modules.
- if (!m_loaded_modules.empty())
- m_loaded_modules.clear();
+ m_loaded_modules.clear();
+ m_pending_library_events = true;
}
void NativeProcessWindows::OnUnloadDll(lldb::addr_t module_addr) {
- if (!m_loaded_modules.empty())
- m_loaded_modules.clear();
+ m_loaded_modules.clear();
+ m_pending_library_events = true;
}
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 cfba787a3d220..83451240cdac9 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
@@ -41,6 +41,10 @@ class NativeProcessWindows : public NativeProcessProtocol,
llvm::Expected<std::unique_ptr<NativeProcessProtocol>>
Attach(lldb::pid_t pid, NativeDelegate &native_delegate) override;
+
+ Extension GetSupportedExtensions() const override {
+ return Extension::libraries;
+ }
};
Status Resume(const ResumeActionList &resume_actions) override;
@@ -95,6 +99,10 @@ class NativeProcessWindows : public NativeProcessProtocol,
Status GetFileLoadAddress(const llvm::StringRef &file_name,
lldb::addr_t &load_addr) override;
+ llvm::Expected<std::vector<LoadedLibraryInfo>> GetLoadedLibraries() override;
+
+ bool HasPendingLibraryEvents() override;
+
// ProcessDebugger Overrides
void OnExitProcess(uint32_t exit_code) override;
void OnDebuggerConnected(lldb::addr_t image_base) override;
@@ -134,6 +142,10 @@ class NativeProcessWindows : public NativeProcessProtocol,
Status CacheLoadedModules();
std::map<lldb_private::FileSpec, lldb::addr_t> m_loaded_modules;
+
+ /// Set whenever an OS DLL load/unload event has been seen since the last stop
+ /// reply.
+ bool m_pending_library_events = true;
};
//------------------------------------------------------------------
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
index e92d18ba8731a..e283e523c4df3 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -1054,6 +1054,9 @@ GDBRemoteCommunicationServerLLGS::PrepareStopReplyPacketForThread(
tid_stop_info.details.fork.child_tid);
}
+ if (process.HasPendingLibraryEvents())
+ response.PutCString("library:1;");
+
return response;
}
@@ -3386,6 +3389,24 @@ GDBRemoteCommunicationServerLLGS::ReadXferObject(llvm::StringRef object,
return MemoryBuffer::getMemBufferCopy(response.GetString(), __FUNCTION__);
}
+ if (object == "libraries") {
+ auto library_list = m_current_process->GetLoadedLibraries();
+ if (!library_list)
+ return library_list.takeError();
+
+ StreamString response;
+ response.Printf("<library-list>");
+ for (auto const &library : *library_list) {
+ response.Printf("<library name=\"%s\">",
+ XMLEncodeAttributeValue(library.name.c_str()).c_str());
+ response.Printf("<section address=\"0x%" PRIx64 "\"/>",
+ library.base_addr);
+ response.Printf("</library>");
+ }
+ response.Printf("</library-list>");
+ return MemoryBuffer::getMemBufferCopy(response.GetString(), __FUNCTION__);
+ }
+
if (object == "features" && annex == "target.xml")
return BuildTargetXml();
@@ -4401,6 +4422,8 @@ std::vector<std::string> GDBRemoteCommunicationServerLLGS::HandleFeatures(
ret.push_back("qXfer:auxv:read+");
if (bool(plugin_features & Extension::libraries_svr4))
ret.push_back("qXfer:libraries-svr4:read+");
+ if (bool(plugin_features & Extension::libraries))
+ ret.push_back("qXfer:libraries:read+");
if (bool(plugin_features & Extension::siginfo_read))
ret.push_back("qXfer:siginfo:read+");
if (bool(plugin_features & Extension::memory_tagging))
More information about the lldb-commits
mailing list