[llvm] [DWARFLinker] Point DW_AT_import at the unit root, not the unit header (PR #219721)

Arcadiy Ivanov via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 29 13:20:59 PDT 2026


https://github.com/arcivanov created https://github.com/llvm/llvm-project/pull/219721

Depends on #219660.
Depends on #219398.

---

`DW_AT_import` names the root debugging information entry of the imported unit. The classic linker rewrote it to the offset of that unit's *header* instead, so every `DW_TAG_imported_unit` in the output pointed at the bytes before a DIE rather than at one. When the imported unit came first the value was `0x0`; otherwise it was short of the root by the size of the header.

`llvm-dwarfdump --verify` calls the result `invalid DIE reference ... Offset is in between DIEs`. A released build emits it and exits 0, so the corruption is silent; an assertions build aborts in `fixupForwardReferences()` instead, at `DIE.h:882`.

**Cause.** A unit root never gets a `DIEInfo::Clone`. `createOutputDIE()` makes the output root a DIE owned by the unit's own `BasicDIEUnit`, and `cloneDIE()` is handed that DIE directly rather than allocating one. `cloneDieReferenceAttribute()` only knew about the `DIEInfo::Clone` path, so for a reference to a root it took the "not cloned yet" branch and manufactured a placeholder. That placeholder is never adopted into a unit tree, so nothing ever assigns it an offset, and the reference resolved to whatever the unit started at.

**The fix, first half.** A reference whose target is the unit root resolves through that unit's output DIE, identified positionally as index 0 rather than by tag. Dropping the attribute when the referenced unit is not in the output keeps a reference to a discarded unit from naming something arbitrary.

**The fix, second half.** That output DIE has to exist before any unit is cloned, because the unit holding the reference may be cloned before the unit it names. Creating it therefore moves out of the per-unit loop into a pre-pass over exactly the units the loop goes on to emit. The guard is deliberately the same one the emission loop uses, so no unit that will be dropped gains an output DIE — creating one for a dropped unit leaves `getDebugInfoSectionSize()` disagreeing with the recorded start offset, which is asserted.

Both halves are load-bearing, checked by reverting each on its own against the full test: without the pre-pass the forward import silently resolves to the wrong offset and the `CHECK-NEXT` fails, and without the reference change the linker aborts in `cloneDIE()` on `Can't supply a DIE and a cloned DIE`.

**Tests.** The tag plays no part in any of this, so `imported-unit.test` covers a `DW_TAG_compile_unit` root as well as the `DW_TAG_partial_unit` root that `dwz` produces. Each document carries both reference directions — U01 imports a unit cloned after it, U03 imports a unit cloned before it — pinned by a single capture, so neither direction can be satisfied alone. The DWARFv4 document is there because its unit header is eleven bytes where DWARFv5's is twelve, which no offset derived from a hardcoded header size survives. `llvm-dwarfdump` resolves `DW_AT_import` and prints the target's name, so the check asserts identity rather than only an offset.

There was no `DW_TAG_imported_unit` coverage anywhere in `llvm-dwarfutil` or `dsymutil` before this.

The partial unit document needs both dependencies: #219660 so that a `DW_FORM_ref_addr` into a partial unit can be given a section offset at all, and #219398 so that the unit ships under a `DW_UT_partial` header and passes `--verify`.

Its update-mode runs are the one place `--verify` is left off. Those runs build `.debug_names`, and `DWARFVerifier` excludes `DW_TAG_compile_unit` and `DW_TAG_module` from the name index completeness check but not `DW_TAG_partial_unit`, so it demands an index entry for a named partial unit root. That is unrelated to this change and is filed as #219720.

Fixes #219621.


>From db72faa8a539fd0e38eaea3ea950d6a5f99cf5e6 Mon Sep 17 00:00:00 2001
From: Arcadiy Ivanov <arcadiy at ivanov.biz>
Date: Sat, 29 Aug 2026 05:23:27 -0400
Subject: [PATCH 1/3] [AsmPrinter] Find a DIE's unit by its owner, not by its
 tag

DIE::getUnitDie() climbs the parent chain looking for the unit root,
and it recognises that root by comparing its tag against
DW_TAG_compile_unit, DW_TAG_skeleton_unit and DW_TAG_type_unit.
DW_TAG_partial_unit is the fourth unit root tag and is absent from
that list, so the walk runs off the top of the tree and returns null.

DIEUnit's own constructor already asserts that its tag is one of
those four, partial units included. A partial unit root is therefore
accepted into a DIEUnit and then cannot be found again from anything
underneath it.

getUnit() returns null in turn, and getDebugSectionOffset() asserts.
Every DW_FORM_ref_addr reference into a partial unit aborts the
DWARFLinker on an assertions build and dereferences the null on a
released one. Only that one form reaches this code: DIEEntry::emitValue
asks for a section-absolute offset for DW_FORM_ref_addr alone, and the
unit-relative forms emit getOffset() instead.

DWARFv5 section 3.1.1 gives DW_TAG_compile_unit and DW_TAG_partial_unit
alike as unit roots, and section 7.5.5 defines DW_FORM_ref_addr as an
offset from the beginning of the .debug_info section, which is the
offset getDebugSectionOffset() computes. dwz is what emits partial
units, and it is a standard step in Fedora, RHEL and Debian debuginfo
packaging, so hoisted shared types referenced across unit boundaries
are the ordinary shape of a distribution debuginfo file.

The tag is the wrong instrument for the question. A DIEUnit sets
itself as the owner of exactly one DIE, its unit DIE, so the walk is
looking for the DIE that carries a DIEUnit owner. Testing for that
owner answers directly and is correct for every root tag rather than
for an enumerated three.

The unit tests cover all four unit root tags, and they also pin the
orphan case: a tree carrying a unit root tag that no DIEUnit owns
belongs to no unit, which is what the documented contract already
said and what merely adding the missing tag to the list would not
give. The lit test links a partial unit root and a compile unit
control, under garbage collection and in update mode. It runs the
classic linker only and without --verify, because the parallel linker
asserts on a type under a partial unit root (#219613) and such a unit
still ships under a DW_UT_compile header (#219651).

Fixes #219650.

Assisted-by: Claude Code
---
 llvm/include/llvm/CodeGen/DIE.h               |  14 +-
 llvm/lib/CodeGen/AsmPrinter/DIE.cpp           |   8 +-
 .../ELF/X86/dwarf5-partial-unit-ref-addr.test | 303 ++++++++++++++++++
 llvm/unittests/CodeGen/DIETest.cpp            |  38 +++
 4 files changed, 352 insertions(+), 11 deletions(-)
 create mode 100644 llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-ref-addr.test

diff --git a/llvm/include/llvm/CodeGen/DIE.h b/llvm/include/llvm/CodeGen/DIE.h
index bddbf9277cfbd..660585ac11399 100644
--- a/llvm/include/llvm/CodeGen/DIE.h
+++ b/llvm/include/llvm/CodeGen/DIE.h
@@ -937,18 +937,16 @@ class DIE : IntrusiveBackListNode, public DIEValueList {
   computeOffsetsAndAbbrevs(const dwarf::FormParams &FormParams,
                            DIEAbbrevSet &AbbrevSet, unsigned CUOffset);
 
-  /// Climb up the parent chain to get the compile unit or type unit DIE that
-  /// this DIE belongs to.
+  /// Climb up the parent chain to get the unit DIE that this DIE belongs to.
   ///
-  /// \returns the compile or type unit DIE that owns this DIE, or NULL if
-  /// this DIE hasn't been added to a unit DIE.
+  /// \returns the unit DIE that owns this DIE, or NULL if this DIE hasn't been
+  /// added to a unit DIE.
   LLVM_ABI const DIE *getUnitDie() const;
 
-  /// Climb up the parent chain to get the compile unit or type unit that this
-  /// DIE belongs to.
+  /// Climb up the parent chain to get the unit that this DIE belongs to.
   ///
-  /// \returns the DIEUnit that represents the compile or type unit that owns
-  /// this DIE, or NULL if this DIE hasn't been added to a unit DIE.
+  /// \returns the DIEUnit that represents the unit that owns this DIE, or NULL
+  /// if this DIE hasn't been added to a unit DIE.
   LLVM_ABI DIEUnit *getUnit() const;
 
   void setOffset(unsigned O) { Offset = O; }
diff --git a/llvm/lib/CodeGen/AsmPrinter/DIE.cpp b/llvm/lib/CodeGen/AsmPrinter/DIE.cpp
index 01e7ae434192d..5cc473e9e8077 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DIE.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DIE.cpp
@@ -191,9 +191,11 @@ uint64_t DIE::getDebugSectionOffset() const {
 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)
+    // A DIEUnit sets itself as the owner of exactly one DIE, its unit DIE, so
+    // the unit root is the DIE carrying a DIEUnit owner. Testing the tag
+    // instead has to enumerate every unit root tag, and misses any it does not
+    // list.
+    if (dyn_cast_if_present<DIEUnit *>(p->Owner))
       return p;
     p = p->getParent();
   }
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-ref-addr.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-ref-addr.test
new file mode 100644
index 0000000000000..83d588c27cbd5
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-ref-addr.test
@@ -0,0 +1,303 @@
+## This test checks that a cross-unit DW_FORM_ref_addr reference into a
+## DW_TAG_partial_unit is rewritten to the section-absolute offset of the
+## referenced DIE, as it already is for a DW_TAG_compile_unit.
+##
+## The two documents differ only in the root tag and unit type of the unit
+## holding the referenced type, so the compile unit document is the control.
+##
+## Only the classic linker is exercised. The parallel linker asserts on a type
+## under a partial unit root, which is issue #219613, and --verify rejects the
+## output because a partial unit still ships under a DW_UT_compile header,
+## which is issue #219651.
+
+# RUN: yaml2obj --docnum=1 %s -o %t-cu.o
+# RUN: yaml2obj --docnum=2 %s -o %t-pu.o
+
+# RUN: llvm-dwarfutil %t-cu.o %t-cu.out
+# RUN: llvm-dwarfdump --debug-info %t-cu.out | FileCheck %s
+
+# RUN: llvm-dwarfutil %t-pu.o %t-pu.out
+# RUN: llvm-dwarfdump --debug-info %t-pu.out | FileCheck %s
+
+## Update mode keeps every DIE, so the same reference has to be resolved there.
+
+# RUN: llvm-dwarfutil --no-garbage-collection --build-accelerator=DWARF \
+# RUN:   %t-cu.o %t-cu.upd
+# RUN: llvm-dwarfdump --debug-info %t-cu.upd | FileCheck %s
+
+# RUN: llvm-dwarfutil --no-garbage-collection --build-accelerator=DWARF \
+# RUN:   %t-pu.o %t-pu.upd
+# RUN: llvm-dwarfdump --debug-info %t-pu.upd | FileCheck %s
+
+## U01 owns the type, U02 names it across the unit boundary.
+# CHECK: DW_AT_name{{.*}}"U01"
+
+# CHECK: 0x[[S:[0-9a-f]*]]: DW_TAG_structure_type
+# CHECK-NEXT: DW_AT_name{{.*}}"S"
+
+# CHECK: DW_AT_name{{.*}}"U02"
+
+# CHECK: DW_TAG_subprogram
+# CHECK-NEXT: DW_AT_name{{.*}}"foo2"
+# CHECK-NEXT: DW_AT_type{{.*}}(0x00000000[[S]] "{{.*}}S")
+
+## The control: U01 is an ordinary compilation unit.
+--- !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:            0x20
+  - 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_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_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_type
+            Form:      DW_FORM_ref_addr
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+  debug_info:
+    - Version:  5
+      UnitType: DW_UT_compile
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 5
+          Values:
+            - CStr: by_hand
+            - CStr: U01
+            - Value: 0x04
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+        - AbbrCode: 3
+          Values:
+            - CStr: ns
+        - AbbrCode: 4
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 0
+        - AbbrCode: 0
+    - Version:  5
+      UnitType: DW_UT_compile
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 5
+          Values:
+            - CStr: by_hand
+            - CStr: U02
+            - Value: 0x04
+            - Value: 0x1140
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 6
+          Values:
+            - CStr: foo2
+            - Value: 0x49
+            - Value: 0x1140
+            - Value: 0x10
+        - AbbrCode: 0
+
+## The case: U01 is a partial unit, and nothing else changes.
+--- !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:            0x20
+  - 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_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_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_type
+            Form:      DW_FORM_ref_addr
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+  debug_info:
+    - Version:  5
+      UnitType: DW_UT_partial
+      AbbrevTableID: 0
+      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
+        - AbbrCode: 3
+          Values:
+            - CStr: ns
+        - AbbrCode: 4
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 0
+        - AbbrCode: 0
+    - Version:  5
+      UnitType: DW_UT_compile
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 5
+          Values:
+            - CStr: by_hand
+            - CStr: U02
+            - Value: 0x04
+            - Value: 0x1140
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 6
+          Values:
+            - CStr: foo2
+            - Value: 0x49
+            - Value: 0x1140
+            - Value: 0x10
+        - AbbrCode: 0
+...
diff --git a/llvm/unittests/CodeGen/DIETest.cpp b/llvm/unittests/CodeGen/DIETest.cpp
index 4eb4a78f7de9d..5311b583be058 100644
--- a/llvm/unittests/CodeGen/DIETest.cpp
+++ b/llvm/unittests/CodeGen/DIETest.cpp
@@ -222,4 +222,42 @@ TEST(DIEValueListTest, DeleteValue) {
   EXPECT_FALSE(List.deleteValue(dwarf::DW_AT_high_pc));
 }
 
+TEST(DIETest, GetUnitDie) {
+  // A DIEUnit owns exactly one DIE, its unit DIE, whatever the root tag is, so
+  // the walk up from a nested DIE has to reach the unit for every unit root
+  // tag.
+  static const dwarf::Tag UnitTags[] = {
+      dwarf::DW_TAG_compile_unit, dwarf::DW_TAG_partial_unit,
+      dwarf::DW_TAG_type_unit, dwarf::DW_TAG_skeleton_unit};
+
+  BumpPtrAllocator Alloc;
+  for (dwarf::Tag UnitTag : UnitTags) {
+    BasicDIEUnit Unit(UnitTag);
+    Unit.setDebugSectionOffset(0x100);
+
+    DIE &Root = Unit.getUnitDie();
+    DIE &Namespace = Root.addChild(DIE::get(Alloc, dwarf::DW_TAG_namespace));
+    DIE &Struct =
+        Namespace.addChild(DIE::get(Alloc, dwarf::DW_TAG_structure_type));
+    Struct.setOffset(0x20);
+
+    EXPECT_EQ(&Root, Struct.getUnitDie());
+    EXPECT_EQ(static_cast<DIEUnit *>(&Unit), Struct.getUnit());
+    EXPECT_EQ(0x120u, Struct.getDebugSectionOffset());
+  }
+}
+
+TEST(DIETest, GetUnitDieWithoutUnit) {
+  // A tree carrying a unit root tag that was never handed to a DIEUnit belongs
+  // to no unit, so there is no absolute offset to compute for anything in it.
+  BumpPtrAllocator Alloc;
+  DIE *Root = DIE::get(Alloc, dwarf::DW_TAG_compile_unit);
+  DIE &Struct = Root->addChild(DIE::get(Alloc, dwarf::DW_TAG_structure_type));
+
+  EXPECT_EQ(nullptr, Root->getUnitDie());
+  EXPECT_EQ(nullptr, Root->getUnit());
+  EXPECT_EQ(nullptr, Struct.getUnitDie());
+  EXPECT_EQ(nullptr, Struct.getUnit());
+}
+
 } // end namespace

>From 825f038369988699022386ebe43b4b82da72825e Mon Sep 17 00:00:00 2001
From: Arcadiy Ivanov <arcadiy at ivanov.biz>
Date: Fri, 28 Aug 2026 03:09:13 -0400
Subject: [PATCH 2/3] [DWARFLinker] Key str_offsets_base and unit_type on the
 unit root

DWARFLinker has two backends, classic and parallel, both shared with
dsymutil, and each assumed every unit root is a DW_TAG_compile_unit.
DWARFContext::compile_units() filters out only type units, so a
DW_TAG_partial_unit root reaches the linker on exactly the same path a
full compilation unit does. That is the shape dwz leaves behind, and dwz
is a standard step in distribution debuginfo packaging.

Two things went wrong for such a unit. Firstly, DW_AT_str_offsets_base
was never synthesized, although the strings were still rewritten into
DW_FORM_strx. DWARFv5 section 7.26 resolves those indices only through
that attribute, so every indexed string in the output read back empty.
Secondly, the unit header was emitted with unit_type DW_UT_compile
whatever the root tag was, contradicting the root DIE. Both backends
exited 0, and llvm-dwarfdump --verify rejected the result on both
counts.

The attribute synthesis and the header byte now key on the DIE being the
unit root rather than on its tag. DWARFv5 section 7.5.1.1 gives full and
partial units the same five-field header, so no size accounting changes.

Skeleton roots stay out of scope for the header byte. Section 7.5.1.2
adds an 8-byte dwo_id to a DW_UT_skeleton header, which the hardcoded
12-byte header size in both backends does not account for, so such a
unit is still emitted as DW_UT_compile. The DW_AT_str_offsets_base half
does cover skeleton roots, which section 3.1.2 lists the attribute for.

The test exercises a DW_TAG_partial_unit root through both backends,
under garbage collection and in update mode. Update mode is reached with
--build-accelerator because --no-garbage-collection on its own copies
the file without running the linker at all.

Fixes #219365

Assisted-by: Claude Code
---
 llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp  |  12 +-
 .../lib/DWARFLinker/Classic/DWARFStreamer.cpp |  13 +-
 .../Parallel/DIEAttributeCloner.cpp           |  17 ++-
 .../DWARFLinker/Parallel/DWARFEmitterImpl.cpp |  13 +-
 .../ELF/X86/dwarf5-partial-unit.test          | 143 ++++++++++++++++++
 5 files changed, 188 insertions(+), 10 deletions(-)
 create mode 100644 llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit.test

diff --git a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
index 46001d7a8a4d5..a3c887fc54be1 100644
--- a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
+++ b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
@@ -2013,9 +2013,15 @@ DIE *DWARFLinker::DIECloner::cloneDIE(const DWARFDie &InputDIE,
     }
   }
 
-  if (Unit.getOrigUnit().getVersion() >= 5 && !AttrInfo.AttrStrOffsetBaseSeen &&
-      Die->getTag() == dwarf::DW_TAG_compile_unit) {
-    // No DW_AT_str_offsets_base seen, add it to the DIE.
+  // Comparing against the output unit DIE identifies the root of the unit,
+  // whatever its tag. cloneStringAttribute() rewrites strings into
+  // DW_FORM_strx for every DWARFv5 unit without consulting the root tag, and
+  // DWARFv5 section 7.26 resolves those indices only through
+  // DW_AT_str_offsets_base, so a DW_TAG_partial_unit or DW_TAG_skeleton_unit
+  // root needs the attribute on the same terms as a full compilation unit
+  // (DWARFv5 sections 3.1.1 and 3.1.2).
+  if (Die == Unit.getOutputUnitDIE() && Unit.getOrigUnit().getVersion() >= 5 &&
+      !AttrInfo.AttrStrOffsetBaseSeen) {
     Die->addValue(DIEAlloc, dwarf::DW_AT_str_offsets_base,
                   dwarf::DW_FORM_sec_offset, DIEInteger(8));
     OutOffset += 4;
diff --git a/llvm/lib/DWARFLinker/Classic/DWARFStreamer.cpp b/llvm/lib/DWARFLinker/Classic/DWARFStreamer.cpp
index c6ccc71889fc4..2118da26c2f6f 100644
--- a/llvm/lib/DWARFLinker/Classic/DWARFStreamer.cpp
+++ b/llvm/lib/DWARFLinker/Classic/DWARFStreamer.cpp
@@ -188,7 +188,18 @@ void DwarfStreamer::emitCompileUnitHeader(CompileUnit &Unit,
   Asm->emitInt16(DwarfVersion);
 
   if (DwarfVersion >= 5) {
-    Asm->emitInt8(dwarf::DW_UT_compile);
+    // The header has to agree with the root DIE that was actually cloned, or
+    // llvm-dwarfdump --verify rejects the unit with "Mismatched unit type".
+    // Reading the tag off the output root covers DW_TAG_partial_unit without
+    // plumbing the input unit type through the DwarfEmitter interface. DWARFv5
+    // section 7.5.1.1 gives full and partial units the same five-field header,
+    // so the size below is unchanged. Skeleton roots are deliberately not
+    // mapped to DW_UT_skeleton: section 7.5.1.2 adds an 8-byte dwo_id to that
+    // header, which the 12-byte constant here and in computeNextUnitOffset()
+    // does not account for.
+    dwarf::Tag RootTag = Unit.getOutputUnitDIE()->getTag();
+    Asm->emitInt8(RootTag == dwarf::DW_TAG_partial_unit ? dwarf::DW_UT_partial
+                                                        : dwarf::DW_UT_compile);
     Asm->emitInt8(Unit.getOrigUnit().getAddressByteSize());
     // We share one abbreviations table across all units so it's always at the
     // start of the section.
diff --git a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
index 68d7b9d72cf61..88c52d7d4e013 100644
--- a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
@@ -114,11 +114,18 @@ void DIEAttributeCloner::clone() {
     }
   }
 
-  // We convert source strings into the indexed form for DWARFv5.
-  // Check if original compile unit already has DW_AT_str_offsets_base
-  // attribute.
-  if (InputDieEntry->getTag() == dwarf::DW_TAG_compile_unit &&
-      InUnit.getVersion() >= 5 && !AttrInfo.HasStringOffsetBaseAttr) {
+  // Index 0 is the root DIE of the unit, whatever its tag. cloneStringAttr()
+  // rewrites strings into DW_FORM_strx for every DWARFv5 unit without
+  // consulting the root tag, and DWARFv5 section 7.26 resolves those indices
+  // only through DW_AT_str_offsets_base, so a DW_TAG_partial_unit or
+  // DW_TAG_skeleton_unit root needs the attribute on the same terms as a full
+  // compilation unit (DWARFv5 sections 3.1.1 and 3.1.2).
+  //
+  // The isCompileUnit() test is not redundant: index 0 can also reach the
+  // artificial type unit, where AttrOutOffset is DIE-relative rather than
+  // section-relative, so the patch registered below would be misplaced.
+  if (InputDIEIdx == 0 && InUnit.getVersion() >= 5 &&
+      !AttrInfo.HasStringOffsetBaseAttr && OutUnit.isCompileUnit()) {
     DebugInfoOutputSection.notePatchWithOffsetUpdate(
         DebugOffsetPatch{AttrOutOffset,
                          &OutUnit->getOrCreateSectionDescriptor(
diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFEmitterImpl.cpp b/llvm/lib/DWARFLinker/Parallel/DWARFEmitterImpl.cpp
index 4009bfcd3018c..be8520f77a37b 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFEmitterImpl.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFEmitterImpl.cpp
@@ -141,7 +141,18 @@ void DwarfEmitterImpl::emitCompileUnitHeader(DwarfUnit &Unit) {
   Asm->emitInt16(Unit.getVersion());
 
   if (Unit.getVersion() >= 5) {
-    Asm->emitInt8(dwarf::DW_UT_compile);
+    // The header has to agree with the root DIE that was actually cloned, or
+    // llvm-dwarfdump --verify rejects the unit with "Mismatched unit type".
+    // getTag() is the tag of that root, cached by setOutUnitDIE(); a TypeUnit's
+    // artificial root is a DW_TAG_compile_unit, so type units keep emitting
+    // DW_UT_compile. DWARFv5 section 7.5.1.1 gives full and partial units the
+    // same five-field header, so the size below is unchanged. Skeleton roots
+    // are deliberately not mapped to DW_UT_skeleton: section 7.5.1.2 adds an
+    // 8-byte dwo_id to that header, which the 12-byte constant here and in
+    // getDebugInfoHeaderSize() does not account for.
+    dwarf::Tag RootTag = Unit.getTag();
+    Asm->emitInt8(RootTag == dwarf::DW_TAG_partial_unit ? dwarf::DW_UT_partial
+                                                        : dwarf::DW_UT_compile);
     Asm->emitInt8(Unit.getFormParams().AddrSize);
     // Proper offset to the abbreviations table will be set later.
     Asm->emitInt32(0);
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit.test
new file mode 100644
index 0000000000000..82230dd5b1e20
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit.test
@@ -0,0 +1,143 @@
+## A DWARFv5 unit whose root is DW_TAG_partial_unit, which is the shape dwz
+## leaves behind. DWARFContext::compile_units() filters out only type units, so
+## such a unit reaches the linker exactly as a full compilation unit does, and
+## both backends used to key two decisions on the DW_TAG_compile_unit tag
+## rather than on the DIE being the unit root:
+##
+##   1. DW_AT_str_offsets_base was not synthesized, while the strings were
+##      still rewritten into DW_FORM_strx, so every indexed string in the
+##      output read back empty.
+##   2. The unit header was emitted with unit_type DW_UT_compile whatever the
+##      root tag was, contradicting the root DIE.
+##
+## Both backends exited 0 and produced output that llvm-dwarfdump --verify
+## rejected on both counts.
+
+# RUN: yaml2obj %s -o %t.o
+
+# RUN: llvm-dwarfutil %t.o %t1
+# RUN: llvm-dwarfdump -a --verbose %t1 | FileCheck %s
+# RUN: llvm-dwarfdump --verify %t1 | FileCheck %s --check-prefix=VERIFY
+
+# RUN: llvm-dwarfutil --linker parallel %t.o %t2
+# RUN: llvm-dwarfdump -a --verbose %t2 | FileCheck %s
+# RUN: llvm-dwarfdump --verify %t2 | FileCheck %s --check-prefix=VERIFY
+
+## Update mode preserves the input index tables rather than regenerating them,
+## but the strings are still converted and the header comes from the same
+## emitter, so both defects reach it as well. It is paired with
+## --build-accelerator here because --no-garbage-collection on its own copies
+## the file without running the linker at all. The empty string table also
+## corrupted the accelerator entries, since .debug_names records the names it
+## reads back out of .debug_info.
+# RUN: llvm-dwarfutil --no-garbage-collection --build-accelerator=DWARF %t.o %t3
+# RUN: llvm-dwarfdump -a --verbose %t3 | FileCheck %s
+# RUN: llvm-dwarfdump --verify %t3 | FileCheck %s --check-prefix=VERIFY
+
+# RUN: llvm-dwarfutil --linker parallel --no-garbage-collection --build-accelerator=DWARF %t.o %t4
+# RUN: llvm-dwarfdump -a --verbose %t4 | FileCheck %s
+# RUN: llvm-dwarfdump --verify %t4 | FileCheck %s --check-prefix=VERIFY
+
+#CHECK: .debug_info contents:
+#CHECK: Compile Unit: {{.*}} version = 0x0005, unit_type = DW_UT_partial
+
+#CHECK: DW_TAG_partial_unit
+#CHECK: DW_AT_producer [DW_FORM_strx] {{.*}} "by_hand"
+#CHECK: DW_AT_name [DW_FORM_strx] {{.*}} "PU1"
+#CHECK: DW_AT_str_offsets_base [DW_FORM_sec_offset] (0x00000008)
+
+#CHECK: DW_TAG_subprogram
+#CHECK: DW_AT_name [DW_FORM_strx] {{.*}} "foo1"
+#CHECK: DW_TAG_subprogram
+#CHECK: DW_AT_name [DW_FORM_strx] {{.*}} "foo2"
+
+#VERIFY: No errors.
+
+## A DWARFv5 partial unit carrying two function ranges. The .debug_addr
+## contribution and DW_AT_addr_base are required, not incidental: without them
+## the rewritten ranges resolve from 0 and the unit fails verification for an
+## unrelated reason. DW_AT_ranges and .debug_rnglists are likewise required,
+## since otherwise garbage collection drops the whole unit and the collecting
+## RUN lines stop testing anything.
+--- !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:            0x30
+  - Name:            .debug_rnglists
+    Type:            SHT_PROGBITS
+    Flags:           [  ]
+    Content:        "1d0000000500080000000000073011000000000000100750110000000000001000"
+  - Name:            .debug_addr
+    Type:            SHT_PROGBITS
+    Flags:           [  ]
+    Content:        "0c000000050008003011000000000000"
+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_ranges
+            Form:      DW_FORM_sec_offset
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_addr_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_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: PU1
+            - Value:  0xc
+            - Value:  0x0
+            - Value:  0x8
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+## 0x5d is the .debug_info offset of the DW_TAG_base_type DIE below; it shifts
+## if the root DIE's attributes change.
+            - Value: 0x5d
+        - AbbrCode: 2
+          Values:
+            - CStr: foo2
+            - Value: 0x1150
+            - Value: 0x10
+            - Value: 0x5d
+        - AbbrCode: 3
+          Values:
+            - CStr: int
+        - AbbrCode: 0
+...

>From 05e7c02ef7c98b5618794f2216f5169ccf5c3c17 Mon Sep 17 00:00:00 2001
From: Arcadiy Ivanov <arcadiy at ivanov.biz>
Date: Sat, 29 Aug 2026 16:20:25 -0400
Subject: [PATCH 3/3] [DWARFLinker] Point DW_AT_import at the unit root, not
 the unit header

DW_AT_import names the root debugging information entry of the
imported unit. The classic linker rewrote it to the offset of that
unit's header instead, so every DW_TAG_imported_unit in the output
pointed at the bytes before a DIE rather than at one. When the
imported unit came first the value was 0x0; otherwise it was short of
the root by the size of the header.

llvm-dwarfdump --verify calls the result "invalid DIE reference ...
Offset is in between DIEs". A released build emits it and exits 0, so
the corruption is silent; an assertions build aborts in
fixupForwardReferences() instead, at DIE.h:882.

The cause is that a unit root never gets a DIEInfo::Clone.
createOutputDIE() makes the output root a DIE owned by the unit's own
BasicDIEUnit, and cloneDIE() is handed that DIE directly rather than
allocating one. cloneDieReferenceAttribute() only knew about the
DIEInfo::Clone path, so for a reference to a root it took the
"not cloned yet" branch and manufactured a placeholder. That
placeholder is never adopted into a unit tree, so nothing ever assigns
it an offset, and the reference resolved to whatever the unit started
at.

Resolve a reference whose target is the unit root through that unit's
output DIE instead, identified positionally as index 0. Dropping the
attribute when the referenced unit is not in the output keeps a
reference to a discarded unit from naming something arbitrary.

That output DIE has to exist before any unit is cloned, because the
unit holding the reference may be cloned before the unit it names.
Creating it moves out of the per-unit loop into a pre-pass over the
same units the loop goes on to emit, so no unit that will be dropped
gains one - creating an output DIE for a dropped unit would leave the
emitted size disagreeing with the recorded start offset.

The tag plays no part in any of this, so the test covers a
DW_TAG_compile_unit root as well as the DW_TAG_partial_unit root that
dwz produces. Each document carries both reference directions, U01
importing a unit cloned after it and U03 importing a unit cloned
before it, pinned by one capture so neither direction can be satisfied
alone. The DWARFv4 document is there because its unit header is eleven
bytes where DWARFv5's is twelve, which no offset derived from a
hardcoded header size survives.

Fixes #219621.

Assisted-by: Claude Code
---
 llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp  |  33 +-
 .../llvm-dwarfutil/ELF/X86/imported-unit.test | 454 ++++++++++++++++++
 2 files changed, 480 insertions(+), 7 deletions(-)
 create mode 100644 llvm/test/tools/llvm-dwarfutil/ELF/X86/imported-unit.test

diff --git a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
index a3c887fc54be1..018c314f6c500 100644
--- a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
+++ b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
@@ -1204,13 +1204,26 @@ unsigned DWARFLinker::DIECloner::cloneDieReferenceAttribute(
     return U.getRefAddrByteSize();
   }
 
-  if (!RefInfo.Clone) {
-    // We haven't cloned this DIE yet. Just create an empty one and
-    // store it. It'll get really cloned when we process it.
-    RefInfo.UnclonedReference = true;
-    RefInfo.Clone = DIE::get(DIEAlloc, dwarf::Tag(RefDie.getTag()));
+  if (RefUnit->getOrigUnit().getDIEIndex(RefDie) == 0) {
+    // A unit root is cloned into its unit's own output DIE rather than into a
+    // DIEInfo::Clone, so resolve the reference through that DIE. The
+    // placeholder below would instead manufacture a DIE that is never adopted
+    // into a unit tree and so never gets an offset assigned.
+    NewRefDie = RefUnit->getOutputUnitDIE();
+
+    // The referenced unit is not in the output, so there is nothing left for
+    // this attribute to name.
+    if (!NewRefDie)
+      return 0;
+  } else {
+    if (!RefInfo.Clone) {
+      // We haven't cloned this DIE yet. Just create an empty one and
+      // store it. It'll get really cloned when we process it.
+      RefInfo.UnclonedReference = true;
+      RefInfo.Clone = DIE::get(DIEAlloc, dwarf::Tag(RefDie.getTag()));
+    }
+    NewRefDie = RefInfo.Clone;
   }
-  NewRefDie = RefInfo.Clone;
 
   if (AttrSpec.Form == dwarf::DW_FORM_ref_addr ||
       (Unit.hasODR() && isODRAttribute(AttrSpec.Attr))) {
@@ -2906,6 +2919,13 @@ Expected<uint64_t> DWARFLinker::DIECloner::cloneAllCompileUnits(
       (Emitter == nullptr) ? 0 : Emitter->getDebugInfoSectionSize();
   const uint64_t StartOutputDebugInfoSize = OutputDebugInfoSize;
 
+  // A reference to a unit root resolves through that unit's output DIE, and
+  // the referring unit may be cloned first, so every unit that will be emitted
+  // needs its output DIE before any cloning starts.
+  for (auto &CurrentUnit : CompileUnits)
+    if (CurrentUnit->getOrigUnit().getUnitDIE() && CurrentUnit->getInfo(0).Keep)
+      CurrentUnit->createOutputDIE();
+
   for (auto &CurrentUnit : CompileUnits) {
     const uint16_t DwarfVersion = CurrentUnit->getOrigUnit().getVersion();
     const uint32_t UnitHeaderSize = DwarfVersion >= 5 ? 12 : 11;
@@ -2918,7 +2938,6 @@ Expected<uint64_t> DWARFLinker::DIECloner::cloneAllCompileUnits(
     if (CurrentUnit->getInfo(0).Keep) {
       // Clone the InputDIE into your Unit DIE in our compile unit since it
       // already has a DIE inside of it.
-      CurrentUnit->createOutputDIE();
       rememberUnitForMacroOffset(*CurrentUnit);
       cloneDIE(InputDIE, File, *CurrentUnit, 0 /* PC offset */, UnitHeaderSize,
                0, IsLittleEndian, CurrentUnit->getOutputUnitDIE());
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/imported-unit.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/imported-unit.test
new file mode 100644
index 0000000000000..575a7de4771cf
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/imported-unit.test
@@ -0,0 +1,454 @@
+## DW_AT_import must hold the .debug_info offset of the referenced unit's root
+## DIE. The classic linker rewrote it to that unit's header offset instead,
+## short of the root by the size of the header, leaving every
+## DW_TAG_imported_unit in the output pointing between DIEs.
+##
+## Both reference directions are covered in every document. U01 imports U02,
+## which is cloned after it, and U03 imports U02, which is cloned before it. A
+## single capture pins both against U02's root, so neither direction can be
+## satisfied alone.
+##
+## The defect does not depend on the imported unit's root tag, so the three
+## documents hold one set of expectations and differ only in U02's root tag,
+## the DWARF version, and whether U02 carries code.
+
+## DWARFv5 with DW_TAG_compile_unit roots, through both linkers, under garbage
+## collection and in update mode.
+# RUN: yaml2obj --docnum=1 %s -o %t1.o
+
+# RUN: llvm-dwarfutil %t1.o %t1.classic
+# RUN: llvm-dwarfdump --debug-info %t1.classic | FileCheck %s -DU02TAG=compile_unit
+# RUN: llvm-dwarfdump --verify %t1.classic
+
+# RUN: llvm-dwarfutil --no-garbage-collection --build-accelerator=DWARF %t1.o %t1.classic.upd
+# RUN: llvm-dwarfdump --debug-info %t1.classic.upd | FileCheck %s -DU02TAG=compile_unit
+# RUN: llvm-dwarfdump --verify %t1.classic.upd
+
+# RUN: llvm-dwarfutil --linker parallel %t1.o %t1.parallel
+# RUN: llvm-dwarfdump --debug-info %t1.parallel | FileCheck %s -DU02TAG=compile_unit
+# RUN: llvm-dwarfdump --verify %t1.parallel
+
+# RUN: llvm-dwarfutil --linker parallel --no-garbage-collection --build-accelerator=DWARF %t1.o %t1.parallel.upd
+# RUN: llvm-dwarfdump --debug-info %t1.parallel.upd | FileCheck %s -DU02TAG=compile_unit
+# RUN: llvm-dwarfdump --verify %t1.parallel.upd
+
+## DWARFv4, whose unit header is eleven bytes where DWARFv5's is twelve, so an
+## offset derived from a hardcoded header size does not survive this document.
+# RUN: yaml2obj --docnum=2 %s -o %t2.o
+
+# RUN: llvm-dwarfutil %t2.o %t2.classic
+# RUN: llvm-dwarfdump --debug-info %t2.classic | FileCheck %s -DU02TAG=compile_unit
+# RUN: llvm-dwarfdump --verify %t2.classic
+
+# RUN: llvm-dwarfutil --linker parallel %t2.o %t2.parallel
+# RUN: llvm-dwarfdump --debug-info %t2.parallel | FileCheck %s -DU02TAG=compile_unit
+# RUN: llvm-dwarfdump --verify %t2.parallel
+
+## The dwz shape: the imported unit is a DW_TAG_partial_unit, which is what dwz
+## produces. Its root carries no code, so it has no DW_AT_low_pc.
+# RUN: yaml2obj --docnum=3 %s -o %t3.o
+
+# RUN: llvm-dwarfutil %t3.o %t3.classic
+# RUN: llvm-dwarfdump --debug-info %t3.classic | FileCheck %s -DU02TAG=partial_unit
+# RUN: llvm-dwarfdump --verify %t3.classic
+
+# RUN: llvm-dwarfutil --linker parallel %t3.o %t3.parallel
+# RUN: llvm-dwarfdump --debug-info %t3.parallel | FileCheck %s -DU02TAG=partial_unit
+# RUN: llvm-dwarfdump --verify %t3.parallel
+
+## Update mode runs without --verify: it builds .debug_names, and the verifier
+## demands an index entry for a named DW_TAG_partial_unit root where it excuses
+## a DW_TAG_compile_unit one.
+# RUN: llvm-dwarfutil --no-garbage-collection --build-accelerator=DWARF %t3.o %t3.classic.upd
+# RUN: llvm-dwarfdump --debug-info %t3.classic.upd | FileCheck %s -DU02TAG=partial_unit
+
+# RUN: llvm-dwarfutil --linker parallel --no-garbage-collection --build-accelerator=DWARF %t3.o %t3.parallel.upd
+# RUN: llvm-dwarfdump --debug-info %t3.parallel.upd | FileCheck %s -DU02TAG=partial_unit
+
+# CHECK: DW_TAG_compile_unit
+# CHECK: DW_AT_name{{.*}}"U01"
+
+## Forward reference: U02 has not been cloned yet when this is emitted.
+# CHECK: DW_TAG_imported_unit
+# CHECK-NEXT: DW_AT_import{{.*}}(0x00000000[[U02ROOT:[0-9a-f]{8}]] "U02")
+
+# CHECK: 0x[[U02ROOT]]: DW_TAG_[[U02TAG]]
+# CHECK: DW_AT_name{{.*}}"U02"
+
+# CHECK: DW_TAG_compile_unit
+# CHECK: DW_AT_name{{.*}}"U03"
+
+## Backward reference: U02 already carries an output DIE at this point.
+# CHECK: DW_TAG_imported_unit
+# CHECK-NEXT: DW_AT_import{{.*}}(0x00000000[[U02ROOT]] "U02")
+
+--- !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:            0x30
+  - Name:            .debug_str_offsets
+    Type:            SHT_PROGBITS
+    Flags:           [  ]
+    Content:        "0400000005000000"
+DWARF:
+  debug_abbrev:
+    - ID:    0
+      Table:
+      - 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_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
+  debug_info:
+    - Version:  5
+      UnitType: DW_UT_compile
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U01
+            - Value: 0x04
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 3
+          Values:
+            - Value: 0x57
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+        - AbbrCode: 0
+    - Version:  5
+      UnitType: DW_UT_compile
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U02
+            - Value: 0x04
+            - Value: 0x1140
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 2
+          Values:
+            - CStr: foo2
+            - Value: 0x1140
+            - Value: 0x10
+        - AbbrCode: 4
+          Values:
+            - CStr: ns
+        - AbbrCode: 5
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 0
+        - AbbrCode: 0
+    - Version:  5
+      UnitType: DW_UT_compile
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U03
+            - Value: 0x04
+            - Value: 0x1150
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 3
+          Values:
+            - Value: 0x57
+        - AbbrCode: 2
+          Values:
+            - CStr: foo3
+            - Value: 0x1150
+            - Value: 0x10
+        - AbbrCode: 0
+...
+--- !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:            0x30
+DWARF:
+  debug_abbrev:
+    - ID:    0
+      Table:
+      - 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
+      - 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
+  debug_info:
+    - Version:  4
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U01
+            - Value: 0x04
+            - Value: 0x1130
+            - Value: 0x10
+        - AbbrCode: 3
+          Values:
+            - Value: 0x51
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+        - AbbrCode: 0
+    - Version:  4
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U02
+            - Value: 0x04
+            - Value: 0x1140
+            - Value: 0x10
+        - AbbrCode: 2
+          Values:
+            - CStr: foo2
+            - Value: 0x1140
+            - Value: 0x10
+        - AbbrCode: 0
+    - Version:  4
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U03
+            - Value: 0x04
+            - Value: 0x1150
+            - Value: 0x10
+        - AbbrCode: 3
+          Values:
+            - Value: 0x51
+        - AbbrCode: 2
+          Values:
+            - CStr: foo3
+            - Value: 0x1150
+            - Value: 0x10
+        - AbbrCode: 0
+...
+--- !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:            0x30
+  - Name:            .debug_str_offsets
+    Type:            SHT_PROGBITS
+    Flags:           [  ]
+    Content:        "0400000005000000"
+DWARF:
+  debug_abbrev:
+    - ID:    0
+      Table:
+      - 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_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_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
+  debug_info:
+    - Version:  5
+      UnitType: DW_UT_compile
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U01
+            - Value: 0x04
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 3
+          Values:
+            - Value: 0x57
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+        - AbbrCode: 0
+    - Version:  5
+      UnitType: DW_UT_partial
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 6
+          Values:
+            - CStr: by_hand
+            - CStr: U02
+            - Value: 0x04
+            - Value: 0x8
+        - AbbrCode: 2
+          Values:
+            - CStr: foo2
+            - Value: 0x1140
+            - Value: 0x10
+        - AbbrCode: 4
+          Values:
+            - CStr: ns
+        - AbbrCode: 5
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 0
+        - AbbrCode: 0
+    - Version:  5
+      UnitType: DW_UT_compile
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U03
+            - Value: 0x04
+            - Value: 0x1150
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 3
+          Values:
+            - Value: 0x57
+        - AbbrCode: 2
+          Values:
+            - CStr: foo3
+            - Value: 0x1150
+            - Value: 0x10
+        - AbbrCode: 0
+...



More information about the llvm-commits mailing list