[llvm] [llvm-objdump] Add --substitute-path and --source-dir for --source (PR #201096)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Jun 12 02:43:15 PDT 2026
https://github.com/VladimirMedic updated https://github.com/llvm/llvm-project/pull/201096
>From fb1adebaec1ac1563566f661c1752355e37a550a Mon Sep 17 00:00:00 2001
From: VladimirMedic <vladimir.medic at gmail.com>
Date: Tue, 2 Jun 2026 12:24:53 +0200
Subject: [PATCH 1/7] [llvm-objdump] Add --substitute-path and --source-dir for
--source
---
llvm/docs/CommandGuide/llvm-objdump.rst | 23 ++++
.../tools/llvm-objdump/Inputs/source-path.c | 9 ++
.../tools/llvm-objdump/Inputs/source-path.ll | 65 ++++++++++
llvm/test/tools/llvm-objdump/source-path.test | 73 ++++++++++++
llvm/tools/llvm-objdump/ObjdumpOpts.td | 14 +++
llvm/tools/llvm-objdump/SourcePrinter.cpp | 111 +++++++++++++++++-
llvm/tools/llvm-objdump/llvm-objdump.cpp | 13 ++
llvm/tools/llvm-objdump/llvm-objdump.h | 4 +
8 files changed, 310 insertions(+), 2 deletions(-)
create mode 100644 llvm/test/tools/llvm-objdump/Inputs/source-path.c
create mode 100644 llvm/test/tools/llvm-objdump/Inputs/source-path.ll
create mode 100644 llvm/test/tools/llvm-objdump/source-path.test
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;
>From 7838cb729fd56950f84ef5eb729b7558b1057d4c Mon Sep 17 00:00:00 2001
From: VladimirMedic <vladimir.medic at gmail.com>
Date: Fri, 5 Jun 2026 12:23:46 +0200
Subject: [PATCH 2/7] Addressing review comments.
---
llvm/docs/CommandGuide/llvm-objdump.rst | 9 +--
.../tools/llvm-objdump/Inputs/source-path.c | 9 ---
.../tools/llvm-objdump/Inputs/source-path.ll | 65 -----------------
llvm/test/tools/llvm-objdump/source-path.test | 73 -------------------
llvm/tools/llvm-objdump/ObjdumpOpts.td | 12 ++-
llvm/tools/llvm-objdump/SourcePrinter.cpp | 7 +-
llvm/tools/llvm-objdump/llvm-objdump.cpp | 21 +++---
llvm/tools/llvm-objdump/llvm-objdump.h | 2 +-
8 files changed, 21 insertions(+), 177 deletions(-)
delete mode 100644 llvm/test/tools/llvm-objdump/Inputs/source-path.c
delete mode 100644 llvm/test/tools/llvm-objdump/Inputs/source-path.ll
delete mode 100644 llvm/test/tools/llvm-objdump/source-path.test
diff --git a/llvm/docs/CommandGuide/llvm-objdump.rst b/llvm/docs/CommandGuide/llvm-objdump.rst
index f3e280a8987dc..b3069515f8b6a 100644
--- a/llvm/docs/CommandGuide/llvm-objdump.rst
+++ b/llvm/docs/CommandGuide/llvm-objdump.rst
@@ -39,8 +39,6 @@ combined with other commands:
.. option:: --disassemble-symbols=<symbol1[,symbol2,...]>
-.. option:: --disassemble=symbol1 --disassemble=symbol2 ...
-
Disassemble only the specified symbols. Takes demangled symbol names when
:option:`--demangle` is specified, otherwise takes mangled symbol names.
Implies :option:`--disassemble`.
@@ -247,7 +245,7 @@ OPTIONS
the order given and the first matching rule is used, as in GDB
``set substitute-path``.
-.. option:: --source-dir=<dir>
+.. option:: --source-dir <dir>
.. option:: -source-dir <dir>
@@ -257,9 +255,8 @@ OPTIONS
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.
+ This option may be specified multiple times; each ``--source-dir`` adds one
+ directory. Later options on the command line are searched before earlier ones.
.. option:: --print-imm-hex
diff --git a/llvm/test/tools/llvm-objdump/Inputs/source-path.c b/llvm/test/tools/llvm-objdump/Inputs/source-path.c
deleted file mode 100644
index d5ef46ff62673..0000000000000
--- a/llvm/test/tools/llvm-objdump/Inputs/source-path.c
+++ /dev/null
@@ -1,9 +0,0 @@
-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
deleted file mode 100644
index a202dda2d5f6e..0000000000000
--- a/llvm/test/tools/llvm-objdump/Inputs/source-path.ll
+++ /dev/null
@@ -1,65 +0,0 @@
-; 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
deleted file mode 100644
index 676da131c69fa..0000000000000
--- a/llvm/test/tools/llvm-objdump/source-path.test
+++ /dev/null
@@ -1,73 +0,0 @@
-;; 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 3541d9a71dec5..67df89273ff7c 100644
--- a/llvm/tools/llvm-objdump/ObjdumpOpts.td
+++ b/llvm/tools/llvm-objdump/ObjdumpOpts.td
@@ -246,12 +246,10 @@ 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 source_dir : Separate<["--"], "source-dir">,
+ MetaVarName<"<dir>">,
+ HelpText<"Add a directory to the front of the source search path when "
+ "looking up source files. May be specified multiple times.">;
def : Separate<["-"], "source-dir">, Alias<source_dir>;
def substitute_path : MultiArg<["--"], "substitute-path", 2>,
@@ -259,7 +257,7 @@ def substitute_path : MultiArg<["--"], "substitute-path", 2>,
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.">;
+ "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 a212b495162c0..0b271d72f7be7 100644
--- a/llvm/tools/llvm-objdump/SourcePrinter.cpp
+++ b/llvm/tools/llvm-objdump/SourcePrinter.cpp
@@ -14,7 +14,6 @@
#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"
@@ -91,9 +90,6 @@ static std::string applySubstitutePaths(StringRef FileName) {
normalizeSourcePath(Directory);
for (const auto &[From, To] : SubstitutePaths) {
- if (From.empty())
- continue;
-
SmallString<256> FromPath(From);
normalizeSourcePath(FromPath);
StringRef Dir = Directory;
@@ -710,7 +706,8 @@ bool SourcePrinter::cacheSource(const DILineInfo &LineInfo) {
SmallVector<StringRef, 8> SearchDirs;
for (const std::string &Dir : SourceDirs)
SearchDirs.push_back(Dir);
- if (auto Resolved = findSourceFilePath(LineInfo.FileName, SearchDirs))
+ if (std::optional<std::string> Resolved =
+ findSourceFilePath(LineInfo.FileName, SearchDirs))
PathToOpen = std::move(*Resolved);
}
diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index 1311bf9625fd2..2272fe6ee2e1b 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -84,7 +84,6 @@
#include <set>
#include <system_error>
#include <unordered_map>
-#include <utility>
using namespace llvm;
using namespace llvm::object;
@@ -3906,17 +3905,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());
+ for (const opt::Arg *A : InputArgs.filtered(OBJDUMP_substitute_path)) {
+ StringRef From = A->getValue(0);
+ StringRef To = A->getValue(1);
+ if (From.empty())
+ reportCmdLineError(A->getSpelling() +
+ ": <from> must not be empty");
+ SubstitutePaths.emplace_back(From.str(), A->getValue(1));
}
+ for (StringRef Dir : InputArgs.getAllArgValues(OBJDUMP_source_dir))
+ if (!Dir.empty())
+ SourceDirs.insert(SourceDirs.begin(), 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 2241e64a6d81b..a68dc29409ac1 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.h
+++ b/llvm/tools/llvm-objdump/llvm-objdump.h
@@ -68,8 +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 std::vector<std::pair<std::string, std::string>> SubstitutePaths;
extern bool PrintImmHex;
extern bool PrintLines;
extern bool PrintSource;
>From 05947b1015b560f75b9304e63e55c4a423d2642f Mon Sep 17 00:00:00 2001
From: VladimirMedic <vladimir.medic at gmail.com>
Date: Fri, 5 Jun 2026 14:35:44 +0200
Subject: [PATCH 3/7] Adding missing test file, fixed dosc and opts.
---
llvm/docs/CommandGuide/llvm-objdump.rst | 4 +-
.../tools/llvm-objdump/X86/source-path.test | 100 ++++++++++++++++++
llvm/tools/llvm-objdump/ObjdumpOpts.td | 2 +-
3 files changed, 103 insertions(+), 3 deletions(-)
create mode 100644 llvm/test/tools/llvm-objdump/X86/source-path.test
diff --git a/llvm/docs/CommandGuide/llvm-objdump.rst b/llvm/docs/CommandGuide/llvm-objdump.rst
index b3069515f8b6a..e7a171210b2a3 100644
--- a/llvm/docs/CommandGuide/llvm-objdump.rst
+++ b/llvm/docs/CommandGuide/llvm-objdump.rst
@@ -39,6 +39,8 @@ combined with other commands:
.. option:: --disassemble-symbols=<symbol1[,symbol2,...]>
+.. option:: --disassemble=symbol1 --disassemble=symbol2 ...
+
Disassemble only the specified symbols. Takes demangled symbol names when
:option:`--demangle` is specified, otherwise takes mangled symbol names.
Implies :option:`--disassemble`.
@@ -245,8 +247,6 @@ OPTIONS
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
diff --git a/llvm/test/tools/llvm-objdump/X86/source-path.test b/llvm/test/tools/llvm-objdump/X86/source-path.test
new file mode 100644
index 0000000000000..e407dc368d3aa
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/X86/source-path.test
@@ -0,0 +1,100 @@
+;; 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: one directory per flag; later flags on the command line are
+;; searched before earlier ones.
+;;
+;; --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-interleave.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 file under %/p alone); second finds
+; Inputs/source-interleave-x86_64.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
+
+; Empty <to> strips the matched prefix (GDB allows this); use --source-dir to
+; resolve the resulting relative path.
+; RUN: echo '--substitute-path' > %t-empty-to.rsp
+; RUN: echo '/wrong' >> %t-empty-to.rsp
+; RUN: echo '""' >> %t-empty-to.rsp
+; RUN: llvm-objdump --no-print-imm-hex -d --source @%t-empty-to.rsp \
+; RUN: --source-dir %/p/Inputs %t.o | FileCheck %s --check-prefix=SOURCE
+
+; Empty <to> without --source-dir: relative path is not resolved; warn once, no crash.
+; RUN: llvm-objdump --no-print-imm-hex -d --source @%t-empty-to.rsp %t.o 2>&1 | \
+; RUN: FileCheck %s --check-prefix=SOURCE-EMPTY-TO -DFILE=%t.o
+
+; Empty <from> is rejected (use a response file so the shell does not drop "").
+; RUN: echo '--substitute-path' > %t-empty-from.rsp
+; RUN: echo '""' >> %t-empty-from.rsp
+; RUN: echo '%/p' >> %t-empty-from.rsp
+; RUN: not llvm-objdump @%t-empty-from.rsp %t.o 2>&1 | \
+; RUN: FileCheck %s --check-prefix=SUBST-ERR-FROM
+
+; SUBST-ERR-FROM: llvm-objdump{{.*}}: error: --substitute-path: <from> must not be empty
+
+; LINE: <foo>:
+; LINE-NEXT: ; foo():
+; LINE-NEXT: ; {{.*}}source-interleave-x86_64.c:2
+; LINE-NEXT: pushq
+; LINE: ; {{.*}}source-interleave-x86_64.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-interleave-x86_64.c
+
+; SOURCE-MISSING-SUBST: warning: '[[FILE]]': failed to find source {{[/\\]}}nowhere{{[/\\]}}Inputs{{[/\\]}}source-interleave-x86_64.c
+
+; SOURCE-EMPTY-TO: <foo>:
+; SOURCE-EMPTY-TO: warning: '[[FILE]]': failed to find source {{.*}}Inputs{{[/\\]}}source-interleave-x86_64.c
+; SOURCE-EMPTY-TO: pushq
+; SOURCE-EMPTY-TO-NOT: ; int foo
+
+; 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 67df89273ff7c..e22f58236ab9a 100644
--- a/llvm/tools/llvm-objdump/ObjdumpOpts.td
+++ b/llvm/tools/llvm-objdump/ObjdumpOpts.td
@@ -249,7 +249,7 @@ defm prefix : Eq<"prefix", "Add prefix to absolute paths">,
def source_dir : Separate<["--"], "source-dir">,
MetaVarName<"<dir>">,
HelpText<"Add a directory to the front of the source search path when "
- "looking up source files. May be specified multiple times.">;
+ "looking up source files. May be specified multiple times">;
def : Separate<["-"], "source-dir">, Alias<source_dir>;
def substitute_path : MultiArg<["--"], "substitute-path", 2>,
>From 779f0d4e965a46b525fff8c3b39694c1a8070fc7 Mon Sep 17 00:00:00 2001
From: VladimirMedic <vladimir.medic at gmail.com>
Date: Fri, 5 Jun 2026 14:42:11 +0200
Subject: [PATCH 4/7] Alphabetical order in options.
---
llvm/docs/CommandGuide/llvm-objdump.rst | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/llvm/docs/CommandGuide/llvm-objdump.rst b/llvm/docs/CommandGuide/llvm-objdump.rst
index e7a171210b2a3..fa68a7718fda6 100644
--- a/llvm/docs/CommandGuide/llvm-objdump.rst
+++ b/llvm/docs/CommandGuide/llvm-objdump.rst
@@ -238,6 +238,14 @@ OPTIONS
initial directories from absolute paths. This option has no effect without
:option:`--prefix`.
+.. 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.
+
.. option:: --substitute-path <from> <to>
When disassembling with the :option:`--source` option, replace ``from`` with
@@ -247,14 +255,6 @@ OPTIONS
the order given and the first matching rule is used, as in GDB
``set substitute-path``.
-.. 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.
-
This option may be specified multiple times; each ``--source-dir`` adds one
directory. Later options on the command line are searched before earlier ones.
>From 009fed9fdea49b6fc8b8e331ecb303f3cff2dbc5 Mon Sep 17 00:00:00 2001
From: VladimirMedic <vladimir.medic at gmail.com>
Date: Fri, 5 Jun 2026 15:15:40 +0200
Subject: [PATCH 5/7] Remove warning, fix documentation.
---
llvm/docs/CommandGuide/llvm-objdump.rst | 7 +++----
llvm/tools/llvm-objdump/llvm-objdump.cpp | 1 -
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/llvm/docs/CommandGuide/llvm-objdump.rst b/llvm/docs/CommandGuide/llvm-objdump.rst
index fa68a7718fda6..e385c257f0203 100644
--- a/llvm/docs/CommandGuide/llvm-objdump.rst
+++ b/llvm/docs/CommandGuide/llvm-objdump.rst
@@ -238,13 +238,15 @@ OPTIONS
initial directories from absolute paths. This option has no effect without
:option:`--prefix`.
-.. 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.
+ This option may be specified multiple times; each ``--source-dir`` adds one
+ directory. Later options on the command line are searched before earlier ones.
.. option:: --substitute-path <from> <to>
@@ -255,9 +257,6 @@ OPTIONS
the order given and the first matching rule is used, as in GDB
``set substitute-path``.
- This option may be specified multiple times; each ``--source-dir`` adds one
- directory. Later options on the command line are searched before earlier ones.
-
.. option:: --print-imm-hex
Use hex format when printing immediate values in disassembly output (default).
diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index 2272fe6ee2e1b..c04b24d6583b2 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -3907,7 +3907,6 @@ static void parseObjdumpOptions(const llvm::opt::InputArgList &InputArgs) {
parseIntArg(InputArgs, OBJDUMP_prefix_strip, PrefixStrip);
for (const opt::Arg *A : InputArgs.filtered(OBJDUMP_substitute_path)) {
StringRef From = A->getValue(0);
- StringRef To = A->getValue(1);
if (From.empty())
reportCmdLineError(A->getSpelling() +
": <from> must not be empty");
>From 2f629c5fad54fc077e1eb52e490c5eed001d940c Mon Sep 17 00:00:00 2001
From: VladimirMedic <vladimir.medic at gmail.com>
Date: Wed, 10 Jun 2026 10:33:15 +0200
Subject: [PATCH 6/7] Remove extra includes, report error on empty dir in
--source-path, add new test cases, fix some existing according to review.
---
.../tools/llvm-objdump/X86/source-path.test | 67 +++++++++++++------
llvm/tools/llvm-objdump/llvm-objdump.cpp | 9 ++-
llvm/tools/llvm-objdump/llvm-objdump.h | 2 -
3 files changed, 54 insertions(+), 24 deletions(-)
diff --git a/llvm/test/tools/llvm-objdump/X86/source-path.test b/llvm/test/tools/llvm-objdump/X86/source-path.test
index e407dc368d3aa..bcc7ed92c37e0 100644
--- a/llvm/test/tools/llvm-objdump/X86/source-path.test
+++ b/llvm/test/tools/llvm-objdump/X86/source-path.test
@@ -26,51 +26,73 @@
; 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 file under %/p alone); second finds
-; Inputs/source-interleave-x86_64.c via basename lookup.
+;; First --source-dir misses (no source file under %/p alone); second finds
+;; Inputs/source-interleave-x86_64.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).
+;; 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).
+;; 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.
+;; 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
-; Empty <to> strips the matched prefix (GDB allows this); use --source-dir to
-; resolve the resulting relative path.
-; RUN: echo '--substitute-path' > %t-empty-to.rsp
-; RUN: echo '/wrong' >> %t-empty-to.rsp
-; RUN: echo '""' >> %t-empty-to.rsp
-; RUN: llvm-objdump --no-print-imm-hex -d --source @%t-empty-to.rsp \
+;; Empty <to> strips the matched prefix; use --source-dir to
+;; resolve the resulting relative path.
+; RUN: llvm-objdump --no-print-imm-hex -d --source --substitute-path /wrong "" \
; RUN: --source-dir %/p/Inputs %t.o | FileCheck %s --check-prefix=SOURCE
-; Empty <to> without --source-dir: relative path is not resolved; warn once, no crash.
-; RUN: llvm-objdump --no-print-imm-hex -d --source @%t-empty-to.rsp %t.o 2>&1 | \
+;; Empty <to> without --source-dir: relative path is not resolved; warn once, no crash.
+; RUN: llvm-objdump --no-print-imm-hex -d --source --substitute-path /wrong "" %t.o 2>&1 | \
; RUN: FileCheck %s --check-prefix=SOURCE-EMPTY-TO -DFILE=%t.o
-; Empty <from> is rejected (use a response file so the shell does not drop "").
-; RUN: echo '--substitute-path' > %t-empty-from.rsp
-; RUN: echo '""' >> %t-empty-from.rsp
-; RUN: echo '%/p' >> %t-empty-from.rsp
-; RUN: not llvm-objdump @%t-empty-from.rsp %t.o 2>&1 | \
+;; Empty <from> is rejected .
+; RUN: not llvm-objdump --substitute-path "" %/p %t.o 2>&1 | \
; RUN: FileCheck %s --check-prefix=SUBST-ERR-FROM
; SUBST-ERR-FROM: llvm-objdump{{.*}}: error: --substitute-path: <from> must not be empty
+;; --substitute-path runs before --prefix: remap /wrong/Inputs to the
+;; root-relative /Inputs, then prepend the test directory.
+; RUN: llvm-objdump --no-print-imm-hex -d --source \
+; RUN: --substitute-path /wrong/Inputs /Inputs --prefix %/p %t.o | \
+; RUN: FileCheck %s --check-prefix=SOURCE
+; RUN: llvm-objdump --no-print-imm-hex -d --line-numbers \
+; RUN: --substitute-path /wrong/Inputs /Inputs --prefix %/p %t.o | \
+; RUN: FileCheck %s --check-prefix=LINE-SUBST-PREFIX -DPREFIX=%/p
+
+;; --prefix alone cannot fix /wrong/Inputs, but --source-dir finds the file
+;; via basename lookup under the search directory.
+; RUN: llvm-objdump --no-print-imm-hex -d --source \
+; RUN: --prefix %/p --source-dir %/p/Inputs %t.o | \
+; RUN: FileCheck %s --check-prefix=SOURCE
+
+;; All three: substitute and prefix produce a missing path; --source-dir
+;; resolves the file from the search directory.
+; RUN: llvm-objdump --no-print-imm-hex -d --source \
+; RUN: --substitute-path /wrong/Inputs /missing \
+; RUN: --prefix %/p --source-dir %/p/Inputs %t.o | \
+; RUN: FileCheck %s --check-prefix=SOURCE
+
+;; When substitute-path yields a relative path, --prefix is ignored (as in
+;; source-interleave-prefix.test) but --source-dir still resolves the file.
+; RUN: llvm-objdump --no-print-imm-hex -d --source --substitute-path /wrong "" %t.o \
+; RUN: --prefix myprefix --source-dir %/p/Inputs %t.o | \
+; RUN: FileCheck %s --check-prefix=SOURCE
+
; LINE: <foo>:
; LINE-NEXT: ; foo():
; LINE-NEXT: ; {{.*}}source-interleave-x86_64.c:2
@@ -86,7 +108,7 @@
; SOURCE-MISSING-SUBST: warning: '[[FILE]]': failed to find source {{[/\\]}}nowhere{{[/\\]}}Inputs{{[/\\]}}source-interleave-x86_64.c
; SOURCE-EMPTY-TO: <foo>:
-; SOURCE-EMPTY-TO: warning: '[[FILE]]': failed to find source {{.*}}Inputs{{[/\\]}}source-interleave-x86_64.c
+; SOURCE-EMPTY-TO: warning: '[[FILE]]': failed to find source Inputs{{[/\\]}}source-interleave-x86_64.c
; SOURCE-EMPTY-TO: pushq
; SOURCE-EMPTY-TO-NOT: ; int foo
@@ -98,3 +120,10 @@
; SOURCE-NEXT: movl
; SOURCE-NEXT: popq
; SOURCE-NEXT: retq
+
+; LINE-SUBST-PREFIX: <foo>:
+; LINE-SUBST-PREFIX-NEXT: ; foo():
+; LINE-SUBST-PREFIX-NEXT: ; [[PREFIX]]{{[/\\]}}Inputs{{[/\\]}}source-interleave-x86_64.c:2
+; LINE-SUBST-PREFIX-NEXT: pushq
+; LINE-SUBST-PREFIX: ; [[PREFIX]]{{[/\\]}}Inputs{{[/\\]}}source-interleave-x86_64.c:3
+; LINE-SUBST-PREFIX-NEXT: movq
diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index c04b24d6583b2..6da338edf44e4 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -3912,9 +3912,12 @@ static void parseObjdumpOptions(const llvm::opt::InputArgList &InputArgs) {
": <from> must not be empty");
SubstitutePaths.emplace_back(From.str(), A->getValue(1));
}
- for (StringRef Dir : InputArgs.getAllArgValues(OBJDUMP_source_dir))
- if (!Dir.empty())
- SourceDirs.insert(SourceDirs.begin(), Dir.str());
+ for (StringRef Dir : InputArgs.getAllArgValues(OBJDUMP_source_dir)) {
+ if (Dir.empty())
+ reportCmdLineError("--source-dir argument must not be empty");
+ SourceDirs.insert(SourceDirs.begin(), 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 a68dc29409ac1..b52f92721386f 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.h
+++ b/llvm/tools/llvm-objdump/llvm-objdump.h
@@ -18,8 +18,6 @@
#include "llvm/Support/FormattedStream.h"
#include <functional>
#include <memory>
-#include <utility>
-#include <vector>
namespace llvm {
class StringRef;
>From b36b95d0ed5c308c63fa270a77dae83d80e71ca2 Mon Sep 17 00:00:00 2001
From: VladimirMedic <vladimir.medic at gmail.com>
Date: Fri, 12 Jun 2026 11:42:51 +0200
Subject: [PATCH 7/7] Change search order for --source-dir, remove extra
comamnds from test.
---
llvm/test/tools/llvm-objdump/X86/source-path.test | 8 ++------
llvm/tools/llvm-objdump/llvm-objdump.cpp | 2 +-
2 files changed, 3 insertions(+), 7 deletions(-)
diff --git a/llvm/test/tools/llvm-objdump/X86/source-path.test b/llvm/test/tools/llvm-objdump/X86/source-path.test
index bcc7ed92c37e0..48f188be04f05 100644
--- a/llvm/test/tools/llvm-objdump/X86/source-path.test
+++ b/llvm/test/tools/llvm-objdump/X86/source-path.test
@@ -4,16 +4,12 @@
;; lives in this test's Inputs directory. Without path remapping, --source
;; cannot load the file from disk.
;;
-;; --source-dir: one directory per flag; later flags on the command line are
-;; searched before earlier ones.
+;; --source-dir: one directory per flag; flags on the command line are
+;; searched in order fitst-to-last.
;;
;; --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-interleave.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
diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp
index 6da338edf44e4..d1c41d4302b9c 100644
--- a/llvm/tools/llvm-objdump/llvm-objdump.cpp
+++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp
@@ -3915,7 +3915,7 @@ static void parseObjdumpOptions(const llvm::opt::InputArgList &InputArgs) {
for (StringRef Dir : InputArgs.getAllArgValues(OBJDUMP_source_dir)) {
if (Dir.empty())
reportCmdLineError("--source-dir argument must not be empty");
- SourceDirs.insert(SourceDirs.begin(), Dir.str());
+ SourceDirs.insert(SourceDirs.end(), Dir.str());
}
if (const opt::Arg *A = InputArgs.getLastArg(OBJDUMP_debug_vars_EQ)) {
More information about the llvm-commits
mailing list