[llvm] [llvm-objdump] Add --substitute-path and --source-dir for --source (PR #201096)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 2 04:21:39 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-binary-utilities
Author: VladimirMedic
<details>
<summary>Changes</summary>
Add GDB-style options so llvm-objdump --source can find source files when debug-info paths do not match the local filesystem:
--substitute-path <from> <to> — Replace from with to at the start of the directory part of a source path. Rules are tried in order; the first match wins (same as GDB set substitute-path). A rule applies only if from ends at a directory separator.
--source-dir <dir> — Add directories to the source search path. For each file, try the debug-info path, then each search dir + relative path, then each search dir + basename. Repeatable; later flags are searched first.
Path lookup normalizes separators and ./.. components for matching and fs::exists, but leaves paths unchanged in warnings when no rule matches. Uses sys::path::relative_path when combining search dirs with absolute debug paths so a drive/root prefix does not discard the search directory.
Documented in llvm-objdump.rst. Tests in llvm/test/tools/llvm-objdump/source-path.test.
Test plan
llvm-lit llvm/test/tools/llvm-objdump/source-path.test
Manual: llvm-objdump --source --substitute-path /build/tree /src/tree obj
Manual: llvm-objdump --source --source-dir /src/tree obj
---
Full diff: https://github.com/llvm/llvm-project/pull/201096.diff
8 Files Affected:
- (modified) llvm/docs/CommandGuide/llvm-objdump.rst (+23)
- (added) llvm/test/tools/llvm-objdump/Inputs/source-path.c (+9)
- (added) llvm/test/tools/llvm-objdump/Inputs/source-path.ll (+65)
- (added) llvm/test/tools/llvm-objdump/source-path.test (+73)
- (modified) llvm/tools/llvm-objdump/ObjdumpOpts.td (+14)
- (modified) llvm/tools/llvm-objdump/SourcePrinter.cpp (+109-2)
- (modified) llvm/tools/llvm-objdump/llvm-objdump.cpp (+13)
- (modified) llvm/tools/llvm-objdump/llvm-objdump.h (+4)
``````````diff
diff --git a/llvm/docs/CommandGuide/llvm-objdump.rst b/llvm/docs/CommandGuide/llvm-objdump.rst
index d7e2ed0015950..f3e280a8987dc 100644
--- a/llvm/docs/CommandGuide/llvm-objdump.rst
+++ b/llvm/docs/CommandGuide/llvm-objdump.rst
@@ -238,6 +238,29 @@ OPTIONS
initial directories from absolute paths. This option has no effect without
:option:`--prefix`.
+.. option:: --substitute-path <from> <to>
+
+ When disassembling with the :option:`--source` option, replace ``from`` with
+ ``to`` at the start of the directory part of source file paths when looking up
+ sources. A rule is applied only if ``from`` ends at a directory separator in
+ the path. This option may be specified multiple times; rules are evaluated in
+ the order given and the first matching rule is used, as in GDB
+ ``set substitute-path``.
+
+.. option:: --source-dir=<dir>
+
+.. option:: -source-dir <dir>
+
+ When disassembling with the :option:`--source` option, add ``dir`` to the
+ front of the source search path when looking up source files. For each source
+ file, llvm-objdump tries the path from the debug info, then each search
+ directory with the full path appended, then each search directory with only
+ the file name.
+
+ Multiple directories may be given in one argument separated by ``:`` (``;``
+ on Windows). This option may be specified multiple times; each directory is
+ appended to the search path in the order given.
+
.. option:: --print-imm-hex
Use hex format when printing immediate values in disassembly output (default).
diff --git a/llvm/test/tools/llvm-objdump/Inputs/source-path.c b/llvm/test/tools/llvm-objdump/Inputs/source-path.c
new file mode 100644
index 0000000000000..d5ef46ff62673
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/Inputs/source-path.c
@@ -0,0 +1,9 @@
+int a = 1;
+int foo() {
+ return a;
+}
+
+int main() {
+ int *b = &a;
+ return *b + foo();
+}
diff --git a/llvm/test/tools/llvm-objdump/Inputs/source-path.ll b/llvm/test/tools/llvm-objdump/Inputs/source-path.ll
new file mode 100644
index 0000000000000..a202dda2d5f6e
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/Inputs/source-path.ll
@@ -0,0 +1,65 @@
+; NOTE: To use this file as input, replace SRC_COMPDIR with a directory path via sed.
+
+; ModuleID = 'source-path.bc'
+source_filename = "source-path.c"
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+ at a = global i32 1, align 4, !dbg !4
+
+; Function Attrs: nounwind uwtable
+define i32 @foo() #0 !dbg !9 {
+entry:
+ %0 = load i32, ptr @a, align 4, !dbg !12
+ ret i32 %0, !dbg !13
+}
+
+; Function Attrs: nounwind uwtable
+define i32 @main() #0 !dbg !14 {
+entry:
+ %retval = alloca i32, align 4
+ %b = alloca ptr, align 8
+ store i32 0, ptr %retval, align 4
+ call void @llvm.dbg.declare(metadata ptr %b, metadata !15, metadata !17), !dbg !18
+ store ptr @a, ptr %b, align 8, !dbg !18
+ %0 = load ptr, ptr %b, align 8, !dbg !19
+ %1 = load i32, ptr %0, align 4, !dbg !20
+ %call = call i32 @foo(), !dbg !21
+ %add = add nsw i32 %1, %call, !dbg !22
+ ret i32 %add, !dbg !23
+}
+
+; Function Attrs: nounwind readnone
+declare void @llvm.dbg.declare(metadata, metadata, metadata) #1
+
+attributes #0 = { nounwind uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "frame-pointer"="all" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { nounwind readnone }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!6, !7}
+!llvm.ident = !{!8}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 4.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, globals: !3)
+!1 = !DIFile(filename: "source-path.c", directory: "SRC_COMPDIR")
+!2 = !{}
+!3 = !{!4}
+!4 = !DIGlobalVariableExpression(var: !DIGlobalVariable(name: "a", scope: !0, file: !1, line: 1, type: !5, isLocal: false, isDefinition: true), expr: !DIExpression())
+!5 = !DIBasicType(name: "int", size: 32, align: 32, encoding: DW_ATE_signed)
+!6 = !{i32 2, !"Dwarf Version", i32 4}
+!7 = !{i32 2, !"Debug Info Version", i32 3}
+!8 = !{!"clang version 4.0.0"}
+!9 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 2, type: !10, isLocal: false, isDefinition: true, scopeLine: 2, isOptimized: false, unit: !0, retainedNodes: !2)
+!10 = !DISubroutineType(types: !11)
+!11 = !{!5}
+!12 = !DILocation(line: 3, column: 10, scope: !9)
+!13 = !DILocation(line: 3, column: 3, scope: !9)
+!14 = distinct !DISubprogram(name: "main", scope: !1, file: !1, line: 6, type: !10, isLocal: false, isDefinition: true, scopeLine: 6, isOptimized: false, unit: !0, retainedNodes: !2)
+!15 = !DILocalVariable(name: "b", scope: !14, file: !1, line: 7, type: !16)
+!16 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !5, size: 64, align: 64)
+!17 = !DIExpression()
+!18 = !DILocation(line: 7, column: 8, scope: !14)
+!19 = !DILocation(line: 8, column: 11, scope: !14)
+!20 = !DILocation(line: 8, column: 10, scope: !14)
+!21 = !DILocation(line: 8, column: 15, scope: !14)
+!22 = !DILocation(line: 8, column: 13, scope: !14)
+!23 = !DILocation(line: 8, column: 3, scope: !14)
diff --git a/llvm/test/tools/llvm-objdump/source-path.test b/llvm/test/tools/llvm-objdump/source-path.test
new file mode 100644
index 0000000000000..676da131c69fa
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/source-path.test
@@ -0,0 +1,73 @@
+;; Test --substitute-path and --source-dir with --source and --line-numbers.
+;;
+;; The object records source paths under /wrong/Inputs, but the source file
+;; lives in this test's Inputs directory. Without path remapping, --source
+;; cannot load the file from disk.
+;;
+;; --source-dir: each flag is prepended to the search list, so later flags on
+;; the command line are tried first.
+;;
+;; --substitute-path: rules are tried in command-line order; the first match
+;; wins (same as GDB set substitute-path).
+
+; RUN: sed -e "s,SRC_COMPDIR,/wrong/Inputs,g" %p/Inputs/source-path.ll > %t.ll
+; RUN: llc -o %t.o -filetype=obj -mtriple=x86_64-pc-linux %t.ll
+; REQUIRES: x86-registered-target
+
+; RUN: llvm-objdump --no-print-imm-hex -d --line-numbers %t.o | FileCheck %s --check-prefix=LINE
+; RUN: llvm-objdump --no-print-imm-hex -d --source %t.o 2>&1 | \
+; RUN: FileCheck %s --check-prefix=SOURCE-MISSING -DFILE=%t.o
+
+; RUN: llvm-objdump --no-print-imm-hex -d --source --substitute-path /wrong %/p %t.o | \
+; RUN: FileCheck %s --check-prefix=SOURCE
+; RUN: llvm-objdump --no-print-imm-hex -d --source --source-dir %/p/Inputs %t.o | \
+; RUN: FileCheck %s --check-prefix=SOURCE
+
+; RUN: llvm-objdump --no-print-imm-hex -d --source --substitute-path /wron %/p %t.o 2>&1 | \
+; RUN: FileCheck %s --check-prefix=SOURCE-MISSING -DFILE=%t.o
+
+; First --source-dir misses (no source-path.c under %/p alone); second finds
+; Inputs/source-path.c via basename lookup.
+; RUN: llvm-objdump --no-print-imm-hex -d --source \
+; RUN: --source-dir %/p/Inputs --source-dir %/p %t.o | \
+; RUN: FileCheck %s --check-prefix=SOURCE
+
+; More specific rule listed first wins (GDB example: /wrong/Inputs before /wrong).
+; RUN: llvm-objdump --no-print-imm-hex -d --source \
+; RUN: --substitute-path /wrong/Inputs %/p/Inputs \
+; RUN: --substitute-path /wrong /nowhere %t.o | \
+; RUN: FileCheck %s --check-prefix=SOURCE
+
+; Broader rule listed first matches; later rule is not applied (miss on remapped path).
+; RUN: llvm-objdump --no-print-imm-hex -d --source \
+; RUN: --substitute-path /wrong /nowhere \
+; RUN: --substitute-path /wrong/Inputs %/p/Inputs %t.o 2>&1 | \
+; RUN: FileCheck %s --check-prefix=SOURCE-MISSING-SUBST -DFILE=%t.o
+
+; First substitute rule is a no-match; second matching rule remaps the path.
+; RUN: llvm-objdump --no-print-imm-hex -d --source \
+; RUN: --substitute-path /wron /nowhere \
+; RUN: --substitute-path /wrong %/p %t.o | \
+; RUN: FileCheck %s --check-prefix=SOURCE
+; LINE: <foo>:
+; LINE-NEXT: ; foo():
+; LINE-NEXT: ; {{.*}}source-path.c:2
+; LINE-NEXT: pushq
+; LINE: ; {{.*}}source-path.c:3
+; LINE-NEXT: movq
+; LINE-NEXT: movl
+; LINE-NEXT: popq
+; LINE-NEXT: retq
+
+; SOURCE-MISSING: warning: '[[FILE]]': failed to find source {{[/\\]}}wrong{{[/\\]}}Inputs{{[/\\]}}source-path.c
+
+; SOURCE-MISSING-SUBST: warning: '[[FILE]]': failed to find source {{[/\\]}}nowhere{{[/\\]}}Inputs{{[/\\]}}source-path.c
+
+; SOURCE: <foo>:
+; SOURCE-NEXT: ; int foo() {
+; SOURCE-NEXT: pushq
+; SOURCE: ; return a;
+; SOURCE-NEXT: movq
+; SOURCE-NEXT: movl
+; SOURCE-NEXT: popq
+; SOURCE-NEXT: retq
diff --git a/llvm/tools/llvm-objdump/ObjdumpOpts.td b/llvm/tools/llvm-objdump/ObjdumpOpts.td
index 73f4c48abb19f..3541d9a71dec5 100644
--- a/llvm/tools/llvm-objdump/ObjdumpOpts.td
+++ b/llvm/tools/llvm-objdump/ObjdumpOpts.td
@@ -246,6 +246,20 @@ def : Flag<["-"], "w">, Alias<wide>;
defm prefix : Eq<"prefix", "Add prefix to absolute paths">,
MetaVarName<"prefix">;
+defm source_dir :
+ Eq<"source-dir",
+ "Add a directory to the front of the source search path when looking up "
+ "source files. Multiple directories may be given in one argument "
+ "separated by ':' (';' on Windows)">,
+ MetaVarName<"<dir>">;
+def : Separate<["-"], "source-dir">, Alias<source_dir>;
+
+def substitute_path : MultiArg<["--"], "substitute-path", 2>,
+ MetaVarName<"<from> <to>">,
+ HelpText<"Replace <from> with <to> at the start of the directory part of "
+ "source file paths when looking up sources. The rule is applied "
+ "only if <from> ends at a directory separator in the path. May be "
+ "specified multiple times.">;
defm prefix_strip
: Eq<"prefix-strip", "Strip out initial directories from absolute "
"paths. No effect without --prefix">,
diff --git a/llvm/tools/llvm-objdump/SourcePrinter.cpp b/llvm/tools/llvm-objdump/SourcePrinter.cpp
index a92b955a49cde..a212b495162c0 100644
--- a/llvm/tools/llvm-objdump/SourcePrinter.cpp
+++ b/llvm/tools/llvm-objdump/SourcePrinter.cpp
@@ -15,16 +15,113 @@
#include "SourcePrinter.h"
#include "llvm-objdump.h"
#include "llvm/ADT/SmallSet.h"
+#include "llvm/ADT/SmallString.h"
#include "llvm/DebugInfo/DWARF/DWARFExpressionPrinter.h"
#include "llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h"
#include "llvm/Demangle/Demangle.h"
+#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/Path.h"
#define DEBUG_TYPE "objdump"
namespace llvm {
namespace objdump {
+static bool sourceFileExists(StringRef Path) {
+ if (sys::fs::exists(Path) && !sys::fs::is_directory(Path))
+ return true;
+
+ return false;
+}
+
+static void normalizeSourcePath(SmallVectorImpl<char> &Path) {
+ sys::path::native(Path);
+ sys::path::remove_dots(Path, /*remove_dot_dot=*/true);
+}
+
+static std::optional<std::string> trySourcePath(StringRef Path) {
+ SmallString<256> Normalized(Path);
+ normalizeSourcePath(Normalized);
+ if (sourceFileExists(Normalized))
+ return std::string(Normalized);
+ return std::nullopt;
+}
+
+static std::optional<std::string>
+searchSourceWithDirs(StringRef FileName, ArrayRef<StringRef> SearchDirs,
+ bool TryLiteralFirst) {
+ if (TryLiteralFirst)
+ if (auto Path = trySourcePath(FileName))
+ return Path;
+
+ StringRef PathSuffix = sys::path::relative_path(FileName);
+ StringRef BaseName = sys::path::filename(FileName);
+ for (StringRef Dir : SearchDirs) {
+ SmallString<256> Candidate(Dir);
+ sys::path::append(Candidate, PathSuffix);
+ if (auto Path = trySourcePath(Candidate))
+ return Path;
+ }
+ for (StringRef Dir : SearchDirs) {
+ SmallString<256> Candidate(Dir);
+ sys::path::append(Candidate, BaseName);
+ if (auto Path = trySourcePath(Candidate))
+ return Path;
+ }
+ return std::nullopt;
+}
+
+static std::optional<std::string>
+findSourceFilePath(StringRef FileName, ArrayRef<StringRef> SearchDirs) {
+ if (FileName.empty() || FileName == DILineInfo::BadString)
+ return std::nullopt;
+
+ if (sys::path::is_absolute_gnu(FileName))
+ return searchSourceWithDirs(FileName, SearchDirs, /*TryLiteralFirst=*/true);
+ return searchSourceWithDirs(FileName, SearchDirs, /*TryLiteralFirst=*/false);
+}
+
+static std::string applySubstitutePaths(StringRef FileName) {
+ if (SubstitutePaths.empty())
+ return FileName.str();
+
+ StringRef BaseName = sys::path::filename(FileName);
+ SmallString<256> Directory(sys::path::parent_path(FileName));
+ normalizeSourcePath(Directory);
+
+ for (const auto &[From, To] : SubstitutePaths) {
+ if (From.empty())
+ continue;
+
+ SmallString<256> FromPath(From);
+ normalizeSourcePath(FromPath);
+ StringRef Dir = Directory;
+ if (!Dir.starts_with(FromPath))
+ continue;
+ if (Dir.size() > FromPath.size() &&
+ !sys::path::is_separator(Dir[FromPath.size()]))
+ continue;
+
+ SmallString<256> NewDir(To);
+ StringRef Suffix = Dir.substr(FromPath.size());
+ while (!Suffix.empty() && sys::path::is_separator(Suffix.front()))
+ Suffix = Suffix.drop_front();
+ if (!Suffix.empty())
+ sys::path::append(NewDir, Suffix);
+ normalizeSourcePath(NewDir);
+
+ if (NewDir.empty())
+ return BaseName.str();
+ SmallString<256> Result(NewDir);
+ sys::path::append(Result, BaseName);
+ normalizeSourcePath(Result);
+ return std::string(Result);
+ }
+
+ return FileName.str();
+}
+
bool InlinedFunction::liveAtAddress(object::SectionedAddress Addr) const {
if (!Range.valid())
return false;
@@ -608,8 +705,16 @@ bool SourcePrinter::cacheSource(const DILineInfo &LineInfo) {
if (LineInfo.Source) {
Buffer = MemoryBuffer::getMemBuffer(*LineInfo.Source);
} else {
- auto BufferOrError =
- MemoryBuffer::getFile(LineInfo.FileName, /*IsText=*/true);
+ std::string PathToOpen = LineInfo.FileName;
+ if (!SourceDirs.empty()) {
+ SmallVector<StringRef, 8> SearchDirs;
+ for (const std::string &Dir : SourceDirs)
+ SearchDirs.push_back(Dir);
+ if (auto Resolved = findSourceFilePath(LineInfo.FileName, SearchDirs))
+ PathToOpen = std::move(*Resolved);
+ }
+
+ auto BufferOrError = MemoryBuffer::getFile(PathToOpen, /*IsText=*/true);
if (!BufferOrError) {
if (MissingSources.insert(LineInfo.FileName).second)
reportWarning("failed to find source " + LineInfo.FileName,
@@ -654,6 +759,8 @@ void SourcePrinter::printSourceLine(formatted_raw_ostream &OS,
toString(ExpectedLineInfo.takeError()),
ObjectFilename);
}
+ if (!objdump::SubstitutePaths.empty())
+ LineInfo.FileName = applySubstitutePaths(LineInfo.FileName);
if (!objdump::Prefix.empty() &&
sys::path::is_absolute_gnu(LineInfo.FileName)) {
diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index 1da9b5771fd3f..1311bf9625fd2 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -344,6 +344,8 @@ bool objdump::UnwindInfo;
bool objdump::UnwindShowWODPool;
std::string objdump::Prefix;
uint32_t objdump::PrefixStrip;
+std::vector<std::pair<std::string, std::string>> objdump::SubstitutePaths;
+std::vector<std::string> objdump::SourceDirs;
DebugFormat objdump::DbgVariables = DFDisabled;
DebugFormat objdump::DbgInlinedFunctions = DFDisabled;
@@ -3904,6 +3906,17 @@ static void parseObjdumpOptions(const llvm::opt::InputArgList &InputArgs) {
UnwindShowWODPool = InputArgs.hasArg(OBJDUMP_unwind_show_wod_pool);
Prefix = InputArgs.getLastArgValue(OBJDUMP_prefix).str();
parseIntArg(InputArgs, OBJDUMP_prefix_strip, PrefixStrip);
+ for (const opt::Arg *A : InputArgs.filtered(OBJDUMP_substitute_path))
+ SubstitutePaths.emplace_back(A->getValue(0), A->getValue(1));
+ for (StringRef Dirs : InputArgs.getAllArgValues(OBJDUMP_source_dir)) {
+#if defined(_WIN32)
+ for (StringRef Dir : llvm::split(Dirs, ";"))
+#else
+ for (StringRef Dir : llvm::split(Dirs, ":"))
+#endif
+ if (!Dir.empty())
+ SourceDirs.insert(SourceDirs.end(), Dir.str());
+ }
if (const opt::Arg *A = InputArgs.getLastArg(OBJDUMP_debug_vars_EQ)) {
DbgVariables = StringSwitch<DebugFormat>(A->getValue())
.Case("ascii", DFASCII)
diff --git a/llvm/tools/llvm-objdump/llvm-objdump.h b/llvm/tools/llvm-objdump/llvm-objdump.h
index 208bac8be3b3f..2241e64a6d81b 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.h
+++ b/llvm/tools/llvm-objdump/llvm-objdump.h
@@ -18,6 +18,8 @@
#include "llvm/Support/FormattedStream.h"
#include <functional>
#include <memory>
+#include <utility>
+#include <vector>
namespace llvm {
class StringRef;
@@ -66,6 +68,8 @@ extern std::vector<std::string> MAttrs;
extern std::string MCPU;
extern std::string Prefix;
extern uint32_t PrefixStrip;
+extern std::vector<std::pair<std::string, std::string>> SubstitutePaths;
+extern std::vector<std::string> SourceDirs;
extern bool PrintImmHex;
extern bool PrintLines;
extern bool PrintSource;
``````````
</details>
https://github.com/llvm/llvm-project/pull/201096
More information about the llvm-commits
mailing list