[Lldb-commits] [lldb] [lldb][windows] resolve subst drive in dynamic loader (PR #189026)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Fri Mar 27 08:30:19 PDT 2026
https://github.com/charles-zablit created https://github.com/llvm/llvm-project/pull/189026
On Windows, `subst` allow mapping virtual drives to paths. For instance: `C:\foo` is mapped to the `D:` virtual drive.
This can cause issues when comparing paths that are not resolved. The 2 paths above should be equal, yet comparing them as `FileSpecs` yields false.
This patch introduces a method to resolve mapped drives in paths and uses that method in `DynamicLoaderWindowsDYLD::OnLoadModule` to ensure that DLL that were loaded are cached with a resolved path to avoid duplicate entries.
>From b2128eee6b4527bc5f29b84cedf9f8a4f59444ce Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Fri, 27 Mar 2026 15:23:54 +0000
Subject: [PATCH] [lldb][windows] resolve subst drive in dynamic loader
---
.../lldb/Host/windows/HostInfoWindows.h | 5 ++
lldb/source/Host/windows/HostInfoWindows.cpp | 51 ++++++++++++
.../Windows-DYLD/DynamicLoaderWindowsDYLD.cpp | 10 ++-
lldb/unittests/Host/CMakeLists.txt | 6 ++
lldb/unittests/Host/windows/HostInfoTest.cpp | 82 +++++++++++++++++++
5 files changed, 152 insertions(+), 2 deletions(-)
create mode 100644 lldb/unittests/Host/windows/HostInfoTest.cpp
diff --git a/lldb/include/lldb/Host/windows/HostInfoWindows.h b/lldb/include/lldb/Host/windows/HostInfoWindows.h
index 42eaaf3415f7d..7acc837db4ebf 100644
--- a/lldb/include/lldb/Host/windows/HostInfoWindows.h
+++ b/lldb/include/lldb/Host/windows/HostInfoWindows.h
@@ -36,6 +36,11 @@ class HostInfoWindows : public HostInfoBase {
static bool GetEnvironmentVar(const std::string &var_name, std::string &var);
+ /// Resolves \p path to its subst-drive equivalent if one exists.
+ /// e.g. "C:\\S\\foo\\a.out" -> "S:\\foo\\a.out" when "subst S: C:\\S" is
+ /// active.
+ static std::optional<std::string> ResolveSubstDrive(llvm::StringRef path);
+
private:
static FileSpec m_program_filespec;
};
diff --git a/lldb/source/Host/windows/HostInfoWindows.cpp b/lldb/source/Host/windows/HostInfoWindows.cpp
index 0b0cda49a64c0..32667bae5488f 100644
--- a/lldb/source/Host/windows/HostInfoWindows.cpp
+++ b/lldb/source/Host/windows/HostInfoWindows.cpp
@@ -137,3 +137,54 @@ static llvm::ManagedStatic<WindowsUserIDResolver> g_user_id_resolver;
UserIDResolver &HostInfoWindows::GetUserIDResolver() {
return *g_user_id_resolver;
}
+
+std::optional<std::string>
+HostInfoWindows::ResolveSubstDrive(llvm::StringRef path) {
+ std::wstring wpath;
+ if (!llvm::ConvertUTF8toWide(path, wpath))
+ return std::nullopt;
+
+ // Enumerate all logical drives and check whether any is a subst drive
+ // whose target is a prefix of the incoming path.
+ std::array<wchar_t, 512> drive_strings;
+ drive_strings[0] = L'\0';
+ if (!::GetLogicalDriveStringsW(drive_strings.size(), drive_strings.data()))
+ return std::nullopt;
+
+ std::array<wchar_t, 3> drive_buf = {L'_', L':', L'\0'};
+ for (const wchar_t *it = drive_strings.data(); *it != L'\0';
+ it += wcslen(it) + 1) {
+ drive_buf[0] = it[0];
+ std::array<wchar_t, MAX_PATH> device_name;
+ if (!::QueryDosDeviceW(drive_buf.data(), device_name.data(),
+ device_name.size()))
+ continue;
+
+ // Subst drives appear as "\??\<real-path>" (e.g. "\??\C:\S").
+ // Real drives map to "\Device\Harddisk...". Skip them.
+ std::wstring_view device(device_name.data());
+ if (device.substr(0, 4) != L"\\??\\")
+ continue;
+ std::wstring_view subst_target = device.substr(4);
+ if (subst_target.empty())
+ continue;
+
+ if (wpath.size() < subst_target.size())
+ continue;
+ if (_wcsnicmp(wpath.c_str(), subst_target.data(), subst_target.size()) != 0)
+ continue;
+
+ // The match must land on a path separator (or be the full string).
+ size_t n = subst_target.size();
+ if (n < wpath.size() && wpath[n] != L'\\' && wpath[n] != L'/')
+ continue;
+
+ std::wstring rebuilt(drive_buf.data(), 2); // e.g. L"S:"
+ rebuilt += wpath.substr(n);
+ std::string new_path;
+ if (llvm::convertWideToUTF8(rebuilt, new_path))
+ return new_path;
+ }
+
+ return std::nullopt;
+}
diff --git a/lldb/source/Plugins/DynamicLoader/Windows-DYLD/DynamicLoaderWindowsDYLD.cpp b/lldb/source/Plugins/DynamicLoader/Windows-DYLD/DynamicLoaderWindowsDYLD.cpp
index 44d671f843b74..c26b345a87cc5 100644
--- a/lldb/source/Plugins/DynamicLoader/Windows-DYLD/DynamicLoaderWindowsDYLD.cpp
+++ b/lldb/source/Plugins/DynamicLoader/Windows-DYLD/DynamicLoaderWindowsDYLD.cpp
@@ -10,6 +10,7 @@
#include "lldb/Core/Module.h"
#include "lldb/Core/PluginManager.h"
+#include "lldb/Host/windows/HostInfoWindows.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/Platform.h"
#include "lldb/Target/Process.h"
@@ -68,8 +69,13 @@ void DynamicLoaderWindowsDYLD::OnLoadModule(lldb::ModuleSP module_sp,
// Resolve the module unless we already have one.
if (!module_sp) {
Status error;
- module_sp = m_process->GetTarget().GetOrCreateModule(module_spec,
- true /* notify */, &error);
+ ModuleSpec resolved_spec(module_spec);
+ auto resolved_path =
+ HostInfoWindows::ResolveSubstDrive(module_spec.GetFileSpec().GetPath());
+ if (resolved_path)
+ resolved_spec.GetFileSpec() = FileSpec(*resolved_path);
+ module_sp = m_process->GetTarget().GetOrCreateModule(
+ resolved_spec, true /* notify */, &error);
if (error.Fail())
return;
}
diff --git a/lldb/unittests/Host/CMakeLists.txt b/lldb/unittests/Host/CMakeLists.txt
index 5591edda38aca..93e3d7cf65571 100644
--- a/lldb/unittests/Host/CMakeLists.txt
+++ b/lldb/unittests/Host/CMakeLists.txt
@@ -23,6 +23,12 @@ if (UNIX)
)
endif()
+if (WIN32)
+ list(APPEND FILES
+ windows/HostInfoTest.cpp
+ )
+endif()
+
if (LLDB_ENABLE_TERMIOS)
list(APPEND FILES
posix/TerminalTest.cpp
diff --git a/lldb/unittests/Host/windows/HostInfoTest.cpp b/lldb/unittests/Host/windows/HostInfoTest.cpp
new file mode 100644
index 0000000000000..79b209568769d
--- /dev/null
+++ b/lldb/unittests/Host/windows/HostInfoTest.cpp
@@ -0,0 +1,82 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "lldb/Host/windows/HostInfoWindows.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <windows.h>
+
+// Find an unused drive letter to use as the subst target.
+static wchar_t FindFreeDriveLetter() {
+ DWORD in_use = ::GetLogicalDrives();
+ for (wchar_t c = L'D'; c <= L'Z'; ++c)
+ if (!(in_use & (1u << (c - L'A'))))
+ return c;
+ return L'\0';
+}
+
+class ResolveSubstDriveTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ m_drive_letter = FindFreeDriveLetter();
+ ASSERT_NE(m_drive_letter, L'\0') << "No free drive letter";
+ m_drive[0] = m_drive_letter;
+ m_drive[1] = L':';
+ m_drive[2] = L'\0';
+ ASSERT_TRUE(::DefineDosDeviceW(0, m_drive, L"C:\\SubstTestRoot"))
+ << "DefineDosDeviceW failed: " << ::GetLastError();
+ }
+
+ void TearDown() override {
+ if (m_drive_letter)
+ ::DefineDosDeviceW(DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE,
+ m_drive, L"C:\\SubstTestRoot");
+ }
+
+ wchar_t m_drive_letter = L'\0';
+ wchar_t m_drive[3] = {};
+};
+
+TEST_F(ResolveSubstDriveTest, ResolvesRealPath) {
+ auto result = lldb_private::HostInfoWindows::ResolveSubstDrive(
+ "C:\\SubstTestRoot\\foo\\a.out");
+ ASSERT_TRUE(result.has_value());
+ // drive_letter + ":\foo\a.out"
+ // clang-format off
+ std::string expected = {(char)m_drive_letter, ':', '\\', 'f','o','o','\\','a','.','o','u','t'};
+ // clang-format on
+ EXPECT_EQ(*result, expected);
+}
+
+TEST_F(ResolveSubstDriveTest, CaseInsensitive) {
+ auto result = lldb_private::HostInfoWindows::ResolveSubstDrive(
+ "c:\\substtestroot\\bar.dll");
+ EXPECT_TRUE(result.has_value());
+}
+
+TEST_F(ResolveSubstDriveTest, NoMatchReturnsNullopt) {
+ auto result = lldb_private::HostInfoWindows::ResolveSubstDrive(
+ "C:\\UnrelatedDir\\foo.exe");
+ EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(ResolveSubstDriveTest, ExactRootMatch) {
+ // Path equal to the subst target itself (no trailing component)
+ auto result =
+ lldb_private::HostInfoWindows::ResolveSubstDrive("C:\\SubstTestRoot");
+ ASSERT_TRUE(result.has_value());
+ std::string expected = {(char)m_drive_letter, ':'};
+ EXPECT_EQ(*result, expected);
+}
+
+TEST_F(ResolveSubstDriveTest, PartialDirectoryNameNoFalseMatch) {
+ // "C:\SubstTestRootExtra\..." must NOT match "C:\SubstTestRoot"
+ auto result = lldb_private::HostInfoWindows::ResolveSubstDrive(
+ "C:\\SubstTestRootExtra\\foo.exe");
+ EXPECT_FALSE(result.has_value());
+}
\ No newline at end of file
More information about the lldb-commits
mailing list