[DebugInfo] DIE::getUnitDie() omits DW_TAG_partial_unit, so a DW_FORM_ref_addr into a partial unit aborts the DWARFLinker
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Summary
`DIE::getUnitDie()` finds the unit root of an output DIE tree by walking up the parents and testing each tag against `DW_TAG_compile_unit`, `DW_TAG_type_unit` and `DW_TAG_skeleton_unit`. `DW_TAG_partial_unit`, the fourth unit root tag and the one `dwz` produces, is not in that list, so the walk runs off the top of the tree and returns null. `DIE::getUnit()` returns null in turn, and `DIE::getDebugSectionOffset()` asserts.
Every `DW_FORM_ref_addr` reference into a partial unit therefore aborts the DWARFLinker. The reproducer below is a `DW_AT_type` on a subprogram naming a structure that lives inside a partial unit, which is the ordinary shape of `dwz` output: the shared types are hoisted into a partial unit and the compilation units point at them across unit boundaries.
`dwz` is a standard step in Fedora, RHEL and Debian debuginfo packaging, so this is not a corner case.
## Reproducer
`U01` is a `DW_TAG_partial_unit` holding `ns::S`. `U02` is an ordinary compilation unit whose `foo2` has a `DW_AT_type` of form `DW_FORM_ref_addr` naming `S`.
```console
$ llvm-dwarfutil refaddr-pu.o refaddr-pu.out
llvm-dwarfutil: llvm/lib/CodeGen/AsmPrinter/DIE.cpp:187: uint64_t llvm::DIE::getDebugSectionOffset() const: Assertion `Unit && "DIE must be owned by a DIEUnit to get its absolute offset"' failed.
#9 llvm::DIEEntry::emitValue(llvm::AsmPrinter const*, llvm::dwarf::Form) const
#10 llvm::AsmPrinter::emitDwarfDIE(llvm::DIE const&) const
#11 llvm::AsmPrinter::emitDwarfDIE(llvm::DIE const&) const
#12 llvm::dwarf_linker::classic::DwarfStreamer::emitDIE(llvm::DIE&)
#13 llvm::dwarf_linker::classic::DWARFLinker::DIECloner::cloneAllCompileUnits(llvm::DWARFContext&, llvm::dwarf_linker::DWARFFile const&, bool)
```
The input itself is well formed, and the reference resolves:
```console
$ llvm-dwarfdump --debug-info refaddr-pu.o
0x0000000c: DW_TAG_partial_unit
DW_AT_name ("U01")
[...]
0x00000049: DW_TAG_structure_type
DW_AT_name ("S")
[...]
0x0000007e: DW_TAG_subprogram
DW_AT_name ("foo2")
DW_AT_type (0x0000000000000049 "U01::ns::S")
```
Only `DW_FORM_ref_addr` reaches this. `DIEEntry::emitValue` asks for a section-absolute offset for that form alone; the unit-relative forms emit `getOffset()` and are unaffected.
## Analysis
https://github.com/llvm/llvm-project/blob/d34be0ff2eb68d0e486016d8278695fce7d80c3f/llvm/lib/CodeGen/AsmPrinter/DIE.cpp#L191-L201
```cpp
const DIE *DIE::getUnitDie() const {
const DIE *p = this;
while (p) {
if (p->getTag() == dwarf::DW_TAG_compile_unit ||
p->getTag() == dwarf::DW_TAG_skeleton_unit ||
p->getTag() == dwarf::DW_TAG_type_unit)
return p;
p = p->getParent();
}
return nullptr;
}
```
https://github.com/llvm/llvm-project/blob/d34be0ff2eb68d0e486016d8278695fce7d80c3f/llvm/lib/CodeGen/AsmPrinter/DIE.cpp#L203-L208
```cpp
DIEUnit *DIE::getUnit() const {
const DIE *UnitDie = getUnitDie();
if (UnitDie)
return dyn_cast_if_present(UnitDie->Owner);
return nullptr;
}
```
Three unit root tags are enumerated and the fourth is absent.
The tag test is also the wrong instrument for the question being asked. A `DIEUnit` sets `Owner` on exactly one DIE, its unit DIE, so the walk is looking for the DIE that carries a `DIEUnit *` owner rather than for a particular tag. `dyn_cast_if_present(p->Owner)` answers that directly and is correct for every root tag, present and future.
## Specification
DWARF Version 5, February 13, 2017.
Section 3.1.1 "Full and Partial Compilation Unit Entries" (page 60) makes `DW_TAG_compile_unit` and `DW_TAG_partial_unit` both unit roots:
> A full compilation unit is represented by a debugging information entry with the tag `DW_TAG_compile_unit`. A partial compilation unit is represented by a debugging information entry with the tag `DW_TAG_partial_unit`.
Section 3.1.1 (page 60) also names the technique that produces this shape:
> In a compilation employing the DWARF space compression and duplicate elimination techniques from Appendix E.1 on page 365, multiple compilation units using the tags `DW_TAG_compile_unit`, `DW_TAG_partial_unit` and/or `DW_TAG_type_unit` are used to represent portions of an object file.
Section 7.5.5 "Classes and Forms" (page 217) defines what the failing form has to produce:
> 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
Computing that offset is exactly what `getDebugSectionOffset()` does, and it is unable to for any DIE under a partial unit root.
## Expected behavior
A `DW_FORM_ref_addr` reference into a partial unit emits the section-absolute offset of the referenced DIE, as it does for every other unit root tag.
## Versions
The code site is unchanged on `main` as of d34be0ff2eb68d0e486016d8278695fce7d80c3f, and a build of that tree aborts as shown. Released builds have assertions off, where `getUnit()` returns null and the null is dereferenced.
This report was produced with AI assistance; I have reviewed it and am accountable for it.
Contributor guide
Research direction
Start in llvm/lib/CodeGen/AsmPrinter/DIE.cpp at DIE::getUnitDie() and DIE::getUnit(), then run the llvm-dwarfutil refaddr-pu.o refaddr-pu.out reproducer. Done means a DW_FORM_ref_addr into a DW_TAG_partial_unit no longer aborts and emits the referenced DIE's section-absolute offset.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100