| 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