[llvm] Normalize file path to fix problems with different folder separators. (PR #215225)

via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 10 02:21:44 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-binary-utilities

Author: VladimirMedic

<details>
<summary>Changes</summary>

We have two issues reported by users:
1. Inconsistent file path separators in isa disassembly for line correlation
2. --substitute-path silently fails for binaries compiled on Windows when analyzed on Linux

Normalizing the file path in LineInfo is sorting both problems. As the path is normalized immediately after symbolization, following redundant normalization calls are removed. Tests are adjusted to accept either / or \ depending on the host. Additional input is created to test point 2. 

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


6 Files Affected:

- (added) llvm/test/tools/llvm-objdump/X86/Inputs/source-interleave-windows-path.ll (+76) 
- (modified) llvm/test/tools/llvm-objdump/X86/source-interleave-function-from-debug.test (+6-6) 
- (modified) llvm/test/tools/llvm-objdump/X86/source-interleave-prefix-windows.test (+7-9) 
- (modified) llvm/test/tools/llvm-objdump/X86/source-interleave-prefix.test (+16-11) 
- (modified) llvm/test/tools/llvm-objdump/X86/source-path.test (+33) 
- (modified) llvm/tools/llvm-objdump/SourcePrinter.cpp (+15-9) 


``````````diff
diff --git a/llvm/test/tools/llvm-objdump/X86/Inputs/source-interleave-windows-path.ll b/llvm/test/tools/llvm-objdump/X86/Inputs/source-interleave-windows-path.ll
new file mode 100644
index 0000000000000..fd6f320c60453
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/X86/Inputs/source-interleave-windows-path.ll
@@ -0,0 +1,76 @@
+; NOTE: This is a copy of source-interleave.ll whose source path is spelled with
+;       Windows separators, as emitted by a toolchain running on Windows. It
+;       lets source-path.test check that such paths are handled on any host.
+;
+;       The whole relative path is held in the file name with an empty
+;       directory. MCDwarfLineTableHeader::tryGetFile splits that into a
+;       directory and a base name using the host separator, so on a POSIX host
+;       nothing is split off and the recorded path keeps '\' throughout, which
+;       is the case this is meant to cover.
+;
+;       The separators are baked in rather than substituted by sed, because
+;       escaping a backslash through both sed and the lit shell is unreadable.
+
+; ModuleID = 'source-interleave-x86_64.bc'
+source_filename = "source-interleave-x86_64.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: "dx12\5Ccompute\5Cno_rs\5Csource-interleave-x86_64.c", directory: "")
+!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/X86/source-interleave-function-from-debug.test b/llvm/test/tools/llvm-objdump/X86/source-interleave-function-from-debug.test
index 96ae656100531..32ef8d8c0eb9e 100644
--- a/llvm/test/tools/llvm-objdump/X86/source-interleave-function-from-debug.test
+++ b/llvm/test/tools/llvm-objdump/X86/source-interleave-function-from-debug.test
@@ -9,7 +9,7 @@
 
 ; CHECK:      0000000000000000 <foo>:
 ; CHECK-NEXT: ; foo():
-; CHECK-NEXT: ; /tmp{{/|\\}}src.cc:1
+; CHECK-NEXT: ; {{/|\\}}tmp{{/|\\}}src.cc:1
 ; CHECK-NEXT:        0: b8 05 00 00 00                movl    $5, %eax
 ; CHECK-NEXT:        5: c3                            retq
 
@@ -18,7 +18,7 @@
 ; CHECK-DEMANGLE:         0000000000000008 <xyz::bar()>:
 ; CHECK-DEMANGLE-NEXT:    ; xyz::bar():
 
-; CHECK-NEXT: ; /tmp{{/|\\}}src.cc:3
+; CHECK-NEXT: ; {{/|\\}}tmp{{/|\\}}src.cc:3
 ; CHECK-NEXT:        8: b8 0a 00 00 00                movl    $10, %eax
 ; CHECK-NEXT:        d: c3                            retq
 
@@ -27,7 +27,7 @@
 ; CHECK-DEMANGLE:         0000000000000010 <xyz::baz()>:
 ; CHECK-DEMANGLE-NEXT:    ; xyz::baz():
 
-; CHECK-NEXT: ; /tmp{{/|\\}}src.cc:3
+; CHECK-NEXT: ; {{/|\\}}tmp{{/|\\}}src.cc:3
 ; CHECK-NEXT:       10: b8 14 00 00 00                movl    $20, %eax
 ; CHECK-NEXT:       15: c3                            retq
 
@@ -39,17 +39,17 @@
 
 ; STRIPPED:      0000000000000000 <.text>:
 ; STRIPPED-NEXT: ; Function1():
-; STRIPPED-NEXT: ; /tmp{{/|\\}}src.cc:1
+; STRIPPED-NEXT: ; {{/|\\}}tmp{{/|\\}}src.cc:1
 ; STRIPPED-NEXT:        0: b8 05 00 00 00                movl    $5, %eax
 ; STRIPPED-NEXT:        5: c3                            retq
 
 ; STRIPPED:      ; xyz::bar():
-; STRIPPED-NEXT: ; /tmp{{/|\\}}src.cc:3
+; STRIPPED-NEXT: ; {{/|\\}}tmp{{/|\\}}src.cc:3
 ; STRIPPED-NEXT:        8: b8 0a 00 00 00                movl    $10, %eax
 ; STRIPPED-NEXT:        d: c3                            retq
 
 ; STRIPPED:      ; xyz::baz():
-; STRIPPED-NEXT: ; /tmp{{/|\\}}src.cc:3
+; STRIPPED-NEXT: ; {{/|\\}}tmp{{/|\\}}src.cc:3
 ; STRIPPED-NEXT:       10: b8 14 00 00 00                movl    $20, %eax
 ; STRIPPED-NEXT:       15: c3                            retq
 
diff --git a/llvm/test/tools/llvm-objdump/X86/source-interleave-prefix-windows.test b/llvm/test/tools/llvm-objdump/X86/source-interleave-prefix-windows.test
index f75ba94353b68..bee32bd74007d 100644
--- a/llvm/test/tools/llvm-objdump/X86/source-interleave-prefix-windows.test
+++ b/llvm/test/tools/llvm-objdump/X86/source-interleave-prefix-windows.test
@@ -6,14 +6,12 @@
 
 ; RUN: sed -e "s,SRC_COMPDIR,/Inputs,g" %p/Inputs/source-interleave.ll > %t.ll
 ; RUN: llc -o %t.o -filetype=obj -mtriple=x86_64-pc-linux %t.ll
-; RUN: llvm-objdump --prefix 'myprefix/\' --source %t.o 2>&1 | FileCheck %s -DFILE=%t.o -DPREFIX='myprefix' -DSEP=%{fs-sep}
+; RUN: llvm-objdump --prefix 'myprefix/\' --source %t.o 2>&1 | FileCheck %s -DFILE=%t.o -DPREFIX='myprefix'
 
-;; When --prefix is specified and the file path is absolute, sys::path::append
-;; is used to add the file path to the prefix. On Windows, if the file path
-;; starts with a slash, sys::path::append currently does a straight
-;; concatenation, resulting in the first slash being preserved as-is ('/').
-;; The second slash (after 'Inputs') is generated by llc using
-;; sys::path::append to join the directory and filename, which uses the
-;; preferred path separator (controlled by LLVM_WINDOWS_PREFER_FORWARD_SLASH).
-; CHECK: warning: '[[FILE]]': failed to find source [[PREFIX]]/Inputs[[SEP]]source-interleave-x86_64.c
+;; The source path from the debug info is normalized before --prefix is applied,
+;; so the reported path uses one separator style throughout instead of mixing
+;; them. Which style that is depends on LLVM_WINDOWS_PREFER_FORWARD_SLASH, so
+;; both are accepted here. sys::path::append then concatenates the path onto
+;; the prefix without inserting a separator, because it already starts with one.
+; CHECK: warning: '[[FILE]]': failed to find source [[PREFIX]]{{[/\\]}}Inputs{{[/\\]}}source-interleave-x86_64.c
 
diff --git a/llvm/test/tools/llvm-objdump/X86/source-interleave-prefix.test b/llvm/test/tools/llvm-objdump/X86/source-interleave-prefix.test
index 746add22f96e5..02cea7efbb6b9 100644
--- a/llvm/test/tools/llvm-objdump/X86/source-interleave-prefix.test
+++ b/llvm/test/tools/llvm-objdump/X86/source-interleave-prefix.test
@@ -1,19 +1,20 @@
 ;; Test --prefix option.
 
-;; Separators change from platform to platform. In POSIX the full path for the
-;; directory './Inputs' appended with the file 'source-interleave-x86_64.c' is
-;; './Inputs/source-interleave-x86_64.c'. For Windows it is
-;; './Inputs\source-interleave-x86_64.c'. Platform specific tests are needed
-;; since '\' may or may not be a separator.
+;; Separators change from platform to platform, and on Windows they further
+;; depend on LLVM_WINDOWS_PREFER_FORWARD_SLASH. Source paths taken from the
+;; debug info are normalized so that a single path does not mix styles, but
+;; which separator that ends up being is not part of the contract: every check
+;; below accepts '/' and '\' interchangeably. Platform specific tests are still
+;; needed since '\' may or may not be a separator.
 
 ;; Test prefix option ignored for relative paths.
-;; For the test below it is possible to accept both '/' and '\' as a separator.
+;; Normalization folds away the leading './' of './Inputs', leaving 'Inputs'.
 
 ; RUN: sed -e "s,SRC_COMPDIR,./Inputs,g" %p/Inputs/source-interleave.ll > %t-relative-path.ll
 ; RUN: llc -o %t-relative-path.o -filetype=obj -mtriple=x86_64-pc-linux %t-relative-path.ll
 ; RUN: llvm-objdump --prefix myprefix --source %t-relative-path.o 2>&1 | \
-; RUN:   FileCheck %s --check-prefix=CHECK-BROKEN-PREFIX -DFILE=%t-relative-path.o -DPREFIX=. -DCOMPDIR=/Inputs
-; CHECK-BROKEN-PREFIX: warning: '[[FILE]]': failed to find source [[PREFIX]][[COMPDIR]]{{[/\\]}}source-interleave-x86_64.c
+; RUN:   FileCheck %s --check-prefix=CHECK-RELATIVE-PREFIX -DFILE=%t-relative-path.o
+; CHECK-RELATIVE-PREFIX: warning: '[[FILE]]': failed to find source Inputs{{[/\\]}}source-interleave-x86_64.c
 
 ;; Test invalid source interleave fixed by adding the correct prefix.
 
@@ -28,18 +29,22 @@
 ; RUN: sed -e "s,SRC_COMPDIR,%/p/Inputs,g" %p/Inputs/source-interleave.ll > %t-correct-prefix.ll
 ; RUN: llc -o %t-correct-prefix.o -filetype=obj -mtriple=x86_64-pc-linux %t-correct-prefix.ll
 ; RUN: llvm-objdump --prefix myprefix --source %t-correct-prefix.o 2>&1 | \
-; RUN:   FileCheck %s --check-prefix=CHECK-BROKEN-PREFIX -DFILE=%t-correct-prefix.o -DPREFIX=myprefix%/p -DCOMPDIR=/Inputs
+; RUN:   FileCheck %s --check-prefix=CHECK-ABSOLUTE-PREFIX -DFILE=%t-correct-prefix.o
+;; The compilation directory is an absolute host path, so the middle of the
+;; reported path is not spelled out here.
+; CHECK-ABSOLUTE-PREFIX: warning: '[[FILE]]': failed to find source myprefix{{.*}}Inputs{{[/\\]}}source-interleave-x86_64.c
 
 ;; Using only a prefix separator is the same as not using the `--prefix` option.
 
 ; RUN: llvm-objdump --prefix / --source %t-missing-prefix.o 2>&1 | \
-; RUN:   FileCheck %s --check-prefix=CHECK-BROKEN-PREFIX -DFILE=%t-missing-prefix.o -DPREFIX='' -DCOMPDIR=/Inputs
+; RUN:   FileCheck %s --check-prefix=CHECK-BROKEN-PREFIX -DFILE=%t-missing-prefix.o -DPREFIX=''
+; CHECK-BROKEN-PREFIX: warning: '[[FILE]]': failed to find source [[PREFIX]]{{[/\\]}}Inputs{{[/\\]}}source-interleave-x86_64.c
 
 ;; All trailing separators on the prefix are discarded.
 ;; The prefix 'myprefix//' is converted to 'myprefix'.
 
 ; RUN: llvm-objdump --prefix myprefix// --source %t-missing-prefix.o 2>&1 | \
-; RUN:   FileCheck %s --check-prefix=CHECK-BROKEN-PREFIX -DFILE=%t-missing-prefix.o -DPREFIX=myprefix -DCOMPDIR=/Inputs
+; RUN:   FileCheck %s --check-prefix=CHECK-BROKEN-PREFIX -DFILE=%t-missing-prefix.o -DPREFIX=myprefix
 
 ;; Test invalid source interleave fixed by adding the correct prefix and
 ;; stripping out an extra directory from the path.
diff --git a/llvm/test/tools/llvm-objdump/X86/source-path.test b/llvm/test/tools/llvm-objdump/X86/source-path.test
index 4d6e9990e8245..ee1be1f70b2ac 100644
--- a/llvm/test/tools/llvm-objdump/X86/source-path.test
+++ b/llvm/test/tools/llvm-objdump/X86/source-path.test
@@ -151,3 +151,36 @@
 ; LINE-SUBST-PREFIX-NEXT: pushq
 ; LINE-SUBST-PREFIX: ; [[PREFIX]]{{[/\\]}}Inputs{{[/\\]}}source-interleave-x86_64.c:3
 ; LINE-SUBST-PREFIX-NEXT: movq
+
+;; Objects built on Windows record source paths with '\' separators, which are
+;; not separators on a POSIX host. Paths from the debug info are normalized
+;; before the rules below are applied, so the same object behaves the same way
+;; whatever the host is.
+
+; RUN: llc -o %t-winpath.o -filetype=obj -mtriple=x86_64-pc-linux \
+; RUN:   %p/Inputs/source-interleave-windows-path.ll
+
+;; Which separator a path is printed with depends on the host and, on Windows,
+;; on LLVM_WINDOWS_PREFER_FORWARD_SLASH, so it is not checked for here. What is
+;; checked is that one path does not mix the two styles: the separator is
+;; captured from its first occurrence and has to be the same one throughout.
+; RUN: llvm-objdump --no-print-imm-hex -d --line-numbers %t-winpath.o | \
+; RUN:   FileCheck %s --check-prefix=WINPATH-LINE
+
+; WINPATH-LINE: <foo>:
+; WINPATH-LINE-NEXT: ; foo():
+; WINPATH-LINE-NEXT: ; dx12[[SEP:[/\\]]]compute[[SEP]]no_rs[[SEP]]source-interleave-x86_64.c:2
+
+;; <from> may be spelled with either separator; both name the same directory.
+
+; RUN: llvm-objdump --no-print-imm-hex -d --source \
+; RUN:   --substitute-path 'dx12\compute\no_rs' %/p/Inputs %t-winpath.o | \
+; RUN:   FileCheck %s --check-prefix=SOURCE
+; RUN: llvm-objdump --no-print-imm-hex -d --source \
+; RUN:   --substitute-path dx12/compute/no_rs %/p/Inputs %t-winpath.o | \
+; RUN:   FileCheck %s --check-prefix=SOURCE
+
+;; --source-dir finds the same file by its base name.
+
+; RUN: llvm-objdump --no-print-imm-hex -d --source --source-dir %/p/Inputs \
+; RUN:   %t-winpath.o | FileCheck %s --check-prefix=SOURCE
diff --git a/llvm/tools/llvm-objdump/SourcePrinter.cpp b/llvm/tools/llvm-objdump/SourcePrinter.cpp
index 9d8daa9043fd8..3a06b3d721ba2 100644
--- a/llvm/tools/llvm-objdump/SourcePrinter.cpp
+++ b/llvm/tools/llvm-objdump/SourcePrinter.cpp
@@ -85,22 +85,22 @@ static std::string applySubstitutePaths(StringRef FileName) {
   if (SubstitutePaths.empty())
     return FileName.str();
 
+  // FileName is normalized by the caller, so the components split out of it are
+  // normalized too. Only the rules given on the command line still need it.
   StringRef BaseName = sys::path::filename(FileName);
-  SmallString<256> Directory(sys::path::parent_path(FileName));
-  normalizeSourcePath(Directory);
+  StringRef Directory = sys::path::parent_path(FileName);
 
   for (const auto &[From, To] : SubstitutePaths) {
     SmallString<256> FromPath(From);
     normalizeSourcePath(FromPath);
-    StringRef Dir = Directory;
-    if (!Dir.starts_with(FromPath))
+    if (!Directory.starts_with(FromPath))
       continue;
-    if (Dir.size() > FromPath.size() &&
-        !sys::path::is_separator(Dir[FromPath.size()]))
+    if (Directory.size() > FromPath.size() &&
+        !sys::path::is_separator(Directory[FromPath.size()]))
       continue;
 
     SmallString<256> NewDir(To);
-    StringRef Suffix = Dir.substr(FromPath.size());
+    StringRef Suffix = Directory.substr(FromPath.size());
     while (!Suffix.empty() && sys::path::is_separator(Suffix.front()))
       Suffix = Suffix.drop_front();
     if (!Suffix.empty())
@@ -111,7 +111,6 @@ static std::string applySubstitutePaths(StringRef FileName) {
       return BaseName.str();
     SmallString<256> Result(NewDir);
     sys::path::append(Result, BaseName);
-    normalizeSourcePath(Result);
     return std::string(Result);
   }
 
@@ -747,6 +746,12 @@ void SourcePrinter::printSourceLine(formatted_raw_ostream &OS,
       Symbolizer->symbolizeCode(*Obj, Address);
   if (ExpectedLineInfo) {
     LineInfo = *ExpectedLineInfo;
+    if (LineInfo.FileName != DILineInfo::BadString) {
+      SmallString<256> NormalizedName(LineInfo.FileName);
+      normalizeSourcePath(NormalizedName);
+      LineInfo.FileName = std::string(NormalizedName);
+    }
+
   } else if (!WarnedInvalidDebugInfo) {
     WarnedInvalidDebugInfo = true;
     // TODO Untested.
@@ -754,7 +759,8 @@ void SourcePrinter::printSourceLine(formatted_raw_ostream &OS,
                       toString(ExpectedLineInfo.takeError()),
                   ObjectFilename);
   }
-  if (!objdump::SubstitutePaths.empty())
+  if (!objdump::SubstitutePaths.empty() &&
+      LineInfo.FileName != DILineInfo::BadString)
     LineInfo.FileName = applySubstitutePaths(LineInfo.FileName);
 
   if (!objdump::Prefix.empty() &&

``````````

</details>


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


More information about the llvm-commits mailing list