[llvm] [symbolizer] Add a --pdb option. (PR #171053)

Yuxuan Shui via llvm-commits llvm-commits at lists.llvm.org
Sun Dec 7 12:04:18 PST 2025


https://github.com/yshui updated https://github.com/llvm/llvm-project/pull/171053

>From e9daea126d1f8d774bb0801fe62d7633b9123a3f Mon Sep 17 00:00:00 2001
From: Yuxuan Shui <yshuiv7 at gmail.com>
Date: Sun, 7 Dec 2025 19:50:12 +0000
Subject: [PATCH] [symbolizer] Add a --pdb option.

Closes #142490
---
 llvm/docs/CommandGuide/llvm-symbolizer.rst    |  5 +
 .../llvm/DebugInfo/Symbolize/Symbolize.h      |  1 +
 llvm/lib/DebugInfo/Symbolize/Symbolize.cpp    | 94 +++++++++++--------
 llvm/test/tools/llvm-symbolizer/pdb/pdb.test  |  7 ++
 llvm/tools/llvm-symbolizer/Opts.td            |  1 +
 .../tools/llvm-symbolizer/llvm-symbolizer.cpp |  1 +
 6 files changed, 69 insertions(+), 40 deletions(-)

diff --git a/llvm/docs/CommandGuide/llvm-symbolizer.rst b/llvm/docs/CommandGuide/llvm-symbolizer.rst
index fb86a694f5d3c..409ad43da127a 100644
--- a/llvm/docs/CommandGuide/llvm-symbolizer.rst
+++ b/llvm/docs/CommandGuide/llvm-symbolizer.rst
@@ -442,6 +442,11 @@ OPTIONS
       }
     ]
 
+.. option:: --pdb <path>
+
+  Use the specified PDB file at ``<path>``, overriding the PDB info
+  contained in the COFF object.
+
 .. option:: --pretty-print, -p
 
   Print human-readable output. If :option:`--inlining` is specified, the
diff --git a/llvm/include/llvm/DebugInfo/Symbolize/Symbolize.h b/llvm/include/llvm/DebugInfo/Symbolize/Symbolize.h
index fb8f3d8af6b1b..230c57a392f0f 100644
--- a/llvm/include/llvm/DebugInfo/Symbolize/Symbolize.h
+++ b/llvm/include/llvm/DebugInfo/Symbolize/Symbolize.h
@@ -64,6 +64,7 @@ class LLVMSymbolizer {
     std::vector<std::string> DsymHints;
     std::string FallbackDebugPath;
     std::string DWPName;
+    std::string PDBName;
     std::vector<std::string> DebugFileDirectory;
     std::vector<std::string> GsymFileDirectory;
     size_t MaxCacheSize =
diff --git a/llvm/lib/DebugInfo/Symbolize/Symbolize.cpp b/llvm/lib/DebugInfo/Symbolize/Symbolize.cpp
index 56527719da51f..5ffdb94df9c01 100644
--- a/llvm/lib/DebugInfo/Symbolize/Symbolize.cpp
+++ b/llvm/lib/DebugInfo/Symbolize/Symbolize.cpp
@@ -662,50 +662,64 @@ LLVMSymbolizer::getOrCreateModuleInfo(StringRef ModuleName) {
   ObjectPair Objects = ObjectsOrErr.get();
 
   std::unique_ptr<DIContext> Context;
-  // If this is a COFF object containing PDB info and not containing DWARF
-  // section, use a PDBContext to symbolize. Otherwise, use DWARF.
-  // Create a DIContext to symbolize as follows:
-  // - If there is a GSYM file, create a GsymContext.
-  // - Otherwise, if this is a COFF object containing PDB info, create a
-  // PDBContext.
-  // - Otherwise, create a DWARFContext.
-  const auto GsymFile = lookUpGsymFile(BinaryName.str());
-  if (!GsymFile.empty()) {
-    auto ReaderOrErr = gsym::GsymReader::openFile(GsymFile);
-
-    if (ReaderOrErr) {
-      std::unique_ptr<gsym::GsymReader> Reader =
-          std::make_unique<gsym::GsymReader>(std::move(*ReaderOrErr));
-
-      Context = std::make_unique<gsym::GsymContext>(std::move(Reader));
+  pdb::PDB_ReaderType ReaderType =
+      Opts.UseDIA ? pdb::PDB_ReaderType::DIA : pdb::PDB_ReaderType::Native;
+  const auto *CoffObject = dyn_cast<COFFObjectFile>(Objects.first);
+
+  // First, if the user specified a pdb file on the command line, use that.
+  if (CoffObject && !Opts.PDBName.empty()) {
+    using namespace pdb;
+    std::unique_ptr<IPDBSession> Session;
+    if (auto Err = loadDataForPDB(ReaderType, Opts.PDBName, Session)) {
+      Modules.emplace(ModuleName, std::unique_ptr<SymbolizableModule>());
+      return createFileError(Opts.PDBName, std::move(Err));
     }
+    Context.reset(new PDBContext(*CoffObject, std::move(Session)));
   }
+
   if (!Context) {
-    if (auto CoffObject = dyn_cast<COFFObjectFile>(Objects.first)) {
-      const codeview::DebugInfo *DebugInfo;
-      StringRef PDBFileName;
-      auto EC = CoffObject->getDebugPDBInfo(DebugInfo, PDBFileName);
-      // Use DWARF if there're DWARF sections.
-      bool HasDwarf = llvm::any_of(
-          Objects.first->sections(), [](SectionRef Section) -> bool {
-            if (Expected<StringRef> SectionName = Section.getName())
-              return SectionName.get() == ".debug_info";
-            return false;
-          });
-      if (!EC && !HasDwarf && DebugInfo != nullptr && !PDBFileName.empty()) {
-        using namespace pdb;
-        std::unique_ptr<IPDBSession> Session;
-
-        PDB_ReaderType ReaderType =
-            Opts.UseDIA ? PDB_ReaderType::DIA : PDB_ReaderType::Native;
-        if (auto Err = loadDataForEXE(ReaderType, Objects.first->getFileName(),
-                                      Session)) {
-          Modules.emplace(ModuleName, std::unique_ptr<SymbolizableModule>());
-          // Return along the PDB filename to provide more context
-          return createFileError(PDBFileName, std::move(Err));
-        }
-        Context.reset(new PDBContext(*CoffObject, std::move(Session)));
+    // If this is a COFF object containing PDB info and not containing DWARF
+    // section, use a PDBContext to symbolize. Otherwise, use DWARF.
+    // Create a DIContext to symbolize as follows:
+    // - If there is a GSYM file, create a GsymContext.
+    // - Otherwise, if this is a COFF object containing PDB info, create a
+    // PDBContext.
+    // - Otherwise, create a DWARFContext.
+    const auto GsymFile = lookUpGsymFile(BinaryName.str());
+    if (!GsymFile.empty()) {
+      auto ReaderOrErr = gsym::GsymReader::openFile(GsymFile);
+
+      if (ReaderOrErr) {
+        std::unique_ptr<gsym::GsymReader> Reader =
+            std::make_unique<gsym::GsymReader>(std::move(*ReaderOrErr));
+
+        Context = std::make_unique<gsym::GsymContext>(std::move(Reader));
+      }
+    }
+  }
+
+  if (!Context && CoffObject) {
+    const codeview::DebugInfo *DebugInfo;
+    StringRef PDBFileName;
+    auto EC = CoffObject->getDebugPDBInfo(DebugInfo, PDBFileName);
+    // Use DWARF if there're DWARF sections.
+    bool HasDwarf =
+        llvm::any_of(Objects.first->sections(), [](SectionRef Section) -> bool {
+          if (Expected<StringRef> SectionName = Section.getName())
+            return SectionName.get() == ".debug_info";
+          return false;
+        });
+    if (!EC && !HasDwarf && DebugInfo != nullptr && !PDBFileName.empty()) {
+      using namespace pdb;
+      std::unique_ptr<IPDBSession> Session;
+
+      if (auto Err = loadDataForEXE(ReaderType, Objects.first->getFileName(),
+                                    Session)) {
+        Modules.emplace(ModuleName, std::unique_ptr<SymbolizableModule>());
+        // Return along the PDB filename to provide more context
+        return createFileError(PDBFileName, std::move(Err));
       }
+      Context.reset(new PDBContext(*CoffObject, std::move(Session)));
     }
   }
   if (!Context)
diff --git a/llvm/test/tools/llvm-symbolizer/pdb/pdb.test b/llvm/test/tools/llvm-symbolizer/pdb/pdb.test
index 46a1ae9814e6d..a9d79cffb7d12 100644
--- a/llvm/test/tools/llvm-symbolizer/pdb/pdb.test
+++ b/llvm/test/tools/llvm-symbolizer/pdb/pdb.test
@@ -1,3 +1,6 @@
+RUN: rm -rf %t
+RUN: mkdir -p %t
+
 RUN: echo 0x401380 > %t.input
 RUN: echo 0x401390 >> %t.input
 RUN: echo 0x4013A0 >> %t.input
@@ -11,6 +14,10 @@ RUN:    | FileCheck %s
 RUN: llvm-symbolizer --obj="%p/Inputs/test.exe" --no-demangle < %t.input \
 RUN:    | FileCheck %s --check-prefix=CHECK-NO-DEMANGLE
 
+RUN: cp %p/Inputs/test.exe %t/test.exe
+RUN: llvm-symbolizer --obj="%t/test.exe" --pdb="%p/Inputs/test.pdb" < %t.input \
+RUN:    | FileCheck %s
+
 ; Check that -dia works
 RUN: llvm-symbolizer --dia --obj="%p/Inputs/test.exe" < %t.input \
 RUN:    | FileCheck %s
diff --git a/llvm/tools/llvm-symbolizer/Opts.td b/llvm/tools/llvm-symbolizer/Opts.td
index 10f1e6dbbddf7..ddbd4471f4452 100644
--- a/llvm/tools/llvm-symbolizer/Opts.td
+++ b/llvm/tools/llvm-symbolizer/Opts.td
@@ -41,6 +41,7 @@ def functions_EQ : Joined<["--"], "functions=">, HelpText<"Print function name f
 defm gsym_file_directory : Eq<"gsym-file-directory", "Path to directory where to look for GSYM files">, MetaVarName<"<dir>">, Group<grp_gsym>;
 def help : F<"help", "Display this help">;
 defm dwp : Eq<"dwp", "Path to DWP file to be use for any split CUs">, MetaVarName<"<file>">;
+defm pdb : Eq<"pdb", "Path to PDB file">, MetaVarName<"<file>">;
 defm dsym_hint
     : Eq<"dsym-hint",
          "Path to .dSYM bundles to search for debug info for the object files">,
diff --git a/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp b/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp
index 4784dafeb2948..1b9fcf35cd4ef 100644
--- a/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp
+++ b/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp
@@ -500,6 +500,7 @@ int llvm_symbolizer_main(int argc, char **argv, const llvm::ToolContext &) {
   Opts.DefaultArch = Args.getLastArgValue(OPT_default_arch_EQ).str();
   Opts.Demangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, !IsAddr2Line);
   Opts.DWPName = Args.getLastArgValue(OPT_dwp_EQ).str();
+  Opts.PDBName = Args.getLastArgValue(OPT_pdb_EQ).str();
   Opts.FallbackDebugPath =
       Args.getLastArgValue(OPT_fallback_debug_path_EQ).str();
   Opts.GsymFileDirectory = Args.getAllArgValues(OPT_gsym_file_directory_EQ);



More information about the llvm-commits mailing list