[Lldb-commits] [lldb] [lldb][windows] resolve subst drive in dynamic loader (PR #189026)

via lldb-commits lldb-commits at lists.llvm.org
Fri Mar 27 08:30:58 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Charles Zablit (charles-zablit)

<details>
<summary>Changes</summary>

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.

---
Full diff: https://github.com/llvm/llvm-project/pull/189026.diff


5 Files Affected:

- (modified) lldb/include/lldb/Host/windows/HostInfoWindows.h (+5) 
- (modified) lldb/source/Host/windows/HostInfoWindows.cpp (+51) 
- (modified) lldb/source/Plugins/DynamicLoader/Windows-DYLD/DynamicLoaderWindowsDYLD.cpp (+8-2) 
- (modified) lldb/unittests/Host/CMakeLists.txt (+6) 
- (added) lldb/unittests/Host/windows/HostInfoTest.cpp (+82) 


``````````diff
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

``````````

</details>


https://github.com/llvm/llvm-project/pull/189026


More information about the lldb-commits mailing list