[llvm-dwarfutil] DW_AT_import is rewritten to the referenced unit's header offset, so every DW_TAG_imported_unit is dangling
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
`llvm-dwarfutil` rewrites every `DW_AT_import` to the start offset of the unit it references rather than to that unit's root DIE, so every `DW_TAG_imported_unit` in the output is a dangling reference. For the first unit in the output the emitted value is `0x0`.
The input below has two DWARFv5 compile units: `U01` defines `ns::S` and holds no code, `U02` has the only function and a `DW_TAG_imported_unit` naming `U01`'s root DIE at `0x0c`.
```
$ llvm-dwarfutil repro.o repro.out
$ llvm-dwarfdump --debug-info repro.out
[...]
0x00000041: DW_TAG_imported_unit
DW_AT_import (0x0000000000000000)
```
```
$ llvm-dwarfdump --verify repro.out
error: invalid DIE reference 0x00000000. Offset is in between DIEs:
[...]
error: Invalid DIE reference occurred 1 time(s).
Errors detected.
```
The output violates DWARF v5 on two counts.
Firstly, §3.2.5 "Imported Unit Entries" (page 74) requires the attribute to name a unit, and Appendix E.1.2.2 says which entry that is:
> An imported unit entry contains a `DW_AT_import` attribute whose value is a reference to the normal or partial compilation unit whose declarations logically belong at the place of the imported unit entry.
> A `DW_TAG_imported_unit` debugging information entry has an `DW_AT_import` attribute referencing a `DW_TAG_compile_unit` or `DW_TAG_partial_unit` debugging information entry.
The target is a *debugging information entry*, not a unit header.
Secondly, §7.5.5 "Classes and Forms" (page 217) defines what the form is permitted to designate:
> The second type of reference can identify any debugging information entry within a `.debug_info` section; in particular, it may refer to an entry in a different compilation unit from the unit containing the reference [...] This type of reference (`DW_FORM_ref_addr`) is an offset from the beginning of the `.debug_info` section of the target executable or shared object file
An offset that lands on a unit header identifies no debugging information entry at all, which is precisely what `--verify` reports: the offset is in between DIEs.
Observe, that the emitted value is not garbage. It is the referenced unit's start offset with the root DIE's own offset lost, and zero only because the first unit begins at zero. Chain three units so the imported one is not first and the pattern is plain: `U03` importing `U02` gets `0x1c`, which is `U02`'s unit header, short by exactly the twelve bytes of a 32-bit DWARFv5 header (§7.5.1.1, page 200) against its root DIE at `0x28`.
```
0x0000001c: Compile Unit: length = 0x00000017, [...] (next unit at 0x00000037)
0x00000028: DW_TAG_compile_unit
DW_AT_name ("U02")
[...]
0x0000005f: DW_TAG_imported_unit
DW_AT_import (0x000000000000001c)
```
The cause is that a unit root never gets a `DIEInfo::Clone`. `CompileUnit::createOutputDIE()` builds the output root as a DIE owned by the unit's `DIEUnit`, and `cloneDIE()` is handed that DIE directly (`Classic/DWARFLinker.cpp:2915`), so the `Clone` field stays null for index 0 — the two are mutually exclusive by assertion in `cloneDIE`.
`cloneDieReferenceAttribute()` therefore takes the `!RefInfo.Clone` path (`Classic/DWARFLinker.cpp:1207`) for an import target, manufactures a placeholder `DIE::get(DIEAlloc, ...)` that is never adopted into any unit tree, and records a forward reference to it. `CompileUnit::fixupForwardReferences()` (`Classic/DWARFLinkerCompileUnit.cpp:140`) then emits `RefDie->getOffset() + RefUnit->getStartOffset()`, and the orphan placeholder's offset was never assigned, so the first term is zero and only the unit start offset survives.
Both released builds below have assertions off. With assertions on the linker aborts rather than emitting the bad value:
```
llvm-dwarfutil: llvm/include/llvm/CodeGen/DIE.h:882: unsigned int llvm::DIE::getOffset() const: Assertion `Offset && "Offset being queried before it's been computed."' failed.
#9 llvm::dwarf_linker::classic::CompileUnit::fixupForwardReferences()
#10 llvm::dwarf_linker::classic::DWARFLinker::DIECloner::cloneAllCompileUnits(llvm::DWARFContext&, llvm::dwarf_linker::DWARFFile const&, bool)
```
Scope: the classic linker only. `--linker parallel` resolves the same references correctly. The root tag is irrelevant — a `DW_TAG_partial_unit` root, which is what `dwz` emits and therefore what Fedora, RHEL and Debian debuginfo packaging ships, behaves identically to the compile unit shown here.
Reproduced on Fedora `llvm-dwarfutil` 22.1.8 and on 23.1.0.
**Expected behavior:** `DW_AT_import` holds the `.debug_info` offset of the cloned root DIE of the referenced unit.
Reproducer:
```yaml
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_REL
Machine: EM_X86_64
Sections:
- Name: .text
Type: SHT_PROGBITS
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
Address: 0x1130
Size: 0x10
- Name: .debug_str_offsets
Type: SHT_PROGBITS
Flags: [ ]
Content: "0400000005000000"
DWARF:
debug_abbrev:
- ID: 0
Table:
- Tag: DW_TAG_compile_unit
Children: DW_CHILDREN_yes
Attributes:
- Attribute: DW_AT_producer
Form: DW_FORM_string
- Attribute: DW_AT_name
Form: DW_FORM_string
- Attribute: DW_AT_language
Form: DW_FORM_data2
- Attribute: DW_AT_str_offsets_base
Form: DW_FORM_sec_offset
- Tag: DW_TAG_namespace
Children: DW_CHILDREN_yes
Attributes:
- Attribute: DW_AT_name
Form: DW_FORM_string
- Tag: DW_TAG_structure_type
Children: DW_CHILDREN_no
Attributes:
- Attribute: DW_AT_name
Form: DW_FORM_string
- Attribute: DW_AT_byte_size
Form: DW_FORM_data1
- Tag: DW_TAG_compile_unit
Children: DW_CHILDREN_yes
Attributes:
- Attribute: DW_AT_producer
Form: DW_FORM_string
- Attribute: DW_AT_name
Form: DW_FORM_string
- Attribute: DW_AT_language
Form: DW_FORM_data2
- Attribute: DW_AT_low_pc
Form: DW_FORM_addr
- Attribute: DW_AT_high_pc
Form: DW_FORM_data8
- Attribute: DW_AT_str_offsets_base
Form: DW_FORM_sec_offset
- Tag: DW_TAG_subprogram
Children: DW_CHILDREN_no
Attributes:
- Attribute: DW_AT_name
Form: DW_FORM_string
- Attribute: DW_AT_low_pc
Form: DW_FORM_addr
- Attribute: DW_AT_high_pc
Form: DW_FORM_data8
- Tag: DW_TAG_imported_unit
Children: DW_CHILDREN_no
Attributes:
- Attribute: DW_AT_import
Form: DW_FORM_ref_addr
debug_info:
- Version: 5
UnitType: DW_UT_compile
AbbrevTableID: 0
Entries:
- AbbrCode: 1
Values:
- CStr: by_hand
- CStr: U01
- Value: 0x04
- Value: 0x8
- AbbrCode: 2
Values:
- CStr: ns
- AbbrCode: 3
Values:
- CStr: S
- Value: 0x8
- AbbrCode: 0
- AbbrCode: 0
- Version: 5
UnitType: DW_UT_compile
AbbrevTableID: 0
Entries:
- AbbrCode: 4
Values:
- CStr: by_hand
- CStr: U02
- Value: 0x04
- Value: 0x1130
- Value: 0x10
- Value: 0x8
- AbbrCode: 6
Values:
- Value: 0x0c
- AbbrCode: 5
Values:
- CStr: foo1
- Value: 0x1130
- Value: 0x10
- AbbrCode: 0
...
```
Contributor guide
Research direction
Start with the classic linker paths in Classic/DWARFLinker.cpp, especially cloneDIE() around line 2915 and cloneDieReferenceAttribute() around line 1207, then follow CompileUnit::fixupForwardReferences() in Classic/DWARFLinkerCompileUnit.cpp. Run the supplied YAML reproducer through llvm-dwarfutil and inspect it with llvm-dwarfdump --verify. Done means DW_AT_import points to the cloned root DIE for compile and partial units, with verification succeeding.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100