[llvm] [DWARFLinker][Parallel] Keep imports under any unit root in plain DWARF (PR #219732)

via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 29 15:44:33 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-debuginfo

Author: Arcadiy Ivanov (arcivanov)

<details>
<summary>Changes</summary>

Depends on #<!-- -->219615.

An imported unit, module or declaration entry is kept in plain DWARF when it sits directly under the unit root, and stays a candidate for the artificial type unit everywhere else. `collectRootsToKeep` decided between plain DWARF and the type table by asking whether the DIE holding the import was tagged `DW_TAG_compile_unit`.

`DW_TAG_partial_unit` is the root tag `dwz` emits, and in multifile mode the partial units `dwz` produces import one another, so this is the ordinary shape of a Fedora, RHEL or Debian debuginfo package. A partial unit root is not tagged `DW_TAG_compile_unit`, so the import under it is given type table placement, cloned into `__artificial_type_unit`, and cloning it there asks for the type name of its `DW_AT_import` target. That target is a unit root, which is never assigned a type name — an assertion in `cloneDieRefAttr` where assertions are on, a null dereference and SIGSEGV where they are not. Reaching that crash also needs ODR deduplication to have a type to move, so a partial unit on its own is not enough; the reproducer in the issue segfaults stock `llvm-dwarfutil` 22.1.8 and 23.1.0.

`collectRootsToKeep` now tests the DIE index instead of the tag. The unit root is always at index zero in `DWARFUnit`'s DIE array whatever tag the root carries — `getUnitDIE()` is defined as `DieArray[0]` — which is the invariant the tag comparison stood in for. `CompileUnit::isUnitRootDIE` is the helper #<!-- -->219615 introduces for the two `cloneDIE()` gates, reused here rather than respelled.

Only the parallel backend is affected. Classic leaves the type where it found it and does not go near this code.

The test links a `DW_TAG_compile_unit` root alongside as the control and pins both halves of the placement decision: two imported unit entries under unit roots, which have to stay in plain DWARF, and a using-declaration nested in a namespace, which has to remain a type table candidate. The nested half is worth the extra input, because it had no coverage anywhere in the tree — dropping the condition outright, so that every import is kept in plain DWARF, still passed all 182 existing `llvm-dwarfutil` and `dsymutil` tests. Against the input added here, that same variant fails on both roots.

The classic backend is not linked in the test. Classic aborts on this input for an unrelated reason, rewriting `DW_AT_import` to the referenced unit's start offset rather than to its root DIE, and it does so whatever tag the root carries, so it fails the control too. That defect is #<!-- -->219621.

Fixes #<!-- -->219622.


---

Patch is 25.14 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/219732.diff


6 Files Affected:

- (modified) llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp (+2-4) 
- (modified) llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h (+5) 
- (modified) llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp (+4-2) 
- (added) llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-imported-unit-odr.test (+222) 
- (added) llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-namespace.test (+178) 
- (added) llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-types-only.test (+168) 


``````````diff
diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
index 4138a9fd69aca..126cac340a49d 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
@@ -1419,8 +1419,7 @@ std::pair<DIE *, TypeEntry *> CompileUnit::cloneDIE(
 
   bool NeedToClonePlainDIE = Info.needToKeepInPlainDwarf();
   bool NeedToCloneTypeDIE =
-      (InputDieEntry->getTag() != dwarf::DW_TAG_compile_unit) &&
-      Info.needToPlaceInTypeTable();
+      !isUnitRootDIE(InputDieIdx) && Info.needToPlaceInTypeTable();
   std::pair<DIE *, TypeEntry *> ClonedDIE;
 
   DIEGenerator PlainDIEGenerator(Allocator, *this);
@@ -1449,8 +1448,7 @@ std::pair<DIE *, TypeEntry *> CompileUnit::cloneDIE(
       (ClonedDIE.first && Info.getKeepPlainChildren());
 
   bool HasTypeChildrenToClone =
-      ((ClonedDIE.second ||
-        InputDieEntry->getTag() == dwarf::DW_TAG_compile_unit) &&
+      ((ClonedDIE.second || isUnitRootDIE(InputDieIdx)) &&
        Info.getKeepTypeChildren());
 
   // Recursively clone children.
diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h
index c8c1b8822aa64..70b90727f4df4 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h
@@ -335,6 +335,11 @@ class alignas(8) CompileUnit : public DwarfUnit {
   ///
   /// @{
 
+  /// \p DieIdx index of the DIE.
+  /// \returns true if the DIE is the unit's root, which is always at index
+  /// zero, whatever tag it carries.
+  static bool isUnitRootDIE(uint32_t DieIdx) { return DieIdx == 0; }
+
   /// \p Idx index of the DIE.
   /// \returns DieInfo descriptor.
   DIEInfo &getDIEInfo(unsigned Idx) { return DieInfoArray[Idx]; }
diff --git a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
index 669ea2ceb16c9..7cacc3fc42dc4 100644
--- a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
@@ -220,8 +220,10 @@ void DependencyTracker::collectRootsToKeep(
     case dwarf::DW_TAG_imported_module:
     case dwarf::DW_TAG_imported_declaration:
     case dwarf::DW_TAG_imported_unit: {
-      // Always keep DIEs having DW_AT_import attribute.
-      if (Entry.DieEntry->getTag() == dwarf::DW_TAG_compile_unit) {
+      // Always keep DIEs having DW_AT_import attribute. An import placed
+      // directly under the unit root belongs in plain DWARF whatever tag that
+      // root carries, so the root is identified by position.
+      if (CompileUnit::isUnitRootDIE(Entry.CU->getDIEIndex(Entry.DieEntry))) {
         addActionToRootEntriesWorkList(
             LiveRootWorklistActionTy::MarkSingleLiveEntry, ChildEntry,
             ReferencedBy);
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-imported-unit-odr.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-imported-unit-odr.test
new file mode 100644
index 0000000000000..c5cf5a6757776
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-imported-unit-odr.test
@@ -0,0 +1,222 @@
+## An import sitting directly under a unit root is kept in plain DWARF, and
+## everywhere else it stays a candidate for the artificial type unit. The
+## linker chose between plain DWARF and the type table by the tag of the DIE
+## holding the import, and only DW_TAG_compile_unit was recognized there.
+## Under a DW_TAG_partial_unit root - the shape dwz produces in multifile mode,
+## where the partial units it emits import one another - an imported unit entry
+## was given type table placement instead, and cloning it there asks for the
+## type name of its DW_AT_import target. That target is a unit root, which is
+## never assigned a type name. A DW_TAG_compile_unit root is linked alongside
+## as the control.
+##
+## U01:  struct T {};
+##       namespace ns { struct S {}; using ::T; }
+## U02:  DW_TAG_imported_unit -> U01
+## U03:  DW_TAG_imported_unit -> U02
+##       ns::S foo1() { ... }        // DW_FORM_ref_addr reference into U01
+##
+## Reaching the failure needs ODR deduplication to have a type to move, which
+## is what foo1 and its return type are for; with nothing referencing ns::S no
+## artificial type unit is built and the import is never cloned into one. The
+## using-declaration nested in the namespace is the other half of the same
+## decision, and is the only coverage in the tree for an import that must stay
+## a type table candidate.
+##
+## The classic backend is not linked here. It aborts on this input for an
+## unrelated reason, rewriting DW_AT_import to the referenced unit's start
+## offset rather than to its root DIE, and it does so whatever tag the root
+## carries, so it fails the control too.
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_compile_unit -DUNITTYPE=DW_UT_compile -o %t.cu.o
+# RUN: llvm-dwarfutil --linker parallel %t.cu.o %t.cu.out
+# RUN: llvm-dwarfdump --debug-info %t.cu.out | FileCheck %s -DROOT=DW_TAG_compile_unit
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_partial_unit -DUNITTYPE=DW_UT_partial -o %t.pu.o
+# RUN: llvm-dwarfutil --linker parallel %t.pu.o %t.pu.out
+# RUN: llvm-dwarfdump --debug-info %t.pu.out | FileCheck %s -DROOT=DW_TAG_partial_unit
+
+## Both types move into the artificial type unit, so deduplication did engage
+## and the imports were cloned while there was a type unit to clone them into.
+## The using-declaration moves with them and its target is rewritten to the
+## copy of T that landed there.
+# CHECK:           DW_AT_name ("__artificial_type_unit")
+# CHECK:           DW_TAG_namespace
+# CHECK-NEXT:        DW_AT_name ("ns")
+# CHECK:             DW_TAG_imported_declaration
+# CHECK-NEXT:          DW_AT_import (0x[[T:[0-9a-f]{8}]] "T")
+# CHECK:      0x[[S:[0-9a-f]{8}]]:     DW_TAG_structure_type
+# CHECK-NEXT:          DW_AT_name ("S")
+# CHECK:      0x[[T]]:   DW_TAG_structure_type
+# CHECK-NEXT:          DW_AT_name ("T")
+
+## Neither imported unit entry went with them. Both stay in the unit that held
+## them, and both resolve to the root DIE of the unit they name rather than to
+## a DIE that was dropped or never given an offset.
+# CHECK-NOT:  DW_TAG_imported_unit
+# CHECK:      0x[[U01:[0-9a-f]{8}]]: [[ROOT]]
+# CHECK-NEXT:        DW_AT_producer ("by_hand")
+# CHECK-NEXT:        DW_AT_name ("U01")
+# CHECK-NOT:  DW_TAG_imported_unit
+# CHECK:      0x[[U02:[0-9a-f]{8}]]: [[ROOT]]
+# CHECK-NEXT:        DW_AT_producer ("by_hand")
+# CHECK-NEXT:        DW_AT_name ("U02")
+# CHECK:             DW_TAG_imported_unit
+# CHECK-NEXT:          DW_AT_import (0x00000000[[U01]] "U01")
+# CHECK:           DW_TAG_compile_unit
+# CHECK-NEXT:        DW_AT_producer ("by_hand")
+# CHECK-NEXT:        DW_AT_name ("U03")
+# CHECK:             DW_TAG_imported_unit
+# CHECK-NEXT:          DW_AT_import (0x00000000[[U02]] "U02")
+# CHECK:             DW_TAG_subprogram
+# CHECK-NEXT:          DW_AT_name ("foo1")
+# CHECK:               DW_AT_type (0x00000000[[S]] "ns::S")
+
+## Three DWARFv5 units. The first defines the types, the second does nothing
+## but import the first, and the third imports the second and holds the only
+## function, which returns S through a DW_FORM_ref_addr reference into the
+## first. Each root carries its own DW_AT_str_offsets_base, so the strings the
+## linker rewrites into DW_FORM_strx stay resolvable on output; the linker does
+## not synthesize that attribute for a partial unit root by itself.
+--- !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:      [[ROOT]]
+        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_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
+      - Tag:      DW_TAG_imported_declaration
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_import
+            Form:      DW_FORM_ref_addr
+  debug_info:
+    - Version:  5
+      UnitType: [[UNITTYPE]]
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U01
+            - Value: 0x04
+            - Value: 0x8
+        - AbbrCode: 3
+          Values:
+            - CStr: T
+            - Value: 0x8
+        - AbbrCode: 2
+          Values:
+            - CStr: ns
+        - AbbrCode: 3
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 7
+          Values:
+            - Value: 0x1f
+        - AbbrCode: 0
+        - AbbrCode: 0
+    - Version:  5
+      UnitType: [[UNITTYPE]]
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U02
+            - Value: 0x04
+            - Value: 0x8
+        - AbbrCode: 5
+          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: 5
+          Values:
+            - Value: 0x3e
+        - AbbrCode: 6
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x27
+        - AbbrCode: 0
+...
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-namespace.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-namespace.test
new file mode 100644
index 0000000000000..357d829a34b2c
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-namespace.test
@@ -0,0 +1,178 @@
+## A DW_TAG_partial_unit root - what dwz emits, and what Fedora, RHEL and
+## Debian debuginfo packaging therefore ships - was kept out of the artificial
+## type unit by nothing but a test on its tag, so it was cloned as a type DIE
+## and asked for the type entry that no unit root is ever assigned. Reaching
+## that point needs a scope between the type and the unit root, because the
+## walk that looks for a type's enclosing scope stops at a namespace but runs
+## past a unit root. A DW_TAG_compile_unit root is linked alongside as the
+## control.
+##
+## namespace ns {
+##   struct S { int m1; };
+## }
+##
+## ns::S foo1() { ... }
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_compile_unit -DUNITTYPE=DW_UT_compile -o %t.cu.o
+# RUN: llvm-dwarfutil %t.cu.o %t.cu.classic
+# RUN: llvm-dwarfdump --debug-info %t.cu.classic | FileCheck %s --check-prefixes=CLASSIC,CLASSIC-CU
+# RUN: llvm-dwarfutil --linker parallel %t.cu.o %t.cu.parallel
+# RUN: llvm-dwarfdump --debug-info %t.cu.parallel | FileCheck %s --check-prefixes=PARALLEL,PARALLEL-CU
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_partial_unit -DUNITTYPE=DW_UT_partial -o %t.pu.o
+# RUN: llvm-dwarfutil %t.pu.o %t.pu.classic
+# RUN: llvm-dwarfdump --debug-info %t.pu.classic | FileCheck %s --check-prefixes=CLASSIC,CLASSIC-PU
+# RUN: llvm-dwarfutil --linker parallel %t.pu.o %t.pu.parallel
+# RUN: llvm-dwarfdump --debug-info %t.pu.parallel | FileCheck %s --check-prefixes=PARALLEL,PARALLEL-PU
+
+## The classic backend leaves the type where it found it, and the root tag
+## survives linking. llvm-dwarfdump qualifies a type name with the names of its
+## enclosing scopes and a partial unit root is one, which a compile unit root
+## is not, so the type is printed as "U01::ns::S" for the partial unit run and
+## as "ns::S" for the control. The reference target is the same DIE either way.
+# CLASSIC-CU:     DW_TAG_compile_unit
+# CLASSIC-PU:     DW_TAG_partial_unit
+# CLASSIC:        DW_TAG_subprogram
+# CLASSIC-NEXT:     DW_AT_name ("foo1")
+# CLASSIC-CU:       DW_AT_type (0x00000000[[S:[0-9a-f]{8}]] "ns::S")
+# CLASSIC-PU:       DW_AT_type (0x00000000[[S:[0-9a-f]{8}]] "U01::ns::S")
+# CLASSIC:        DW_TAG_namespace
+# CLASSIC-NEXT:     DW_AT_name ("ns")
+# CLASSIC:      0x[[S]]:     DW_TAG_structure_type
+# CLASSIC-NEXT:   DW_AT_name ("S")
+# CLASSIC:          DW_TAG_member
+# CLASSIC-NEXT:       DW_AT_name ("m1")
+
+## The parallel backend puts the type in the artificial type unit, under a copy
+## of the namespace that held it, and the unit's reference resolves there. The
+## unit root itself stays out of that unit, which is what this pins: it has no
+## name of its own to be deduplicated under, and cloning it as a type DIE is
+## what used to crash.
+# PARALLEL:      DW_AT_name ("__artificial_type_unit")
+# PARALLEL:      DW_TAG_namespace
+# PARALLEL-NEXT:   DW_AT_name ("ns")
+# PARALLEL:      0x[[S:[0-9a-f]{8}]]:     DW_TAG_structure_type
+# PARALLEL-NEXT:   DW_AT_name ("S")
+# PARALLEL:          DW_TAG_member
+# PARALLEL-NEXT:       DW_AT_name ("m1")
+# PARALLEL-CU:   DW_TAG_compile_unit
+# PARALLEL-PU:   DW_TAG_partial_unit
+# PARALLEL-NEXT:   DW_AT_producer ("by_hand")
+# PARALLEL-NEXT:   DW_AT_name ("U01")
+# PARALLEL:      DW_TAG_subprogram
+# PARALLEL-NEXT:   DW_AT_name ("foo1")
+# PARALLEL:        DW_AT_type (0x00000000[[S]] "ns::S")
+
+## One DWARFv5 unit with a function in .text returning struct S, which is
+## defined in namespace ns. The root carries its own DW_AT_str_offsets_base, so
+## the strings the linker rewrites into DW_FORM_strx stay resolvable on output;
+## the linker does not synthesize that attribute for a partial unit root by
+## itself.
+--- !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:      [[ROOT]]
+        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: [[UNITTYPE]]
+      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
+...
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-types-only.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-types-only.test
new file mode 100644
index 0000000000000..312b8d0e1921c
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-types-only.test
@@ -0,0 +1,168 @@
+## A unit root is never assigned a type entry, so it is never cloned into the
+## artificial type unit and the recursion into its type children is allowed by
+## a separate test - which named DW_TAG_compile_unit only. A
+## DW_TAG_partial_unit root holding types and no code, the unit dwz produces to
+## carry the definitions it hoisted out of the compile units, therefore reached
+## neither branch and its subtree was dropped after the type name had already
+## been registered. A DW_TAG_compile_unit root is linked alongside as the
+## control.
+##
+## U01:  namespace ns { struct S {}; }
+## U02:  ns::S foo1() { ... }   // DW_FORM_ref_addr reference into U01
+##
+## The classic backend is not linked here. It fails on this input for an
+## unrelated reason, a cross-unit reference to a DIE it never gave an output
+## unit, which is a separate defect from the one this test covers.
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_compile_unit -DUNITTYPE=DW_UT_compile -o %t.cu.o
+# RUN: llvm-dwarfutil --linker parallel %t.cu.o %t.cu.out
+# RUN: llvm-dwarfdump --debug-info %t.cu.out | FileCheck %s --implicit-check-not='DW_AT_name ("U01")'
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_partial_unit -DUNITTYPE=DW_UT_partial -o %t.pu.o
+# RUN: llvm-dwarfutil --linker parallel %t.pu.o %t....
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/219732


More information about the llvm-commits mailing list