[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