[Lldb-commits] [lldb] [lldb][Windows] Report module paths the way the loader spelled them (PR #226492)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Wed Sep 30 04:06:26 PDT 2026
https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/226492
>From f3ccecf65d2a7a3df68496dae0f7595dd5280a24 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Fri, 25 Sep 2026 15:40:10 +0100
Subject: [PATCH 1/2] [lldb][Windows] Report module paths the way the loader
spelled them
---
.../Windows-DYLD/DynamicLoaderWindowsDYLD.cpp | 9 +-
.../ObjectFile/PECOFF/ObjectFilePECOFF.cpp | 5 +-
.../Process/Windows/Common/DebuggerThread.cpp | 97 ++++++++++++++++---
.../Process/Windows/Common/DebuggerThread.h | 6 ++
.../Windows/Common/NativeProcessWindows.cpp | 21 +++-
.../module_aliased_path/Makefile | 5 +
.../TestModuleAliasedPath.py | 90 +++++++++++++++++
.../functionalities/module_aliased_path/foo.c | 3 +
.../module_aliased_path/main.c | 6 ++
9 files changed, 224 insertions(+), 18 deletions(-)
create mode 100644 lldb/test/API/functionalities/module_aliased_path/Makefile
create mode 100644 lldb/test/API/functionalities/module_aliased_path/TestModuleAliasedPath.py
create mode 100644 lldb/test/API/functionalities/module_aliased_path/foo.c
create mode 100644 lldb/test/API/functionalities/module_aliased_path/main.c
diff --git a/lldb/source/Plugins/DynamicLoader/Windows-DYLD/DynamicLoaderWindowsDYLD.cpp b/lldb/source/Plugins/DynamicLoader/Windows-DYLD/DynamicLoaderWindowsDYLD.cpp
index 2d6e97b067c30..fb0619364dcb9 100644
--- a/lldb/source/Plugins/DynamicLoader/Windows-DYLD/DynamicLoaderWindowsDYLD.cpp
+++ b/lldb/source/Plugins/DynamicLoader/Windows-DYLD/DynamicLoaderWindowsDYLD.cpp
@@ -148,8 +148,15 @@ void DynamicLoaderWindowsDYLD::DidAttach() {
// Try to fetch the load address of the file from the process, since there
// could be randomization of the load address.
lldb::addr_t load_addr = GetLoadAddress(executable);
- if (load_addr == LLDB_INVALID_ADDRESS)
+ if (load_addr == LLDB_INVALID_ADDRESS) {
+ // The process does not know the executable under the target's path: it
+ // was started through another path to the same file, or runs a different
+ // one. Take the modules it reports as loaded, which replaces the target's
+ // executable with the process's.
+ auto error = m_process->LoadModules();
+ LLDB_LOG_ERROR(log, std::move(error), "failed to load modules: {0}");
return;
+ }
// Request the process base address.
lldb::addr_t image_base = m_process->GetImageInfoAddress();
diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
index 985dd6b516bae..bc769e0394eda 100644
--- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
+++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp
@@ -1161,12 +1161,11 @@ uint32_t ObjectFilePECOFF::ParseDependentModules() {
// At this moment we only have the base name of the DLL. The full path can
// only be seen after the dynamic loading. Our best guess is Try to get it
// with the help of the object file's directory.
- llvm::SmallString<128> dll_fullpath;
FileSpec dll_specs(dll_name);
dll_specs.SetDirectory(m_file.GetDirectory());
- if (!llvm::sys::fs::real_path(dll_specs.GetPath(), dll_fullpath))
- m_deps_filespec->EmplaceBack(dll_fullpath);
+ if (FileSystem::Instance().Exists(dll_specs))
+ m_deps_filespec->Append(dll_specs);
else {
// Known DLLs or DLL not found in the object file directory.
m_deps_filespec->EmplaceBack(dll_name);
diff --git a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
index 260ac0fe0faf6..67db11b8f4641 100644
--- a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
@@ -29,12 +29,14 @@
#include "lldb/Utility/LLDBLog.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/ConvertUTF.h"
+#include "llvm/Support/Path.h"
#include "llvm/Support/Threading.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
#include <pathcch.h>
#include <psapi.h>
+#include <winternl.h>
#ifndef STATUS_WX86_BREAKPOINT
#define STATUS_WX86_BREAKPOINT 0x4000001FL // For WOW64
@@ -453,6 +455,53 @@ DebuggerThread::HandleCreateThreadEvent(const CREATE_THREAD_DEBUG_INFO &info,
return DBG_CONTINUE;
}
+/// Read the path the process was created with from its process parameters
+/// (PEB::ProcessParameters->ImagePathName).
+///
+/// This keeps the subst drive, junction or symbolic link the process was
+/// launched through.
+static std::optional<std::string> GetImagePathFromPEB(HANDLE process) {
+ // winternl.h declares NtQueryInformationProcess, but there is no import
+ // library for it.
+ static LazyImport<decltype(&::NtQueryInformationProcess)>
+ s_query_information_process{L"ntdll.dll", "NtQueryInformationProcess"};
+ if (!s_query_information_process)
+ return std::nullopt;
+
+ PROCESS_BASIC_INFORMATION pbi = {};
+ if ((*s_query_information_process)(process, ProcessBasicInformation, &pbi,
+ sizeof(pbi), nullptr) < 0 ||
+ !pbi.PebBaseAddress)
+ return std::nullopt;
+
+ PEB peb = {};
+ if (!::ReadProcessMemory(process, pbi.PebBaseAddress, &peb, sizeof(peb),
+ nullptr) ||
+ !peb.ProcessParameters)
+ return std::nullopt;
+
+ RTL_USER_PROCESS_PARAMETERS params = {};
+ if (!::ReadProcessMemory(process, peb.ProcessParameters, ¶ms,
+ sizeof(params), nullptr) ||
+ !params.ImagePathName.Buffer || params.ImagePathName.Length == 0)
+ return std::nullopt;
+
+ std::wstring wpath(params.ImagePathName.Length / sizeof(wchar_t), L'\0');
+ if (!::ReadProcessMemory(process, params.ImagePathName.Buffer, wpath.data(),
+ params.ImagePathName.Length, nullptr))
+ return std::nullopt;
+
+ std::string path;
+ if (!llvm::convertWideToUTF8(wpath, path))
+ return std::nullopt;
+ // A process launched through an extended-length path has the "\\?\" prefix.
+ llvm::StringRef path_ref = path;
+ if (path_ref.consume_front("\\\\?\\UNC\\"))
+ return "\\\\" + path_ref.str();
+ path_ref.consume_front("\\\\?\\");
+ return path_ref.str();
+}
+
DWORD
DebuggerThread::HandleCreateProcessEvent(const CREATE_PROCESS_DEBUG_INFO &info,
DWORD thread_id) {
@@ -476,6 +525,7 @@ DebuggerThread::HandleCreateProcessEvent(const CREATE_PROCESS_DEBUG_INFO &info,
m_image_file = info.hFile;
lldb::addr_t load_addr = reinterpret_cast<lldb::addr_t>(info.lpBaseOfImage);
+ m_image_path = GetImagePathFromPEB(info.hProcess).value_or("");
m_debug_delegate->OnDebuggerConnected(load_addr);
return DBG_CONTINUE;
@@ -617,15 +667,17 @@ static std::optional<std::string> GetFileNameFromHandleFallback(HANDLE hFile) {
return GetMappedFileDosPath(::GetCurrentProcess(), pMem.get());
}
-static std::optional<std::string> GetFileNameByLoadAddress(HANDLE process,
- LPVOID base_addr) {
+/// The path the loader recorded for the module at \p base_addr, or nullopt if
+/// the loader's module list has no entry for it yet.
+static std::optional<std::string> GetLoaderModuleName(HANDLE process,
+ LPVOID base_addr) {
std::vector<wchar_t> module_filename(MAX_PATH + 1);
while (module_filename.size() <= PATHCCH_MAX_CCH) {
DWORD len =
::GetModuleFileNameExW(process, reinterpret_cast<HMODULE>(base_addr),
module_filename.data(), module_filename.size());
if (len == 0)
- break; // Not loaded as a module; fall back to the mapped-file query.
+ return std::nullopt;
if (len < module_filename.size()) {
std::string path_utf8;
llvm::convertWideToUTF8(std::wstring_view(module_filename.data(), len),
@@ -634,9 +686,7 @@ static std::optional<std::string> GetFileNameByLoadAddress(HANDLE process,
}
module_filename.resize(module_filename.size() * 2);
}
-
- // Fallback: ask the kernel for the file backing the mapping at this address.
- return GetMappedFileDosPath(process, base_addr);
+ return std::nullopt;
}
// Determine how many bytes can be read at `addr` in `process` before crossing
@@ -747,7 +797,25 @@ DebuggerThread::HandleLoadDllEvent(const LOAD_DLL_DEBUG_INFO &info,
action = m_debug_delegate->OnLoadDll(module_spec, load_addr, thread_id);
};
- std::optional<std::string> resolved_path;
+ HANDLE process = m_process.GetNativeProcess().GetSystemHandle();
+
+ // Prefer the path the loader used for the DLL, which keeps the subst drive,
+ // junction or symbolic link it was reached through.
+ std::optional<std::string> loader_path =
+ GetFileNameFromImageNameField(process, info);
+ if (loader_path && !llvm::sys::path::is_absolute(
+ *loader_path, llvm::sys::path::Style::windows))
+ loader_path.reset();
+ if (!loader_path)
+ loader_path = GetLoaderModuleName(process, info.lpBaseOfDll);
+ if (loader_path) {
+ llvm::StringRef path_ref = *loader_path;
+ path_ref.consume_front("\\\\?\\");
+ loader_path = path_ref.str();
+ }
+
+ // The file handle gives the resolved path, with the on-disk case.
+ std::optional<std::string> file_path;
if (info.hFile != nullptr) {
std::vector<wchar_t> buffer(1);
DWORD required_size =
@@ -760,17 +828,24 @@ DebuggerThread::HandleLoadDllEvent(const LOAD_DLL_DEBUG_INFO &info,
llvm::convertWideToUTF8(buffer.data(), path_str_utf8);
llvm::StringRef path_str = path_str_utf8;
path_str.consume_front("\\\\?\\");
- resolved_path = path_str.str();
+ file_path = path_str.str();
} else {
- resolved_path = GetFileNameFromHandleFallback(info.hFile);
+ file_path = GetFileNameFromHandleFallback(info.hFile);
}
}
- HANDLE process = m_process.GetNativeProcess().GetSystemHandle();
+ // The loader's path often differs from the on-disk one only in case (e.g.
+ // C:\windows\System32\KERNEL32.DLL). If that's the case, keep the on disk
+ // spelling.
+ std::optional<std::string> resolved_path = loader_path;
+ if (!resolved_path ||
+ (file_path &&
+ llvm::StringRef(*file_path).equals_insensitive(*resolved_path)))
+ resolved_path = file_path;
if (!resolved_path)
resolved_path = GetFileNameFromImageNameField(process, info);
if (!resolved_path)
- resolved_path = GetFileNameByLoadAddress(process, info.lpBaseOfDll);
+ resolved_path = GetMappedFileDosPath(process, info.lpBaseOfDll);
if (resolved_path)
on_load_dll(*resolved_path);
diff --git a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.h b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.h
index 03202264b01ee..a9fa3ec0c7599 100644
--- a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.h
+++ b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.h
@@ -36,6 +36,11 @@ class DebuggerThread : public std::enable_shared_from_this<DebuggerThread> {
HostProcess GetProcess() const { return m_process; }
HostThread GetMainThread() const { return m_main_thread; }
+ /// The path the process was created with, as recorded in its process
+ /// parameters, or an empty string if it could not be read. Set by the
+ /// create-process event.
+ const std::string &GetImagePath() const { return m_image_path; }
+
/// Returns the exception the debug loop is currently reporting, or null if
/// there is none. Safe to call from any thread.
ExceptionRecordSP GetActiveException();
@@ -75,6 +80,7 @@ class DebuggerThread : public std::enable_shared_from_this<DebuggerThread> {
// The image file of the process being debugged.
HANDLE m_image_file = nullptr;
+ std::string m_image_path;
// The current exception waiting to be handled.
ExceptionRecordSP m_active_exception;
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index 849931eaec372..0045696bd416b 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -360,8 +360,10 @@ Status NativeProcessWindows::RemoveBreakpoint(lldb::addr_t addr,
return RemoveSoftwareBreakpoint(addr);
}
-// Resolve the fully qualified, normalized on disk path of a module loaded in
-// the target process.
+// Get the path of a module loaded in the target process, as the loader recorded
+// it.
+//
+// Keep the loader's spelling.
static bool GetLoadedModulePath(HANDLE process, HMODULE module,
std::string &path) {
std::vector<wchar_t> name(MAX_PATH);
@@ -408,7 +410,15 @@ static bool GetLoadedModulePath(HANDLE process, HMODULE module,
canonical.replace(0, wcslen(kUNCPrefix), L"\\\\");
else if (canonical.rfind(kDOSPrefix, 0) == 0)
canonical.erase(0, wcslen(kDOSPrefix));
- wpath = std::move(canonical);
+ std::wstring loader_path = wpath;
+ if (loader_path.rfind(kUNCPrefix, 0) == 0)
+ loader_path.replace(0, wcslen(kUNCPrefix), L"\\\\");
+ else if (loader_path.rfind(kDOSPrefix, 0) == 0)
+ loader_path.erase(0, wcslen(kDOSPrefix));
+ if (::_wcsicmp(canonical.c_str(), loader_path.c_str()) == 0)
+ wpath = std::move(canonical);
+ else
+ wpath = std::move(loader_path);
break;
}
full.resize(needed);
@@ -551,6 +561,11 @@ void NativeProcessWindows::OnDebuggerConnected(lldb::addr_t image_base) {
if (got_info) {
FileSpec exe = info.GetExecutableFile();
+ if (const std::string &image_path =
+ m_session_data->m_debugger->GetImagePath();
+ !image_path.empty() &&
+ !llvm::StringRef(image_path).equals_insensitive(exe.GetPath()))
+ exe = FileSpec(image_path);
if (exe) {
FileSystem::Instance().Resolve(exe);
m_loaded_modules.Add(exe, image_base);
diff --git a/lldb/test/API/functionalities/module_aliased_path/Makefile b/lldb/test/API/functionalities/module_aliased_path/Makefile
new file mode 100644
index 0000000000000..e2607a0b08a7c
--- /dev/null
+++ b/lldb/test/API/functionalities/module_aliased_path/Makefile
@@ -0,0 +1,5 @@
+C_SOURCES := main.c
+DYLIB_C_SOURCES := foo.c
+DYLIB_NAME := foo
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/module_aliased_path/TestModuleAliasedPath.py b/lldb/test/API/functionalities/module_aliased_path/TestModuleAliasedPath.py
new file mode 100644
index 0000000000000..cde581ae87bcc
--- /dev/null
+++ b/lldb/test/API/functionalities/module_aliased_path/TestModuleAliasedPath.py
@@ -0,0 +1,90 @@
+"""
+Test that a process launched through an aliased directory reports its modules
+under that directory, so LLDB does not duplicate the modules it already has.
+
+The target is created from a junction to the build directory. lldb-server must
+report the executable and the DLL next to it with the path the Windows loader
+recorded, which keeps the junction, rather than with the resolved path, which
+does not. Otherwise LLDB adds a second module for each file and the breakpoints
+get a location in both.
+"""
+
+import os
+import subprocess
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+ at requireWindows
+ at skipIfWindowsAndNoLLDBServer
+ at skipIfRemote
+class TestModuleAliasedPath(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ def make_junction(self, real_dir):
+ """Create a junction to real_dir, skipping the test if the host can't
+ create one."""
+ alias_dir = real_dir + ".alias"
+ try:
+ if os.path.lexists(alias_dir):
+ os.rmdir(alias_dir)
+ subprocess.run(
+ 'mklink /J "%s" "%s"' % (alias_dir, real_dir),
+ shell=True,
+ check=True,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.PIPE,
+ text=True,
+ )
+ except (OSError, subprocess.CalledProcessError) as e:
+ detail = getattr(e, "stderr", None) or e
+ self.skipTest("could not create a junction: %s" % detail)
+ self.addTearDownHook(lambda: os.rmdir(alias_dir))
+ self.assertNotEqual(os.path.realpath(alias_dir), alias_dir)
+ return alias_dir
+
+ def test_launch_through_aliased_path(self):
+ self.build()
+
+ alias_dir = self.make_junction(self.getBuildDir())
+
+ target = self.dbg.CreateTarget(os.path.join(alias_dir, "a.out"))
+ self.assertTrue(target, VALID_TARGET)
+ main_bkpt = target.BreakpointCreateBySourceRegex(
+ "break main", lldb.SBFileSpec("main.c")
+ )
+ foo_bkpt = target.BreakpointCreateBySourceRegex(
+ "break here", lldb.SBFileSpec("foo.c")
+ )
+ self.assertEqual(main_bkpt.GetNumLocations(), 1)
+
+ process = target.LaunchSimple(None, None, self.get_process_working_directory())
+ self.assertState(process.GetState(), lldb.eStateStopped)
+ self.assertIsNotNone(
+ lldbutil.get_one_thread_stopped_at_breakpoint(process, main_bkpt)
+ )
+
+ lib_name = self.platformContext.getFullLibName("foo")
+ for name in ["a.out", lib_name]:
+ paths = [
+ m.GetFileSpec().fullpath
+ for m in target.module_iter()
+ if m.GetFileSpec().GetFilename() == name
+ ]
+ self.assertEqual(len(paths), 1, "one module for %s: %s" % (name, paths))
+ self.assertTrue(
+ paths[0].lower().startswith(alias_dir.lower()),
+ "%s is reported under the junction" % paths[0],
+ )
+
+ for bkpt in [main_bkpt, foo_bkpt]:
+ self.assertEqual(bkpt.GetNumLocations(), 1)
+ self.assertEqual(bkpt.GetNumResolvedLocations(), 1)
+
+ process.Continue()
+ self.assertIsNotNone(
+ lldbutil.get_one_thread_stopped_at_breakpoint(process, foo_bkpt)
+ )
diff --git a/lldb/test/API/functionalities/module_aliased_path/foo.c b/lldb/test/API/functionalities/module_aliased_path/foo.c
new file mode 100644
index 0000000000000..375a5207d2654
--- /dev/null
+++ b/lldb/test/API/functionalities/module_aliased_path/foo.c
@@ -0,0 +1,3 @@
+int LLDB_DYLIB_EXPORT foo(void) {
+ return 42; // break here
+}
diff --git a/lldb/test/API/functionalities/module_aliased_path/main.c b/lldb/test/API/functionalities/module_aliased_path/main.c
new file mode 100644
index 0000000000000..3bd932f5e291e
--- /dev/null
+++ b/lldb/test/API/functionalities/module_aliased_path/main.c
@@ -0,0 +1,6 @@
+extern int foo(void);
+
+int main(void) {
+ int result = foo(); // break main
+ return result == 42 ? 0 : 1;
+}
>From 64ed4ba487c9984cfb05415d34fa6aff4a7d428a Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 30 Sep 2026 12:06:05 +0100
Subject: [PATCH 2/2] fixup! [lldb][Windows] Report module paths the way the
loader spelled them
---
.../Process/Windows/Common/DebuggerThread.cpp | 18 +++-------
.../Windows/Common/NativeProcessWindows.cpp | 36 +++++++++----------
.../TestModuleAliasedPath.py | 5 ++-
3 files changed, 23 insertions(+), 36 deletions(-)
diff --git a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
index 67db11b8f4641..74db5a05ba4aa 100644
--- a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
@@ -17,6 +17,7 @@
#include "lldb/Host/windows/HostProcessWindows.h"
#include "lldb/Host/windows/HostThreadWindows.h"
#include "lldb/Host/windows/LazyImport.h"
+#include "lldb/Host/windows/PathUtils.h"
#include "lldb/Host/windows/ProcessLauncherWindows.h"
#include "lldb/Target/Process.h"
#include "lldb/Utility/FileSpec.h"
@@ -495,11 +496,7 @@ static std::optional<std::string> GetImagePathFromPEB(HANDLE process) {
if (!llvm::convertWideToUTF8(wpath, path))
return std::nullopt;
// A process launched through an extended-length path has the "\\?\" prefix.
- llvm::StringRef path_ref = path;
- if (path_ref.consume_front("\\\\?\\UNC\\"))
- return "\\\\" + path_ref.str();
- path_ref.consume_front("\\\\?\\");
- return path_ref.str();
+ return StripExtendedLengthPrefix(path);
}
DWORD
@@ -808,11 +805,8 @@ DebuggerThread::HandleLoadDllEvent(const LOAD_DLL_DEBUG_INFO &info,
loader_path.reset();
if (!loader_path)
loader_path = GetLoaderModuleName(process, info.lpBaseOfDll);
- if (loader_path) {
- llvm::StringRef path_ref = *loader_path;
- path_ref.consume_front("\\\\?\\");
- loader_path = path_ref.str();
- }
+ if (loader_path)
+ loader_path = StripExtendedLengthPrefix(*loader_path);
// The file handle gives the resolved path, with the on-disk case.
std::optional<std::string> file_path;
@@ -826,9 +820,7 @@ DebuggerThread::HandleLoadDllEvent(const LOAD_DLL_DEBUG_INFO &info,
VOLUME_NAME_DOS);
std::string path_str_utf8;
llvm::convertWideToUTF8(buffer.data(), path_str_utf8);
- llvm::StringRef path_str = path_str_utf8;
- path_str.consume_front("\\\\?\\");
- file_path = path_str.str();
+ file_path = StripExtendedLengthPrefix(path_str_utf8);
} else {
file_path = GetFileNameFromHandleFallback(info.hFile);
}
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index 0045696bd416b..fa37c1a22d77f 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -22,6 +22,7 @@
#include "lldb/Host/windows/AutoHandle.h"
#include "lldb/Host/windows/ConnectionConPTYWindows.h"
#include "lldb/Host/windows/HostThreadWindows.h"
+#include "lldb/Host/windows/PathUtils.h"
#include "lldb/Host/windows/ProcessLauncherWindows.h"
#include "lldb/Host/windows/PseudoConsole.h"
#include "lldb/Target/MemoryRegionInfo.h"
@@ -381,15 +382,19 @@ static bool GetLoadedModulePath(HANDLE process, HMODULE module,
}
std::wstring wpath(name.data(), len);
+ if (!llvm::convertWideToUTF8(wpath, path))
+ return false;
+ path = StripExtendedLengthPrefix(path);
- // Canonicalize through a handle to the image file so the reported path
- // matches the on-disk name exactly.
+ // The loader's path often differs from the on-disk one only in case (e.g.
+ // C:\windows\System32\KERNEL32.DLL). Open the image file to get its on-disk
+ // spelling, and use it if that's the only difference.
AutoHandle file(::CreateFileW(
wpath.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
if (!file.IsValid())
- return llvm::convertWideToUTF8(wpath, path);
+ return true;
// Unlike GetModuleFileNameExW, GetFinalPathNameByHandleW reports the buffer
// size it needs instead of truncating, so start empty and let the first call
@@ -402,29 +407,20 @@ static bool GetLoadedModulePath(HANDLE process, HMODULE module,
if (needed == 0)
break;
if (needed < full.size()) {
- std::wstring canonical(full.data(), needed);
+ std::string canonical;
+ if (!llvm::convertWideToUTF8(std::wstring_view(full.data(), needed),
+ canonical))
+ break;
// GetFinalPathNameByHandleW returns an extended-length ("\\?\") path.
- static const wchar_t kUNCPrefix[] = L"\\\\?\\UNC\\";
- static const wchar_t kDOSPrefix[] = L"\\\\?\\";
- if (canonical.rfind(kUNCPrefix, 0) == 0)
- canonical.replace(0, wcslen(kUNCPrefix), L"\\\\");
- else if (canonical.rfind(kDOSPrefix, 0) == 0)
- canonical.erase(0, wcslen(kDOSPrefix));
- std::wstring loader_path = wpath;
- if (loader_path.rfind(kUNCPrefix, 0) == 0)
- loader_path.replace(0, wcslen(kUNCPrefix), L"\\\\");
- else if (loader_path.rfind(kDOSPrefix, 0) == 0)
- loader_path.erase(0, wcslen(kDOSPrefix));
- if (::_wcsicmp(canonical.c_str(), loader_path.c_str()) == 0)
- wpath = std::move(canonical);
- else
- wpath = std::move(loader_path);
+ canonical = StripExtendedLengthPrefix(canonical);
+ if (llvm::StringRef(canonical).equals_insensitive(path))
+ path = std::move(canonical);
break;
}
full.resize(needed);
}
- return llvm::convertWideToUTF8(wpath, path);
+ return true;
}
Status NativeProcessWindows::CacheLoadedModules() {
diff --git a/lldb/test/API/functionalities/module_aliased_path/TestModuleAliasedPath.py b/lldb/test/API/functionalities/module_aliased_path/TestModuleAliasedPath.py
index cde581ae87bcc..d1c1aab1dbedc 100644
--- a/lldb/test/API/functionalities/module_aliased_path/TestModuleAliasedPath.py
+++ b/lldb/test/API/functionalities/module_aliased_path/TestModuleAliasedPath.py
@@ -2,8 +2,8 @@
Test that a process launched through an aliased directory reports its modules
under that directory, so LLDB does not duplicate the modules it already has.
-The target is created from a junction to the build directory. lldb-server must
-report the executable and the DLL next to it with the path the Windows loader
+The target is created from a junction to the build directory. LLDB must report
+the executable and the DLL next to it with the path the Windows loader
recorded, which keeps the junction, rather than with the resolved path, which
does not. Otherwise LLDB adds a second module for each file and the breakpoints
get a location in both.
@@ -19,7 +19,6 @@
@requireWindows
- at skipIfWindowsAndNoLLDBServer
@skipIfRemote
class TestModuleAliasedPath(TestBase):
NO_DEBUG_INFO_TESTCASE = True
More information about the lldb-commits
mailing list