[Lldb-commits] [lldb] [lldb][Windows] Key lldb-server's loaded-module list by base address (PR #223445)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Tue Sep 15 07:07:17 PDT 2026
https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/223445
>From e84abff575da6801dabd8725887a4d8fa1f4ff28 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Mon, 14 Sep 2026 16:43:02 +0100
Subject: [PATCH] [lldb][Windows] Key lldb-server's loaded-module list by base
address
---
.../Process/Windows/Common/LoadedModuleList.h | 81 ++++++++++++++
.../Windows/Common/NativeProcessWindows.cpp | 43 +++-----
.../Windows/Common/NativeProcessWindows.h | 4 +-
lldb/unittests/Process/CMakeLists.txt | 3 +
lldb/unittests/Process/Windows/CMakeLists.txt | 6 +
.../Process/Windows/LoadedModuleListTest.cpp | 104 ++++++++++++++++++
6 files changed, 213 insertions(+), 28 deletions(-)
create mode 100644 lldb/source/Plugins/Process/Windows/Common/LoadedModuleList.h
create mode 100644 lldb/unittests/Process/Windows/CMakeLists.txt
create mode 100644 lldb/unittests/Process/Windows/LoadedModuleListTest.cpp
diff --git a/lldb/source/Plugins/Process/Windows/Common/LoadedModuleList.h b/lldb/source/Plugins/Process/Windows/Common/LoadedModuleList.h
new file mode 100644
index 0000000000000..6d3e0d858aad9
--- /dev/null
+++ b/lldb/source/Plugins/Process/Windows/Common/LoadedModuleList.h
@@ -0,0 +1,81 @@
+//===-- LoadedModuleList.h --------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_PROCESS_WINDOWS_COMMON_LOADEDMODULELIST_H
+#define LLDB_SOURCE_PLUGINS_PROCESS_WINDOWS_COMMON_LOADEDMODULELIST_H
+
+#include "lldb/Utility/FileSpec.h"
+#include "lldb/lldb-types.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+
+#include <map>
+#include <optional>
+
+namespace lldb_private {
+
+/// The images mapped into the inferior. The same file can be mapped more than
+/// once, and an UNLOAD_DLL_DEBUG_EVENT only carries a base address, so every
+/// mapping has to be resolvable back to its file.
+class LoadedModuleList {
+ using Container = std::map<FileSpec, llvm::SmallVector<lldb::addr_t, 1>>;
+
+public:
+ void Add(const FileSpec &file_spec, lldb::addr_t base_addr) {
+ m_modules[file_spec].push_back(base_addr);
+ }
+
+ /// Drop the mapping at \p base_addr. \return the file if that was its last
+ /// mapping, an empty FileSpec otherwise.
+ FileSpec Remove(lldb::addr_t base_addr) {
+ for (auto it = m_modules.begin(); it != m_modules.end(); ++it) {
+ auto &base_addrs = it->second;
+ auto addr_it = llvm::find(base_addrs, base_addr);
+ if (addr_it == base_addrs.end())
+ continue;
+
+ base_addrs.erase(addr_it);
+ if (!base_addrs.empty())
+ return {};
+
+ FileSpec file_spec = it->first;
+ m_modules.erase(it);
+ return file_spec;
+ }
+ return {};
+ }
+
+ /// The address \p file_spec was first mapped at. That is the mapping the
+ /// loader bound the image's imports against, so it is the one to report.
+ std::optional<lldb::addr_t> GetBaseAddress(const FileSpec &file_spec) const {
+ auto it = m_modules.find(file_spec);
+ if (it == m_modules.end())
+ return std::nullopt;
+ return it->second.front();
+ }
+
+ /// \return the stored spelling of \p file_spec, or nullptr if it is not
+ /// mapped.
+ const FileSpec *FindFile(const FileSpec &file_spec) const {
+ auto it = m_modules.find(file_spec);
+ return it == m_modules.end() ? nullptr : &it->first;
+ }
+
+ bool IsEmpty() const { return m_modules.empty(); }
+ size_t GetSize() const { return m_modules.size(); }
+
+ Container::const_iterator begin() const { return m_modules.begin(); }
+ Container::const_iterator end() const { return m_modules.end(); }
+
+private:
+ Container m_modules;
+};
+
+} // namespace lldb_private
+
+#endif
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index bb1feca5eb097..0fa3eae2c7ed6 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -26,6 +26,7 @@
#include "lldb/Target/MemoryRegionInfo.h"
#include "lldb/Target/Process.h"
#include "lldb/Utility/State.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/Errc.h"
@@ -360,7 +361,7 @@ Status NativeProcessWindows::RemoveBreakpoint(lldb::addr_t addr,
Status NativeProcessWindows::CacheLoadedModules() {
Status error;
- if (!m_loaded_modules.empty())
+ if (!m_loaded_modules.IsEmpty())
return Status();
// Retrieve loaded modules by a Target/Module-free implementation.
@@ -376,11 +377,11 @@ Status NativeProcessWindows::CacheLoadedModules() {
FileSpec file_spec(path);
FileSystem::Instance().Resolve(file_spec);
- m_loaded_modules[file_spec] = (addr_t)me.modBaseAddr;
+ m_loaded_modules.Add(file_spec, (addr_t)me.modBaseAddr);
} while (Module32Next(snapshot.get(), &me));
}
- if (!m_loaded_modules.empty())
+ if (!m_loaded_modules.IsEmpty())
return Status();
}
@@ -396,11 +397,9 @@ Status NativeProcessWindows::GetLoadedModuleFileSpec(const char *module_path,
FileSpec module_file_spec(module_path);
FileSystem::Instance().Resolve(module_file_spec);
- for (auto &it : m_loaded_modules) {
- if (it.first == module_file_spec) {
- file_spec = it.first;
- return Status();
- }
+ if (const FileSpec *found = m_loaded_modules.FindFile(module_file_spec)) {
+ file_spec = *found;
+ return Status();
}
return Status::FromErrorStringWithFormat(
"Module (%s) not found in process %" PRIu64 "!",
@@ -417,11 +416,9 @@ NativeProcessWindows::GetFileLoadAddress(const llvm::StringRef &file_name,
load_addr = LLDB_INVALID_ADDRESS;
FileSpec file_spec(file_name);
FileSystem::Instance().Resolve(file_spec);
- for (auto &it : m_loaded_modules) {
- if (it.first == file_spec) {
- load_addr = it.second;
- return Status();
- }
+ if (std::optional<addr_t> base = m_loaded_modules.GetBaseAddress(file_spec)) {
+ load_addr = *base;
+ return Status();
}
return Status::FromErrorStringWithFormat(
"Can't get loaded address of file (%s) in process %" PRIu64 "!",
@@ -434,11 +431,11 @@ NativeProcessWindows::GetLoadedLibraries() {
return error.ToError();
std::vector<LoadedLibraryInfo> libs;
- libs.reserve(m_loaded_modules.size());
- for (const auto &[file_spec, base] : m_loaded_modules) {
+ libs.reserve(m_loaded_modules.GetSize());
+ for (const auto &[file_spec, base_addrs] : m_loaded_modules) {
LoadedLibraryInfo info;
info.name = file_spec.GetPath();
- info.base_addr = base;
+ info.base_addr = base_addrs.front();
libs.push_back(std::move(info));
}
return libs;
@@ -492,7 +489,7 @@ void NativeProcessWindows::OnDebuggerConnected(lldb::addr_t image_base) {
FileSpec exe = info.GetExecutableFile();
if (exe) {
FileSystem::Instance().Resolve(exe);
- m_loaded_modules[exe] = image_base;
+ m_loaded_modules.Add(exe, image_base);
}
}
@@ -742,7 +739,7 @@ DllEventAction NativeProcessWindows::OnLoadDll(const ModuleSpec &module_spec,
FileSpec resolved = module_spec.GetFileSpec();
if (resolved) {
FileSystem::Instance().Resolve(resolved);
- m_loaded_modules[resolved] = module_addr;
+ m_loaded_modules.Add(resolved, module_addr);
}
m_pending_library_events = true;
@@ -779,15 +776,7 @@ DllEventAction NativeProcessWindows::OnUnloadDll(lldb::addr_t module_addr,
Log *log = GetLog(WindowsLog::Process);
llvm::sys::ScopedLock lock(m_mutex);
- FileSpec unloaded_spec;
- for (auto it = m_loaded_modules.begin(); it != m_loaded_modules.end();) {
- if (it->second == module_addr) {
- unloaded_spec = it->first;
- it = m_loaded_modules.erase(it);
- } else {
- ++it;
- }
- }
+ FileSpec unloaded_spec = m_loaded_modules.Remove(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 3074eea1bc932..114b4a200e18c 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h
@@ -14,6 +14,7 @@
#include "lldb/lldb-forward.h"
#include "IDebugDelegate.h"
+#include "LoadedModuleList.h"
#include "ProcessDebugger.h"
namespace lldb_private {
@@ -172,7 +173,8 @@ class NativeProcessWindows : public NativeProcessProtocol,
const ExceptionRecord &record);
Status CacheLoadedModules();
- std::map<lldb_private::FileSpec, lldb::addr_t> m_loaded_modules;
+
+ LoadedModuleList m_loaded_modules;
/// Set whenever an OS DLL load/unload event has been seen since the last stop
/// reply.
diff --git a/lldb/unittests/Process/CMakeLists.txt b/lldb/unittests/Process/CMakeLists.txt
index a240d773c3f30..939fb9f741c68 100644
--- a/lldb/unittests/Process/CMakeLists.txt
+++ b/lldb/unittests/Process/CMakeLists.txt
@@ -4,6 +4,9 @@ if (CMAKE_SYSTEM_NAME MATCHES "Linux|Android")
add_subdirectory(Linux)
add_subdirectory(POSIX)
endif()
+if (CMAKE_SYSTEM_NAME MATCHES "Windows")
+ add_subdirectory(Windows)
+endif()
add_subdirectory(Utility)
add_subdirectory(minidump)
diff --git a/lldb/unittests/Process/Windows/CMakeLists.txt b/lldb/unittests/Process/Windows/CMakeLists.txt
new file mode 100644
index 0000000000000..672c0983e88f1
--- /dev/null
+++ b/lldb/unittests/Process/Windows/CMakeLists.txt
@@ -0,0 +1,6 @@
+add_lldb_unittest(ProcessWindowsTests
+ LoadedModuleListTest.cpp
+
+ LINK_LIBS
+ lldbUtility
+ )
diff --git a/lldb/unittests/Process/Windows/LoadedModuleListTest.cpp b/lldb/unittests/Process/Windows/LoadedModuleListTest.cpp
new file mode 100644
index 0000000000000..08c082d4f3b15
--- /dev/null
+++ b/lldb/unittests/Process/Windows/LoadedModuleListTest.cpp
@@ -0,0 +1,104 @@
+//===-- LoadedModuleListTest.cpp ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Plugins/Process/Windows/Common/LoadedModuleList.h"
+#include "gtest/gtest.h"
+
+using namespace lldb_private;
+
+namespace {
+const FileSpec kDll("C:\\a\\one.dll", FileSpec::Style::windows);
+const FileSpec kOther("C:\\a\\two.dll", FileSpec::Style::windows);
+} // namespace
+
+TEST(LoadedModuleListTest, ReportsTheMappingItWasGiven) {
+ LoadedModuleList modules;
+ modules.Add(kDll, 0x1000);
+
+ EXPECT_EQ(modules.GetSize(), 1u);
+ EXPECT_EQ(modules.GetBaseAddress(kDll), 0x1000u);
+ EXPECT_EQ(*modules.FindFile(kDll), kDll);
+}
+
+TEST(LoadedModuleListTest, IsEmptyUntilSomethingIsMapped) {
+ LoadedModuleList modules;
+ EXPECT_TRUE(modules.IsEmpty());
+ modules.Add(kDll, 0x1000);
+ EXPECT_FALSE(modules.IsEmpty());
+}
+
+TEST(LoadedModuleListTest, DoesNotFindAnUnmappedFile) {
+ LoadedModuleList modules;
+ modules.Add(kDll, 0x1000);
+
+ EXPECT_EQ(modules.GetBaseAddress(kOther), std::nullopt);
+ EXPECT_EQ(modules.FindFile(kOther), nullptr);
+}
+
+TEST(LoadedModuleListTest, ReportsTheFirstOfSeveralMappings) {
+ LoadedModuleList modules;
+ modules.Add(kDll, 0x1000);
+ modules.Add(kDll, 0x9000);
+
+ // One entry per file, at the address the loader mapped it first.
+ EXPECT_EQ(modules.GetSize(), 1u);
+ EXPECT_EQ(modules.GetBaseAddress(kDll), 0x1000u);
+}
+
+TEST(LoadedModuleListTest, KeepsTheFileWhileAnyMappingRemains) {
+ LoadedModuleList modules;
+ modules.Add(kDll, 0x1000);
+ modules.Add(kDll, 0x9000);
+
+ // Unmapping the second mapping must not retire the file: the program is
+ // still running from the first one.
+ EXPECT_FALSE(modules.Remove(0x9000));
+ EXPECT_EQ(modules.GetBaseAddress(kDll), 0x1000u);
+
+ EXPECT_EQ(modules.Remove(0x1000), kDll);
+ EXPECT_TRUE(modules.IsEmpty());
+}
+
+TEST(LoadedModuleListTest, FallsBackToASurvivingMapping) {
+ LoadedModuleList modules;
+ modules.Add(kDll, 0x1000);
+ modules.Add(kDll, 0x9000);
+
+ // Dropping the reported mapping leaves the other one to report.
+ EXPECT_FALSE(modules.Remove(0x1000));
+ EXPECT_EQ(modules.GetBaseAddress(kDll), 0x9000u);
+}
+
+TEST(LoadedModuleListTest, RemovesOnlyTheGivenMapping) {
+ LoadedModuleList modules;
+ modules.Add(kDll, 0x1000);
+ modules.Add(kOther, 0x2000);
+
+ EXPECT_EQ(modules.Remove(0x1000), kDll);
+ EXPECT_EQ(modules.GetSize(), 1u);
+ EXPECT_EQ(modules.GetBaseAddress(kOther), 0x2000u);
+}
+
+TEST(LoadedModuleListTest, IgnoresAnUnknownAddress) {
+ LoadedModuleList modules;
+ modules.Add(kDll, 0x1000);
+
+ EXPECT_FALSE(modules.Remove(0xdead));
+ EXPECT_EQ(modules.GetSize(), 1u);
+}
+
+TEST(LoadedModuleListTest, SameAddressTwiceIsNotOneMapping) {
+ LoadedModuleList modules;
+ modules.Add(kDll, 0x1000);
+ modules.Add(kDll, 0x1000);
+
+ // Two reported loads owe two reported unloads, even at the same base.
+ EXPECT_FALSE(modules.Remove(0x1000));
+ EXPECT_EQ(modules.Remove(0x1000), kDll);
+ EXPECT_TRUE(modules.IsEmpty());
+}
More information about the lldb-commits
mailing list