[llvm-dwarfutil] --linker parallel crashes on a type in a namespace under a DW_TAG_partial_unit root
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Summary
`llvm-dwarfutil --linker parallel` crashes on any input whose unit root is a `DW_TAG_partial_unit` and that defines a type inside a namespace. A build with assertions fails `assert(Entry != nullptr)` in `CompileUnit::createTypeDIEandCloneAttributes`; a release build dereferences that null pointer and segfaults. The classic backend links the same input without complaint, and the same input with a `DW_TAG_compile_unit` root links on both backends.
`DW_TAG_partial_unit` is what `dwz` leaves behind, and `dwz` is a standard step in distribution debuginfo packaging (Fedora and RHEL run it from `find-debuginfo`, Debian from `dh_dwz`), so a type in a namespace under a partial unit root is the ordinary shape of C++ debuginfo as a distribution ships it.
## Reproducer
The input below is one partial unit holding a single function that returns `ns::S`.
```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:
- Table:
- Tag: DW_TAG_partial_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
- Attribute: DW_AT_type
Form: DW_FORM_ref4
- 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_yes
Attributes:
- Attribute: DW_AT_name
Form: DW_FORM_string
- Attribute: DW_AT_byte_size
Form: DW_FORM_data1
- Tag: DW_TAG_member
Children: DW_CHILDREN_no
Attributes:
- Attribute: DW_AT_name
Form: DW_FORM_string
- Attribute: DW_AT_type
Form: DW_FORM_ref4
- Attribute: DW_AT_data_member_location
Form: DW_FORM_data1
- Tag: DW_TAG_base_type
Children: DW_CHILDREN_no
Attributes:
- Attribute: DW_AT_name
Form: DW_FORM_string
debug_info:
- Version: 5
UnitType: DW_UT_partial
Entries:
- AbbrCode: 1
Values:
- CStr: by_hand
- CStr: U01
- Value: 0x04
- Value: 0x1130
- Value: 0x10
- Value: 0x8
- AbbrCode: 2
Values:
- CStr: foo1
- Value: 0x1130
- Value: 0x10
- Value: 0x4d
- AbbrCode: 3
Values:
- CStr: ns
- AbbrCode: 4
Values:
- CStr: S
- Value: 0x8
- AbbrCode: 5
Values:
- CStr: m1
- Value: 0x5c
- Value: 0x0
- AbbrCode: 0
- AbbrCode: 0
- AbbrCode: 6
Values:
- CStr: int
- AbbrCode: 0
...
```
A build of `main` with assertions:
```console
$ yaml2obj partial.yaml -o partial.o
$ llvm-dwarfutil --linker parallel partial.o partial.out
llvm-dwarfutil: llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp:1608: TypeEntry *llvm::dwarf_linker::parallel::CompileUnit::createTypeDIEandCloneAttributes(const DWARFDebugInfoEntry *, DIEGenerator &, TypeEntry *, TypeUnit *, uint32_t): Assertion `Entry != nullptr' failed.
Aborted (core dumped)
```
A release build of 23.1.0, where the same null pointer is dereferenced instead:
```console
$ llvm-dwarfutil --linker parallel partial.o partial.out
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace and instructions to reproduce the bug.
#4 llvm::dwarf_linker::parallel::TypePool::getOrCreateTypeEntryBody(...) llvm/lib/DWARFLinker/Parallel/TypePool.h:154:44
#5 llvm::dwarf_linker::parallel::CompileUnit::createTypeDIEandCloneAttributes(...) llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp:1513:41
#6 llvm::dwarf_linker::parallel::CompileUnit::cloneDIE(...) llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp:0:24
#7 llvm::dwarf_linker::parallel::CompileUnit::cloneAndEmit(...) llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp:1275:3
Segmentation fault (core dumped)
```
Substituting `DW_TAG_compile_unit`/`DW_UT_compile` for `DW_TAG_partial_unit`/`DW_UT_partial` in the same input links cleanly, as does the classic backend on the input as written. Dropping the namespace and leaving `S` directly under the partial unit root also links, because the walk described below then runs past the unit root and the type never becomes a deduplication candidate at all, so no ancestor is ever marked.
The crash needs type deduplication, which is on by default: `--no-odr-deduplication` and update mode (`--no-garbage-collection --build-accelerator=DWARF`, which turns deduplication off) both link. It is not a race, `--num-threads 1` crashes identically.
## Analysis
`getRootForSpecifiedEntry` walks up from a referenced type until `isNamespaceLikeEntry` returns true, and a namespace terminates that walk, so `ns::S` is a deduplication candidate. Marking it walks its ancestors and sets `KeepTypeChildren` on each one, unit root included, whether or not that ancestor is itself a candidate:
https://github.com/llvm/llvm-project/blob/6d890e71354accdc496fdd9ef4f1fce8b366c7c4/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp#L471-L491
`needToPlaceInTypeTable` is true for any DIE carrying that flag:
https://github.com/llvm/llvm-project/blob/6d890e71354accdc496fdd9ef4f1fce8b366c7c4/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h#L321-L325
The only thing that then keeps a unit root out of the artificial type unit is a test on the root's tag:
https://github.com/llvm/llvm-project/blob/6d890e71354accdc496fdd9ef4f1fce8b366c7c4/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp#L1420-L1423
```cpp
bool NeedToCloneTypeDIE =
(InputDieEntry->getTag() != dwarf::DW_TAG_compile_unit) &&
Info.needToPlaceInTypeTable();
```
`DWARFContext::compile_units()` filters out only type units, so a `DW_TAG_partial_unit` root reaches this code on the same path a full compilation unit does. The tag test is the only thing separating them, and a partial unit root passes it and is cloned as a type DIE. `createTypeDIEandCloneAttributes` then asks for the type entry of DIE index 0, which no unit root is ever assigned:
https://github.com/llvm/llvm-project/blob/6d890e71354accdc496fdd9ef4f1fce8b366c7c4/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp#L1605-L1609
```cpp
TypeEntry *Entry = getDieTypeEntry(InputDieIdx);
assert(Entry != nullptr);
```
`getOrCreateTypeEntryBody` dereferences that null pointer where assertions are compiled out.
## Expected behavior
`llvm-dwarfutil --linker parallel` links a `DW_TAG_partial_unit` root the way it links a `DW_TAG_compile_unit` root, and places no unit root in the artificial type unit.
## Versions
Reproduced at 6d890e71354accdc496fdd9ef4f1fce8b366c7c4 on `main`, x86_64 Linux, Release with assertions. Released builds segfault on the same input: `llvm-dwarfutil` 22.1.8 as shipped by Fedora 44, and a local 23.1.0 build. `dsymutil` shares this code and enables ODR deduplication by default.
This report was produced with AI assistance; I have reviewed it and am accountable for it.
Contributor guide
Research direction
Start by running the YAML reproducer with yaml2obj and llvm-dwarfutil --linker parallel, then read llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp, DWARFLinkerCompileUnit.h, and DWARFLinkerCompileUnit.cpp at the cited locations. Compare the partial-unit and compile-unit paths, and verify completion when the reproducer links without an assertion or segmentation fault and no unit root enters the artificial type unit.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 52/100