llvm / llvm/llvm-project

[DebugInfo] DIE::getUnitDie() omits DW_TAG_partial_unit, so a DW_FORM_ref_addr into a partial unit aborts the DWARFLinker

Open
#219,650 1 comment 0 reactions 0 assignees View on GitHub
debuginfo
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.