llvm / llvm/llvm-project

[llvm-dwarfutil] --linker parallel segfaults on a DW_TAG_imported_unit routed into the artificial type unit

Open
#219,622 0 comments 0 reactions 0 assignees View on GitHub
crash llvm-dwarfutil
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

`llvm-dwarfutil --linker parallel` segfaults when a `DW_TAG_partial_unit` contains a `DW_TAG_imported_unit` and ODR deduplication has a type to move. This is the shape `dwz` produces in multifile mode, where the partial units it emits import other partial units.

```
$ llvm-dwarfutil --linker parallel repro.o repro.out
Segmentation fault (core dumped)
```

The input is three DWARFv5 units: `U01` is a partial unit defining `ns::S`, `U02` is a partial unit whose only child imports `U01`, and `U03` is a compile unit that imports `U02` and holds the only function, which returns `ns::S`.

With assertions on the failure is:

```
llvm-dwarfutil: llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp:282: size_t llvm::dwarf_linker::parallel::DIEAttributeCloner::cloneDieRefAttr(const DWARFFormValue &, const DWARFAbbreviationDeclaration::AttributeSpec &): Assertion `RefTypeName && "Type name for referenced DIE is not set"' failed.
#9 llvm::dwarf_linker::parallel::DIEAttributeCloner::cloneDieRefAttr(...)
#10 llvm::dwarf_linker::parallel::DIEAttributeCloner::clone()
#11 llvm::dwarf_linker::parallel::CompileUnit::createTypeDIEandCloneAttributes(...)
#12 llvm::dwarf_linker::parallel::CompileUnit::cloneDIE(...)
```

`DependencyTracker::collectRootsToKeep` decides where to place an import DIE by testing the tag of the DIE that contains it (`Parallel/DependencyTracker.cpp:224`):

```cpp
case dwarf::DW_TAG_imported_unit: {
// Always keep DIEs having DW_AT_import attribute.
if (Entry.DieEntry->getTag() == dwarf::DW_TAG_compile_unit) {
addActionToRootEntriesWorkList(
LiveRootWorklistActionTy::MarkSingleLiveEntry, ChildEntry,
ReferencedBy);
break;
}

addActionToRootEntriesWorkList(
LiveRootWorklistActionTy::MarkSingleTypeEntry, ChildEntry,
ReferencedBy);
```

The question being asked is whether the containing DIE is the unit root, and the tag answers it only for `DW_TAG_compile_unit`. Under a `DW_TAG_partial_unit` root the import falls through to `MarkSingleTypeEntry`, so it is given type table placement and cloned into `__artificial_type_unit`. Cloning it there requires a type name for its `DW_AT_import` target, and that target is a unit root, which is never assigned one.

Two controls, both clean on the same builds:

1. the same input with every root spelled `DW_TAG_compile_unit`;
2. the same partial unit input with nothing referencing `ns::S`, so no artificial type unit is built at all.

Reproduced on Fedora `llvm-dwarfutil` 22.1.8 and on 23.1.0.

**The same abort is reachable a second way, and the root tag plays no part in that one.** Placement is decided for `DW_TAG_imported_module`, `DW_TAG_imported_declaration` and `DW_TAG_imported_unit` together, and asking whether the import sits directly under the unit root is the right question for the first two, whose targets may be deduplicated. It is the wrong question for `DW_TAG_imported_unit`: `DW_AT_import` on such an entry always names a unit root, and a unit root is never a type deduplication candidate, since `getTypeDeduplicationCandidate` returns `nullopt` for all four unit root tags. An imported unit entry nested one level deeper, inside a namespace, is therefore given type table placement and aborts identically.

Moving the `DW_TAG_imported_unit` in `U02` into a `DW_TAG_namespace` and changing nothing else reproduces it, and it reproduces with every root spelled `DW_TAG_compile_unit` too, so control 1 above stops being clean as soon as the import is nested. That half of the defect predates the partial unit work rather than following from it.

**Expected behavior:** a `DW_TAG_imported_unit` is kept in plain DWARF whatever tag its unit root carries and whatever depth it sits at, because its target can never be a type unit candidate.

Testing the root by position rather than by tag removes the crash on the input below and makes both imports resolve — `DW_AT_import (0x0000000000000034 "U01")` and `(0x0000000000000050 "U02")`. Covering the nested case as well needs the imported unit placement decided by what its target is rather than by where the entry sits.

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_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_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
- 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_ref_addr
debug_info:
- Version: 5
UnitType: DW_UT_partial
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_partial
AbbrevTableID: 0
Entries:
- AbbrCode: 1
Values:
- CStr: by_hand
- CStr: U02
- Value: 0x04
- Value: 0x8
- AbbrCode: 6
Values:
- Value: 0x0c
- AbbrCode: 0
- Version: 5
UnitType: DW_UT_compile
AbbrevTableID: 0
Entries:
- AbbrCode: 4
Values:
- CStr: by_hand
- CStr: U03
- Value: 0x04
- Value: 0x1130
- Value: 0x10
- Value: 0x8
- AbbrCode: 6
Values:
- Value: 0x35
- AbbrCode: 7
Values:
- CStr: foo1
- Value: 0x1130
- Value: 0x10
- Value: 0x23
- AbbrCode: 0
...
```

Contributor guide

Open the contributing guide

Research direction

Start at llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp:224 and DIEAttributeCloner.cpp:282, then run the supplied YAML reproducer with llvm-dwarfutil --linker parallel. Compare placement for imported units at partial-unit and nested roots; done means the command no longer crashes and both DW_AT_import references resolve, with the nested case covered.

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
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.