Issue 209184
Summary BOLT corrupts DWARF file indices when the input has duplicate `file_names` entries
Labels BOLT
Assignees
Reporter not4juu
    ## Description

When BOLT rebuilds a compile unit's line-table file list, it re-inserts every entry through `MCDwarfLineTableHeader::tryGetFile`, which **deduplicates** identical files. If the input line program contains a duplicate `file_names` entry (both GCC and Clang can emit the primary source file twice with an identical dir+name, see example below), BOLT collapses it, shrinks the table, and **shifts the following file indices** — but **does not remap the references that point into that table.**

Result, in the affected CUs:

- `.debug_info`: `DW_AT_decl_file` / `DW_AT_call_file` point past the end of the (shrunken) file table.
- `.debug_line`: `DW_LNS_set_file` references a non-existent file number.

**The defect is producer-independent.** The bug triggers on any input whose file table contains an identical duplicate that collapses on re-insertion. A single reproducer confirms this in **both** cases — the same `main.cpp` compiled with either GCC or Clang produces the exact same corruption.

## Reproducer

One self-contained source file, `main.cpp` (plus `widget.hpp`):

### `main.cpp`

```cpp
// Both compilers end up with a byte-identical duplicate primary-file entry in the DWARF5 line-table file_names that BOLT collapses (3->2), 
// shifting widget.hpp's index out of range:
//   GCC:   [0]main.cpp [1]main.cpp  [2]widget.hpp   ([0]==[1])
//   Clang: [0]main.cpp [1]widget.hpp [2]main.cpp    ([0]==[2])
#include "widget.hpp"

#line 1 "main.cpp"

int main(int argc, char **) {
  Widget w{argc};
  return w.scaled(3);
}
```

> NOTE: The `#line 1 "main.cpp"` directive in `main.cpp` is **required for Clang** (it re-injects the primary file name so Clang emits the identical duplicate) and **harmless for GCC** (which already lists the primary file twice). Compile with a relative source path so the injected name matches.



### `widget.hpp`

```cpp
#pragma once

struct Widget {
  int value;
  int scaled(int k) const { return value * k; }
};
```



### Compilation and BOLT debug section update

```bash
# GCC
g++     -g -gdwarf-5 -gz=none -O2 -Wl,-q -Wl,--compress-debug-sections=none main.cpp -o main
# or Clang
clang++ -g -gdwarf-5 -gz=none -O2 -Wl,-q -Wl,--compress-debug-sections=none main.cpp -o main

llvm-bolt main -o main.bolt --update-debug-sections
```

### Baseline (before BOLT)
```bash
llvm-dwarfdump --verify main | grep 'invalid file index' // <--- no problem reported

llvm-dwarfdump --debug-info main   | grep -E 'DW_TAG_|DW_AT_name|DW_AT_decl_file|DW_AT_call_file'
0x0000000c: DW_TAG_compile_unit
              DW_AT_name        ("main.cpp")
0x0000002a:   DW_TAG_structure_type
                DW_AT_name      ("Widget")
                DW_AT_decl_file ("/workspace/widget.hpp")
0x00000037:     DW_TAG_member
                  DW_AT_name    ("value")
                  DW_AT_decl_file       ("/workspace/widget.hpp")
0x00000044:     DW_TAG_subprogram
                  DW_AT_name    ("scaled")
                  DW_AT_decl_file       ("/workspace/widget.hpp")
0x00000058:       DW_TAG_formal_parameter
0x0000005d:       DW_TAG_formal_parameter
0x00000064:   DW_TAG_const_type
0x00000069:   DW_TAG_base_type
                DW_AT_name      ("int")
0x00000070:   DW_TAG_pointer_type
0x00000075:   DW_TAG_const_type
0x0000007a:   DW_TAG_subprogram
                DW_AT_name      ("main")
                DW_AT_decl_file ("/workspace/main.cpp")
0x0000009c:     DW_TAG_formal_parameter
                  DW_AT_name    ("argc")
                  DW_AT_decl_file       ("/workspace/main.cpp")
0x000000aa:     DW_TAG_formal_parameter
0x000000b1:     DW_TAG_variable
                  DW_AT_name    ("w")
                  DW_AT_decl_file       ("/workspace/main.cpp")
0x000000c3:     DW_TAG_inlined_subroutine
                  DW_AT_call_file       ("/workspace/main.cpp")
0x000000e4:       DW_TAG_formal_parameter
0x000000f1:       DW_TAG_formal_parameter
0x00000100:   DW_TAG_pointer_type
0x00000105:   DW_TAG_pointer_type
0x0000010a:   DW_TAG_base_type
                DW_AT_name      ("char")
0x00000111:   DW_TAG_subprogram
0x0000011b:     DW_TAG_formal_parameter
                  DW_AT_name    ("this")
0x00000124:     DW_TAG_formal_parameter
                  DW_AT_name    ("k")
                  DW_AT_decl_file       ("/workspace/widget.hpp")
```

### Observed corruption (after BOLT)

```bash
addr2line -e main.bolt <addr in main> 
addr2line: DWARF error: mangled line number section (bad file number)

llvm-dwarfdump --verify main.bolt | grep 'invalid file index'
error: DIE has DW_AT_decl_file with an invalid file index 2 (valid values are [0-1])
error: DIE has DW_AT_decl_file with an invalid file index 2 (valid values are [0-1])
error: DIE has DW_AT_decl_file with an invalid file index 2 (valid values are [0-1])
error: DIE has DW_AT_decl_file with an invalid file index 2 (valid values are [0-1])
error: .debug_line[0x00000000][0] has invalid file index 2  (valid values are [0,2)):

llvm-dwarfdump --debug-info main.bolt   | grep -E 'DW_TAG_|DW_AT_name|DW_AT_decl_file|DW_AT_call_file'
0x0000000c: DW_TAG_compile_unit
              DW_AT_name        ("main.cpp")
0x00000033:   DW_TAG_structure_type
                DW_AT_name      ("Widget")
                DW_AT_decl_file (0x02)
0x00000040:     DW_TAG_member
                  DW_AT_name    ("value")
                  DW_AT_decl_file       (0x02)
0x0000004d:     DW_TAG_subprogram
                  DW_AT_name    ("scaled")
                  DW_AT_decl_file       (0x02)
0x00000061:       DW_TAG_formal_parameter
0x00000066:       DW_TAG_formal_parameter
0x0000006d:   DW_TAG_const_type
0x00000072:   DW_TAG_base_type
                DW_AT_name      ("int")
0x00000079:   DW_TAG_pointer_type
0x0000007e:   DW_TAG_const_type
0x00000083:   DW_TAG_subprogram
                DW_AT_name      ("main")
                DW_AT_decl_file ("/workspace/widget.hpp")
0x000000a5:     DW_TAG_formal_parameter
                  DW_AT_name    ("argc")
                  DW_AT_decl_file       ("/workspace/widget.hpp")
0x000000b3:     DW_TAG_formal_parameter
0x000000ba:     DW_TAG_variable
                  DW_AT_name    ("w")
                  DW_AT_decl_file       ("/workspace/widget.hpp")
0x000000c9:     DW_TAG_inlined_subroutine
                  DW_AT_call_file       ("/workspace/widget.hpp")
0x000000ea:       DW_TAG_formal_parameter
0x000000f4:       DW_TAG_formal_parameter
0x00000100:   DW_TAG_pointer_type
0x00000105:   DW_TAG_pointer_type
0x0000010a:   DW_TAG_base_type
                DW_AT_name      ("char")
0x00000111:   DW_TAG_subprogram
0x0000011b:     DW_TAG_formal_parameter
                  DW_AT_name    ("this")
0x00000124:     DW_TAG_formal_parameter
                  DW_AT_name    ("k")
                  DW_AT_decl_file       (0x02)
```

**What to look at — two distinct failure modes.** The input file table `[0]main.cpp [1]main.cpp [2]widget.hpp` collapses to `[0]main.cpp [1]widget.hpp`, but BOLT keeps emitting the original indices, so:

 | DIE(s)                                   | emitted index | resolves to           | correct? |
 |------------------------------------------|---------------|-----------------------|----------|
 | `Widget`, `value`, `scaled`, `k`         | `2`           | — (out of range → `0x02`) | ✗ (verify error) |
 | `main`, `argc`, `w`, inlined `call_file` | `1`           | `widget.hpp`          | ✗ (should be `main.cpp`) |

The first case is caught by `--verify` ("invalid file index 2"); the second is a **silent** mis-attribution — index `1` is in range, it simply changed meaning after the duplicate was collapsed (input `1 = main.cpp` → output `1 = widget.hpp`). `addr2line` inside `main` therefore reports the wrong file or fails outright.

## Root cause

1. `BinaryContext::preprocessDebugInfo` calls `getDwarfFile(...)` and discards the returned (possibly deduplicated) index — no input→output map is recorded.
2. `.debug_line` intra-CU (`BinaryEmitter::emitLineInfo`) and the raw-sequence emitter (`emitBinaryDwarfLineTable` in `DebugData.cpp`) emit the raw index; only the cross-CU/inlined case is remapped.
3. `.debug_info` `DW_AT_decl_file`/`DW_AT_call_file` are copied verbatim by `DIEBuilder::cloneScalarAttribute`.

The Aug-2025 fixes `#151401` (DWARF4/5 index base) and `#151230` (cross-CU remapping) do **not** cover this intra-CU index shift or the `.debug_info` references.

## Spec compliance

The duplicated `file_names` entry is **legal, spec-compliant DWARF**, so the fault lies entirely with BOLT (a DWARF consumer/rewriter):

- **DWARF v5, §6.2.4 "The Line Number Program Header"** defines `file_names` and requires only that entry **[0]** be the primary source file (its name matching the CU's `DW_AT_name`). It imposes **no uniqueness constraint** — repeated entries are permitted, merely redundant. Nothing says a consumer may treat a duplicate as an error.



## Proposed fix

Record the input→output file-index mapping when the table is rebuilt and apply it in the three places above (`.debug_line` intra-CU + raw sequence, and `.debug_info` decl/call file).

```diff
diff --git a/bolt/include/bolt/Core/DebugData.h b/bolt/include/bolt/Core/DebugData.h
--- a/bolt/include/bolt/Core/DebugData.h
+++ b/bolt/include/bolt/Core/DebugData.h
@@ -821,6 +821,11 @@ private:
   StringRef RawData;
 
   /// DWARF Version
   uint16_t DwarfVersion;
 
+  /// Maps an input line-table file index to the output (post-deduplication)
+  /// file index for this CU. Slots left as UINT32_MAX fall back to identity.
+  std::vector<uint32_t> FileIndexRemap;
+
 public:
   /// Emit line info for all units in the binary context.
   static void emit(BinaryContext &BC, MCStreamer &Streamer);
@@ -841,6 +846,19 @@ public:
     return Header.tryGetFile(Directory, FileName, Checksum, Source,
                              DwarfVersion, FileNumber);
   }
 
+  /// Record that input file index InputIndex maps to output file index
+  /// OutputIndex after (possible) deduplication in the rebuilt file table.
+  void mapFileIndex(uint32_t InputIndex, uint32_t OutputIndex) {
+    if (InputIndex >= FileIndexRemap.size())
+      FileIndexRemap.resize(InputIndex + 1, UINT32_MAX);
+    FileIndexRemap[InputIndex] = OutputIndex;
+  }
+
+  /// Translate an input file index to the output one, falling back to identity
+  /// when no remap was recorded (e.g. tables that were not rebuilt).
+  uint32_t getMappedFileIndex(uint32_t InputIndex) const {
+    if (InputIndex < FileIndexRemap.size() &&
+        FileIndexRemap[InputIndex] != UINT32_MAX)
+      return FileIndexRemap[InputIndex];
+    return InputIndex;
+  }
+
   /// Return label at the start of the emitted debug line for the unit.
   MCSymbol *getLabel() const { return Header.Label; }
 
diff --git a/bolt/include/bolt/Core/DIEBuilder.h b/bolt/include/bolt/Core/DIEBuilder.h
--- a/bolt/include/bolt/Core/DIEBuilder.h
+++ b/bolt/include/bolt/Core/DIEBuilder.h
@@ -190,7 +190,7 @@
 
   /// Clone an attribute in scalar format.
   void cloneScalarAttribute(
-      DIE &Die, const DWARFDie &InputDIE,
+      DIE &Die, const DWARFDie &InputDIE, DWARFUnit &U,
       const DWARFAbbreviationDeclaration::AttributeSpec AttrSpec,
       const DWARFFormValue &Val);
 
diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -1948,8 +1948,11 @@
       std::optional<MD5::MD5Result> Checksum;
       if (DwarfVersion >= 5 && LineTable->Prologue.ContentTypes.HasMD5)
         Checksum = LineTable->Prologue.FileNames[I].Checksum;
-      cantFail(getDwarfFile(Dir, FileName, 0, Checksum, std::nullopt, CUID,
-                            DwarfVersion));
+      unsigned OutputFileIndex = cantFail(getDwarfFile(
+          Dir, FileName, 0, Checksum, std::nullopt, CUID, DwarfVersion));
+      // Files may be deduplicated on insertion; record the input->output index
+      // mapping so .debug_info and .debug_line references can be fixed up.
+      BinaryLineTable.mapFileIndex(I + Offset, OutputFileIndex);
     }
   }
 
diff --git a/bolt/lib/Core/BinaryEmitter.cpp b/bolt/lib/Core/BinaryEmitter.cpp
--- a/bolt/lib/Core/BinaryEmitter.cpp
+++ b/bolt/lib/Core/BinaryEmitter.cpp
@@ -684,6 +684,12 @@
     if (TargetUnitIndex != CurrentUnitIndex) {
       // Add filename from the inlined function to the current CU.
       TargetFilenum = BC.addDebugFilenameToUnit(
           TargetUnitIndex, CurrentUnitIndex, CurrentRow.File);
+    } else {
+      // Same CU: the file table may have been deduplicated when rebuilt, so
+      // translate the input file index to the output one.
+      TargetFilenum = BC.getDwarfLineTable(TargetUnitIndex)
+                          .getMappedFileIndex(TargetFilenum);
     }
     BC.Ctx->setCurrentDwarfLoc(TargetFilenum, CurrentRow.Line,
                                CurrentRow.Column, Flags, CurrentRow.Isa,
diff --git a/bolt/lib/Core/DebugData.cpp b/bolt/lib/Core/DebugData.cpp
--- a/bolt/lib/Core/DebugData.cpp
+++ b/bolt/lib/Core/DebugData.cpp
@@ -1001,7 +1001,8 @@
 static inline void emitBinaryDwarfLineTable(
     MCStreamer *MCOS, MCDwarfLineTableParams Params,
     const DWARFDebugLine::LineTable *Table,
-    const std::vector<DwarfLineTable::RowSequence> &InputSequences) {
+    const std::vector<DwarfLineTable::RowSequence> &InputSequences,
+    const DwarfLineTable &DLT) {
   if (InputSequences.empty())
     return;
 
@@ -1044,7 +1045,7 @@
       if (FileNum != Row.File) {
         FileNum = Row.File;
         MCOS->emitInt8(dwarf::DW_LNS_set_file);
-        MCOS->emitULEB128IntValue(FileNum);
+        MCOS->emitULEB128IntValue(DLT.getMappedFileIndex(FileNum));
       }
 
diff --git a/bolt/lib/Core/DebugData.cpp b/bolt/lib/Core/DebugData.cpp
--- a/bolt/lib/Core/DebugData.cpp
+++ b/bolt/lib/Core/DebugData.cpp
@@ -1204,7 +1204,7 @@
 
   // Emit line tables for the original code.
-  emitBinaryDwarfLineTable(MCOS, Params, InputTable, InputSequences);
+  emitBinaryDwarfLineTable(MCOS, Params, InputTable, InputSequences, *this);
 
   // This is the end of the section, so set the value of the symbol at the end
diff --git a/bolt/lib/Core/DIEBuilder.cpp b/bolt/lib/Core/DIEBuilder.cpp
--- a/bolt/lib/Core/DIEBuilder.cpp
+++ b/bolt/lib/Core/DIEBuilder.cpp
@@ -927,7 +927,7 @@
 
 void DIEBuilder::cloneScalarAttribute(
-    DIE &Die, const DWARFDie &InputDIE,
+    DIE &Die, const DWARFDie &InputDIE, DWARFUnit &U,
     const DWARFAbbreviationDeclaration::AttributeSpec AttrSpec,
     const DWARFFormValue &Val) {
   uint64_t Value;
@@ -944,6 +944,17 @@
     return;
   }
 
+  // DW_AT_decl_file / DW_AT_call_file index the CU's line-table file list,
+  // which BOLT may deduplicate when rebuilding. Translate the input index to
+  // the output one. DW_FORM_implicit_const stores its value in the abbrev, not
+  // the DIE, so it is left untouched (not observed to be affected in practice).
+  if ((AttrSpec.Attr == dwarf::DW_AT_decl_file ||
+       AttrSpec.Attr == dwarf::DW_AT_call_file) &&
+      AttrSpec.Form != dwarf::D<truncated>Please see the issue for the entire body.
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to