From 4cde00bf2947b8b10f0579f76d519c3af17bcb58 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Wed, 26 Nov 2025 20:43:32 +0100 Subject: [PATCH 1/2] [LLDB][NativePDB] Look for PDB in `target.debug-file-search-paths` --- .../NativePDB/SymbolFileNativePDB.cpp | 52 +++++++++--- .../NativePDB/find-pdb-next-to-exe.test | 81 +++++++++++++++++++ 2 files changed, 124 insertions(+), 9 deletions(-) create mode 100644 lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp index aaec1600dacff..4ee1b0759a10e 100644 --- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp +++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp @@ -86,6 +86,40 @@ static lldb::LanguageType TranslateLanguage(PDB_Lang lang) { } } +static std::optional +findMatchingPDBFilePath(llvm::StringRef original_pdb_path, + llvm::StringRef exe_path) { + const FileSystem &fs = FileSystem::Instance(); + + if (fs.Exists(original_pdb_path)) + return std::string(original_pdb_path); + + const auto exe_dir = FileSpec(exe_path).CopyByRemovingLastPathComponent(); + // While the exe_path uses the native style, the exe might be compiled on a + // different OS, so try to guess the style used. + const FileSpec original_pdb_spec(original_pdb_path, + FileSpec::GuessPathStyle(original_pdb_path) + .value_or(FileSpec::Style::native)); + const llvm::StringRef pdb_filename = original_pdb_spec.GetFilename(); + + // If the file doesn't exist, perhaps the path specified at build time + // doesn't match the PDB's current location, so check the location of the + // executable. + const FileSpec local_pdb = exe_dir.CopyByAppendingPathComponent(pdb_filename); + if (fs.Exists(local_pdb)) + return local_pdb.GetPath(); + + // Otherwise, search for one in target.debug-file-search-paths + FileSpecList search_paths = Target::GetDefaultDebugFileSearchPaths(); + for (const FileSpec &search_dir : search_paths) { + FileSpec pdb_path = search_dir.CopyByAppendingPathComponent(pdb_filename); + if (fs.Exists(pdb_path)) + return pdb_path.GetPath(); + } + + return std::nullopt; +} + static std::unique_ptr loadMatchingPDBFile(std::string exe_path, llvm::BumpPtrAllocator &allocator) { // Try to find a matching PDB for an EXE. @@ -113,17 +147,14 @@ loadMatchingPDBFile(std::string exe_path, llvm::BumpPtrAllocator &allocator) { return nullptr; } - // If the file doesn't exist, perhaps the path specified at build time - // doesn't match the PDB's current location, so check the location of the - // executable. - if (!FileSystem::Instance().Exists(pdb_file)) { - const auto exe_dir = FileSpec(exe_path).CopyByRemovingLastPathComponent(); - const auto pdb_name = FileSpec(pdb_file).GetFilename().GetCString(); - pdb_file = exe_dir.CopyByAppendingPathComponent(pdb_name).GetPathAsConstString().GetStringRef(); - } + std::optional resolved_pdb_path = + findMatchingPDBFilePath(pdb_file, exe_path); + if (!resolved_pdb_path) + return nullptr; // If the file is not a PDB or if it doesn't have a matching GUID, fail. - auto pdb = ObjectFilePDB::loadPDBFile(std::string(pdb_file), allocator); + auto pdb = + ObjectFilePDB::loadPDBFile(*std::move(resolved_pdb_path), allocator); if (!pdb) return nullptr; @@ -137,6 +168,9 @@ loadMatchingPDBFile(std::string exe_path, llvm::BumpPtrAllocator &allocator) { if (expected_info->getGuid() != guid) return nullptr; + + LLDB_LOG(GetLog(LLDBLog::Symbols), "Loading {0} for {1}", pdb->getFilePath(), + exe_path); return pdb; } diff --git a/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test b/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test new file mode 100644 index 0000000000000..9ca850b1fd5b6 --- /dev/null +++ b/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test @@ -0,0 +1,81 @@ +# REQUIRES: lld, target-windows + +# Test where LLDB looks for PDBs. +# RUN: split-file %s %t + +# RUN: mkdir -p %t/build +# RUN: mkdir -p %t/dir1 +# RUN: mkdir -p %t/dir2 +# RUN: mkdir -p %t/dir3 + +# RUN: echo "settings append target.debug-file-search-paths %t/dir2" >> %t/init.input +# RUN: echo "settings append target.debug-file-search-paths %t/dir3" >> %t/init.input + +# RUN: %build --compiler=clang-cl --nodefaultlib --output=%t/build/a.exe %t/main.cpp + +# Regular setup - PDB is at the original path +# RUN: %lldb -S %t/init.input -s %t/check.input %t/build/a.exe > %t.out.txt +# CHECK: (lldb) target create +# CHECK-NEXT: Loading {{.*[/\\]}}build{{[/\\]}}a.pdb for {{.*[/\\]}}build{{[/\\]}}a.exe +# CHECK: (A) a = (x = 47) + +# Move the executable to a different directory but keep the PDB. +# RUN: mv %t/build/a.exe %t/dir1 +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe >> %t.out.txt +# CHECK: (lldb) target create +# CHECK-NEXT: Loading {{.*[/\\]}}build{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe +# CHECK: (A) a = (x = 47) + +# Copy the PDB to the same directory and all search dirs. LLDB should prefer the original PDB. +# RUN: cp %t/build/a.pdb %t/dir1 +# RUN: cp %t/build/a.pdb %t/dir2 +# RUN: cp %t/build/a.pdb %t/dir3 +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe >> %t.out.txt +# CHECK: (lldb) target create +# CHECK-NEXT: Loading {{.*[/\\]}}build{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe +# CHECK: (A) a = (x = 47) + +# Remove the original PDB. LLDB should now use the one next to the exe. +# RUN: rm %t/build/a.pdb +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe >> %t.out.txt +# CHECK: (lldb) target create +# CHECK-NEXT: Loading {{.*[/\\]}}dir1{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe +# CHECK: (A) a = (x = 47) + +# Remove the PDB next to the exe. LLDB should now use the one in dir2 (first in list). +# RUN: rm %t/dir1/a.pdb +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe >> %t.out.txt +# CHECK: (lldb) target create +# CHECK-NEXT: Loading {{.*[/\\]}}dir2{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe +# CHECK: (A) a = (x = 47) + +# Remove the PDB in dir2. LLDB should now use the one in dir3 (second in list). +# RUN: rm %t/dir2/a.pdb +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe >> %t.out.txt +# CHECK: (lldb) target create +# CHECK-NEXT: Loading {{.*[/\\]}}dir3{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe +# CHECK: (A) a = (x = 47) + +# RUN: cat %t.out.txt | FileCheck %s + +# Remove the last PDB in dir3. Now, there's no matching PDB anymore. +# RUN: rm %t/dir3/a.pdb +# RUN: %lldb -S %t/init.input -s %t/check.input -f %t/dir1/a.exe 2>&1 | FileCheck --check-prefix=NOPDB %s +# NOPDB: error: can't find global variable 'a' + +#--- main.cpp + +struct A { + int x = 47; +}; +A a; +int main() {} + +#--- init.input + +log enable lldb symbol + +#--- check.input + +target variable a +q From bbb689888a74d8e3e625e9ed229f7ab16e933f18 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Mon, 1 Dec 2025 16:39:02 +0100 Subject: [PATCH 2/2] fix: use individual check prefixes --- .../NativePDB/find-pdb-next-to-exe.test | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test b/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test index 9ca850b1fd5b6..c35c82ad84d2f 100644 --- a/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test +++ b/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test @@ -14,49 +14,44 @@ # RUN: %build --compiler=clang-cl --nodefaultlib --output=%t/build/a.exe %t/main.cpp # Regular setup - PDB is at the original path -# RUN: %lldb -S %t/init.input -s %t/check.input %t/build/a.exe > %t.out.txt -# CHECK: (lldb) target create -# CHECK-NEXT: Loading {{.*[/\\]}}build{{[/\\]}}a.pdb for {{.*[/\\]}}build{{[/\\]}}a.exe -# CHECK: (A) a = (x = 47) +# RUN: %lldb -S %t/init.input -s %t/check.input %t/build/a.exe | FileCheck --check-prefix=BOTH-ORIG %s +# BOTH-ORIG: (lldb) target create +# BOTH-ORIG-NEXT: Loading {{.*[/\\]}}build{{[/\\]}}a.pdb for {{.*[/\\]}}build{{[/\\]}}a.exe +# BOTH-ORIG: (A) a = (x = 47) # Move the executable to a different directory but keep the PDB. # RUN: mv %t/build/a.exe %t/dir1 -# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe >> %t.out.txt -# CHECK: (lldb) target create -# CHECK-NEXT: Loading {{.*[/\\]}}build{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe -# CHECK: (A) a = (x = 47) +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=PDB-ORIG %s +# PDB-ORIG: (lldb) target create +# PDB-ORIG-NEXT: Loading {{.*[/\\]}}build{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe +# PDB-ORIG: (A) a = (x = 47) # Copy the PDB to the same directory and all search dirs. LLDB should prefer the original PDB. # RUN: cp %t/build/a.pdb %t/dir1 # RUN: cp %t/build/a.pdb %t/dir2 # RUN: cp %t/build/a.pdb %t/dir3 -# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe >> %t.out.txt -# CHECK: (lldb) target create -# CHECK-NEXT: Loading {{.*[/\\]}}build{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe -# CHECK: (A) a = (x = 47) +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=PDB-ORIG %s # Remove the original PDB. LLDB should now use the one next to the exe. # RUN: rm %t/build/a.pdb -# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe >> %t.out.txt -# CHECK: (lldb) target create -# CHECK-NEXT: Loading {{.*[/\\]}}dir1{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe -# CHECK: (A) a = (x = 47) +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=NEXT-TO-EXE %s +# NEXT-TO-EXE: (lldb) target create +# NEXT-TO-EXE-NEXT: Loading {{.*[/\\]}}dir1{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe +# NEXT-TO-EXE: (A) a = (x = 47) # Remove the PDB next to the exe. LLDB should now use the one in dir2 (first in list). # RUN: rm %t/dir1/a.pdb -# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe >> %t.out.txt -# CHECK: (lldb) target create -# CHECK-NEXT: Loading {{.*[/\\]}}dir2{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe -# CHECK: (A) a = (x = 47) +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=DIR2 %s +# DIR2: (lldb) target create +# DIR2-NEXT: Loading {{.*[/\\]}}dir2{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe +# DIR2: (A) a = (x = 47) # Remove the PDB in dir2. LLDB should now use the one in dir3 (second in list). # RUN: rm %t/dir2/a.pdb -# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe >> %t.out.txt -# CHECK: (lldb) target create -# CHECK-NEXT: Loading {{.*[/\\]}}dir3{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe -# CHECK: (A) a = (x = 47) - -# RUN: cat %t.out.txt | FileCheck %s +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=DIR3 %s +# DIR3: (lldb) target create +# DIR3-NEXT: Loading {{.*[/\\]}}dir3{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe +# DIR3: (A) a = (x = 47) # Remove the last PDB in dir3. Now, there's no matching PDB anymore. # RUN: rm %t/dir3/a.pdb