[llvm] [DWARFLinker] Key the unit's address range on the unit root (PR #219602)
Arcadiy Ivanov via llvm-commits
llvm-commits at lists.llvm.org
Sat Oct 3 10:27:16 PDT 2026
https://github.com/arcivanov updated https://github.com/llvm/llvm-project/pull/219602
>From 4e18b5a2b3c997c1bfa70084b7104e9ab9486afb 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 1/2] [DWARFLinker] Key str_offsets_base and unit_type on the
unit root
Fix DWARFv5 output for a unit whose root is DW_TAG_partial_unit, as
produced by dwz. The classic and parallel linkers rewrite strings into
DW_FORM_strx but add DW_AT_str_offsets_base only to a
DW_TAG_compile_unit root, so every string in a partial unit reads back
empty. Both also write DW_UT_compile into every unit header,
contradicting a partial unit root.
Add DW_AT_str_offsets_base to the unit root whatever its tag, and emit
DW_UT_partial for a DW_TAG_partial_unit root. Its header has the same
layout as DW_UT_compile.
The test links a partial unit with both linkers, with and without
garbage collection, and checks the unit type, the attribute, the strings
and --verify. The update mode runs pass --build-accelerator, because
--no-garbage-collection alone copies the file without linking it.
Fixes #219365.
Assisted-by: Claude Code
---
llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp | 4 +-
.../lib/DWARFLinker/Classic/DWARFStreamer.cpp | 4 +-
.../Parallel/DIEAttributeCloner.cpp | 6 +-
.../DWARFLinker/Parallel/DWARFEmitterImpl.cpp | 4 +-
.../ELF/X86/dwarf5-partial-unit.test | 120 ++++++++++++++++++
5 files changed, 131 insertions(+), 7 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 454143ec001209..9254a33dee60d8 100644
--- a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
+++ b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
@@ -2013,8 +2013,8 @@ DIE *DWARFLinker::DIECloner::cloneDIE(const DWARFDie &InputDIE,
}
}
- if (Unit.getOrigUnit().getVersion() >= 5 && !AttrInfo.AttrStrOffsetBaseSeen &&
- Die->getTag() == dwarf::DW_TAG_compile_unit) {
+ if (Die == Unit.getOutputUnitDIE() && Unit.getOrigUnit().getVersion() >= 5 &&
+ !AttrInfo.AttrStrOffsetBaseSeen) {
// No DW_AT_str_offsets_base seen, add it to the DIE.
Die->addValue(DIEAlloc, dwarf::DW_AT_str_offsets_base,
dwarf::DW_FORM_sec_offset, DIEInteger(8));
diff --git a/llvm/lib/DWARFLinker/Classic/DWARFStreamer.cpp b/llvm/lib/DWARFLinker/Classic/DWARFStreamer.cpp
index c6ccc71889fc41..26c0bc00a73898 100644
--- a/llvm/lib/DWARFLinker/Classic/DWARFStreamer.cpp
+++ b/llvm/lib/DWARFLinker/Classic/DWARFStreamer.cpp
@@ -188,7 +188,9 @@ void DwarfStreamer::emitCompileUnitHeader(CompileUnit &Unit,
Asm->emitInt16(DwarfVersion);
if (DwarfVersion >= 5) {
- Asm->emitInt8(dwarf::DW_UT_compile);
+ 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 68d7b9d72cf618..67545ee03d15b5 100644
--- a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
@@ -115,10 +115,10 @@ 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
+ // Check if original unit already has DW_AT_str_offsets_base
// attribute.
- if (InputDieEntry->getTag() == dwarf::DW_TAG_compile_unit &&
- InUnit.getVersion() >= 5 && !AttrInfo.HasStringOffsetBaseAttr) {
+ 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 18d94ac2d834ee..efd70eaef0d266 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFEmitterImpl.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFEmitterImpl.cpp
@@ -139,7 +139,9 @@ void DwarfEmitterImpl::emitCompileUnitHeader(DwarfUnit &Unit) {
Asm->emitInt16(Unit.getVersion());
if (Unit.getVersion() >= 5) {
- Asm->emitInt8(dwarf::DW_UT_compile);
+ 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 00000000000000..6b9185defb2af6
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit.test
@@ -0,0 +1,120 @@
+## Check that both linkers emit a DWARFv5 partial unit with a DW_UT_partial
+## header and a DW_AT_str_offsets_base.
+
+# 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
+
+## Without --build-accelerator, --no-garbage-collection does not run the linker.
+# 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.
+
+## DW_AT_ranges keeps the unit alive under garbage collection, and
+## DW_AT_addr_base resolves the ranges the linker rewrites.
+--- !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 offset of the DW_TAG_base_type DIE below.
+ - Value: 0x5d
+ - AbbrCode: 2
+ Values:
+ - CStr: foo2
+ - Value: 0x1150
+ - Value: 0x10
+ - Value: 0x5d
+ - AbbrCode: 3
+ Values:
+ - CStr: int
+ - AbbrCode: 0
+...
>From e0a8cc06a1580df7bf0271ac751be95c77ed4255 Mon Sep 17 00:00:00 2001
From: Arcadiy Ivanov <arcadiy at ivanov.biz>
Date: Fri, 28 Aug 2026 19:53:25 -0400
Subject: [PATCH 2/2] [DWARFLinker] Key the unit's address range on the unit
root
Fix the address range of a DW_TAG_partial_unit root. Both linkers
rebuild a unit root's DW_AT_low_pc/DW_AT_high_pc or DW_AT_ranges from
the functions they kept, but only for a DW_TAG_compile_unit root. A
partial unit root keeps its input range, so after garbage collection it
still covers removed functions, and its DW_AT_ranges list is remapped
like an ordinary DIE's, which can leave it empty.
Test for the unit root instead of the tag: the classic linker compares
with the output unit DIE, the parallel linker checks for input DIE
index 0. DW_AT_high_pc is cloned as an address or as a scalar depending
on its form, so both clone functions change.
The tests cover DW_AT_low_pc with DW_AT_high_pc in both forms, a unit
with no live function, and DW_AT_ranges. The root covers three
functions of which only the middle one is live, so its range has to
shrink at both ends. Each test links a partial unit root and a compile
unit control with both linkers and verifies every output that still
holds a function.
Fixes #219392.
Assisted-by: Claude Code
---
.../Classic/DWARFLinkerCompileUnit.h | 5 +
llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp | 16 +-
.../Classic/DWARFLinkerCompileUnit.cpp | 2 +-
.../Parallel/DIEAttributeCloner.cpp | 20 +--
.../DWARFLinker/Parallel/DIEAttributeCloner.h | 3 +
.../Parallel/DWARFLinkerCompileUnit.cpp | 10 +-
.../lib/DWARFLinker/Parallel/OutputSections.h | 4 +-
.../ELF/X86/dwarf5-partial-unit-pc-range.test | 167 ++++++++++++++++++
.../ELF/X86/dwarf5-partial-unit-ranges.test | 113 ++++++++++++
9 files changed, 309 insertions(+), 31 deletions(-)
create mode 100644 llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-pc-range.test
create mode 100644 llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-ranges.test
diff --git a/llvm/include/llvm/DWARFLinker/Classic/DWARFLinkerCompileUnit.h b/llvm/include/llvm/DWARFLinker/Classic/DWARFLinkerCompileUnit.h
index 538572338dc8bc..8847a39c17ec25 100644
--- a/llvm/include/llvm/DWARFLinker/Classic/DWARFLinkerCompileUnit.h
+++ b/llvm/include/llvm/DWARFLinker/Classic/DWARFLinkerCompileUnit.h
@@ -145,6 +145,11 @@ class CompileUnit {
return nullptr;
}
+ /// Check whether \p Die is this unit's root DIE, whatever its tag.
+ bool isUnitRootDIE(const DIE &Die) const {
+ return &Die == getOutputUnitDIE();
+ }
+
dwarf::Tag getTag() const { return OrigUnit.getUnitDIE().getTag(); }
bool hasODR() const { return HasODR; }
diff --git a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
index 9254a33dee60d8..3f76b71ebe0c6a 100644
--- a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
+++ b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
@@ -1493,14 +1493,12 @@ unsigned DWARFLinker::DIECloner::cloneAddressAttribute(
return 0;
}
- if (InputDIE.getTag() == dwarf::DW_TAG_compile_unit &&
- AttrSpec.Attr == dwarf::DW_AT_low_pc) {
+ if (Unit.isUnitRootDIE(Die) && AttrSpec.Attr == dwarf::DW_AT_low_pc) {
if (std::optional<uint64_t> LowPC = Unit.getLowPc())
Addr = *LowPC;
else
return 0;
- } else if (InputDIE.getTag() == dwarf::DW_TAG_compile_unit &&
- AttrSpec.Attr == dwarf::DW_AT_high_pc) {
+ } else if (Unit.isUnitRootDIE(Die) && AttrSpec.Attr == dwarf::DW_AT_high_pc) {
if (uint64_t HighPc = Unit.getHighPc())
Addr = HighPc;
else
@@ -1647,8 +1645,7 @@ unsigned DWARFLinker::DIECloner::cloneScalarAttribute(
Value = *Offset;
AttrSpec.Form = dwarf::DW_FORM_sec_offset;
AttrSize = Unit.getOrigUnit().getFormParams().getDwarfOffsetByteSize();
- } else if (AttrSpec.Attr == dwarf::DW_AT_high_pc &&
- Die.getTag() == dwarf::DW_TAG_compile_unit) {
+ } else if (AttrSpec.Attr == dwarf::DW_AT_high_pc && Unit.isUnitRootDIE(Die)) {
std::optional<uint64_t> LowPC = Unit.getLowPc();
if (!LowPC)
return 0;
@@ -1667,10 +1664,9 @@ unsigned DWARFLinker::DIECloner::cloneScalarAttribute(
return 0;
}
- // A compile unit's high_pc comes from the unit's own linked range and spans
- // every symbol in it.
- if (AttrSpec.Attr == dwarf::DW_AT_high_pc &&
- Die.getTag() != dwarf::DW_TAG_compile_unit)
+ // A unit's high_pc comes from the unit's own linked range and spans every
+ // symbol in it.
+ if (AttrSpec.Attr == dwarf::DW_AT_high_pc && !Unit.isUnitRootDIE(Die))
Value = constrainHighPC(InputDIE, Value, /*IsLength=*/true, Info.PCOffset,
*File.Addresses);
diff --git a/llvm/lib/DWARFLinker/Classic/DWARFLinkerCompileUnit.cpp b/llvm/lib/DWARFLinker/Classic/DWARFLinkerCompileUnit.cpp
index 41dfd5a50f6e71..6e3dfbddb926e4 100644
--- a/llvm/lib/DWARFLinker/Classic/DWARFLinkerCompileUnit.cpp
+++ b/llvm/lib/DWARFLinker/Classic/DWARFLinkerCompileUnit.cpp
@@ -170,7 +170,7 @@ void CompileUnit::addFunctionRange(uint64_t FuncLowPc, uint64_t FuncHighPc,
}
void CompileUnit::noteRangeAttribute(const DIE &Die, PatchLocation Attr) {
- if (Die.getTag() == dwarf::DW_TAG_compile_unit) {
+ if (isUnitRootDIE(Die)) {
UnitRangeAttribute = Attr;
return;
}
diff --git a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
index 67545ee03d15b5..6194bccf0473a7 100644
--- a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
@@ -479,8 +479,7 @@ size_t DIEAttributeCloner::cloneScalarAttr(
Value = *Offset;
ResultingForm = dwarf::DW_FORM_sec_offset;
- } else if (AttrSpec.Attr == dwarf::DW_AT_high_pc &&
- InputDieEntry->getTag() == dwarf::DW_TAG_compile_unit) {
+ } else if (AttrSpec.Attr == dwarf::DW_AT_high_pc && isUnitRootDIE()) {
if (!OutUnit.isCompileUnit())
return 0;
@@ -505,9 +504,7 @@ size_t DIEAttributeCloner::cloneScalarAttr(
AttrSpec.Attr == dwarf::DW_AT_start_scope) {
// Create patch for the range offset value.
DebugInfoOutputSection.notePatchWithOffsetUpdate(
- DebugRangePatch{{AttrOutOffset},
- InputDieEntry->getTag() == dwarf::DW_TAG_compile_unit},
- PatchesOffsets);
+ DebugRangePatch{{AttrOutOffset}, isUnitRootDIE()}, PatchesOffsets);
AttrInfo.HasRanges = true;
} else if (DWARFAttribute::mayHaveLocationList(AttrSpec.Attr) &&
dwarf::doesFormBelongToClass(AttrSpec.Form,
@@ -550,10 +547,9 @@ size_t DIEAttributeCloner::cloneScalarAttr(
!OutUnit.isCompileUnit())
return 0;
- // A compile unit's high_pc comes from the unit's own linked range and spans
- // every symbol in it.
- if (AttrSpec.Attr == dwarf::DW_AT_high_pc &&
- InputDieEntry->getTag() != dwarf::DW_TAG_compile_unit)
+ // A unit's high_pc comes from the unit's own linked range and spans every
+ // symbol in it.
+ if (AttrSpec.Attr == dwarf::DW_AT_high_pc && !isUnitRootDIE())
Value = constrainHighPC(Value, /*IsLength=*/true);
auto Result =
@@ -697,14 +693,12 @@ size_t DIEAttributeCloner::cloneAddressAttr(
return 0;
}
- if (InputDieEntry->getTag() == dwarf::DW_TAG_compile_unit &&
- AttrSpec.Attr == dwarf::DW_AT_low_pc) {
+ if (isUnitRootDIE() && AttrSpec.Attr == dwarf::DW_AT_low_pc) {
if (std::optional<uint64_t> LowPC = OutUnit.getAsCompileUnit()->getLowPc())
Addr = *LowPC;
else
return 0;
- } else if (InputDieEntry->getTag() == dwarf::DW_TAG_compile_unit &&
- AttrSpec.Attr == dwarf::DW_AT_high_pc) {
+ } else if (isUnitRootDIE() && AttrSpec.Attr == dwarf::DW_AT_high_pc) {
if (uint64_t HighPc = OutUnit.getAsCompileUnit()->getHighPc())
Addr = HighPc;
else
diff --git a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.h b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.h
index 2a5a2250cc5b28..afe894a9c90041 100644
--- a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.h
+++ b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.h
@@ -140,6 +140,9 @@ class DIEAttributeCloner {
/// is constrained as well.
uint64_t constrainHighPC(uint64_t HighPC, bool IsLength);
+ /// Returns true if the DIE being cloned is the unit root, whatever its tag.
+ bool isUnitRootDIE() const { return InputDIEIdx == 0; }
+
/// Returns true if attribute should be skipped.
bool
shouldSkipAttribute(DWARFAbbreviationDeclaration::AttributeSpec AttrSpec);
diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
index 4138a9fd69aca8..315fb82f24b354 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
@@ -817,10 +817,10 @@ void CompileUnit::cloneAndEmitRangeList(DebugSectionKind RngSectionKind,
std::optional<AddressRangeValuePair> CachedRange;
uint64_t OffsetAfterUnitLength = emitRangeListHeader(OutRangeSection);
- DebugRangePatch *CompileUnitRangePtr = nullptr;
+ DebugRangePatch *UnitRangePtr = nullptr;
DebugInfoSection.ListDebugRangePatch.forEach([&](DebugRangePatch &Patch) {
- if (Patch.IsCompileUnitRanges) {
- CompileUnitRangePtr = &Patch;
+ if (Patch.IsUnitRanges) {
+ UnitRangePtr = &Patch;
} else {
// Get ranges from the source DWARF corresponding to the current
// attribute.
@@ -859,10 +859,10 @@ void CompileUnit::cloneAndEmitRangeList(DebugSectionKind RngSectionKind,
}
});
- if (CompileUnitRangePtr != nullptr) {
+ if (UnitRangePtr != nullptr) {
// Emit compile unit ranges last to be binary compatible with classic
// dsymutil.
- DebugInfoSection.apply(CompileUnitRangePtr->PatchOffset,
+ DebugInfoSection.apply(UnitRangePtr->PatchOffset,
dwarf::DW_FORM_sec_offset,
OutRangeSection.OS.tell());
emitRangeListFragment(LinkedFunctionRanges, OutRangeSection);
diff --git a/llvm/lib/DWARFLinker/Parallel/OutputSections.h b/llvm/lib/DWARFLinker/Parallel/OutputSections.h
index 10488f8e9f2b64..e7c121d2ed3e3b 100644
--- a/llvm/lib/DWARFLinker/Parallel/OutputSections.h
+++ b/llvm/lib/DWARFLinker/Parallel/OutputSections.h
@@ -56,8 +56,8 @@ struct DebugLineStrPatch : SectionPatch {
/// This structure is used to update range list offset into
/// .debug_ranges/.debug_rnglists.
struct DebugRangePatch : SectionPatch {
- /// Indicates patch which points to immediate compile unit's attribute.
- bool IsCompileUnitRanges = false;
+ /// Indicates patch which points to the unit root's attribute.
+ bool IsUnitRanges = false;
};
/// This structure is used to update location list offset into
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-pc-range.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-pc-range.test
new file mode 100644
index 00000000000000..e30e541b983c4f
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-pc-range.test
@@ -0,0 +1,167 @@
+## Check that both linkers set the DW_AT_low_pc/DW_AT_high_pc of a
+## DW_TAG_partial_unit root to the linked range, as for a DW_TAG_compile_unit.
+## The root covers foo1, foo2 and foo3, but only foo2 is linked. DW_AT_high_pc
+## is cloned by different code as an address and as a length, so both are used.
+
+## DW_TAG_compile_unit root, DW_AT_high_pc as an address.
+# RUN: yaml2obj %s -DROOT=DW_TAG_compile_unit -DUNITTYPE=DW_UT_compile -DHIGHPCFORM=DW_FORM_addr -DHIGHPC=0x1160 -DFOO2LOW=0x1140 -o %t.cu-addr.o
+# RUN: llvm-dwarfutil %t.cu-addr.o %t.cu-addr.classic
+# RUN: llvm-dwarfdump --debug-info %t.cu-addr.classic | FileCheck %s --check-prefixes=CHECK,CU --implicit-check-not=DW_TAG_subprogram
+# RUN: llvm-dwarfdump --verify %t.cu-addr.classic
+# RUN: llvm-dwarfutil --linker parallel %t.cu-addr.o %t.cu-addr.parallel
+# RUN: llvm-dwarfdump --debug-info %t.cu-addr.parallel | FileCheck %s --check-prefixes=CHECK,CU --implicit-check-not=DW_TAG_subprogram
+# RUN: llvm-dwarfdump --verify %t.cu-addr.parallel
+
+## DW_TAG_compile_unit root, DW_AT_high_pc as a length.
+# RUN: yaml2obj %s -DROOT=DW_TAG_compile_unit -DUNITTYPE=DW_UT_compile -DHIGHPCFORM=DW_FORM_data8 -DHIGHPC=0x30 -DFOO2LOW=0x1140 -o %t.cu-len.o
+# RUN: llvm-dwarfutil %t.cu-len.o %t.cu-len.classic
+# RUN: llvm-dwarfdump --debug-info %t.cu-len.classic | FileCheck %s --check-prefixes=CHECK,CU --implicit-check-not=DW_TAG_subprogram
+# RUN: llvm-dwarfdump --verify %t.cu-len.classic
+# RUN: llvm-dwarfutil --linker parallel %t.cu-len.o %t.cu-len.parallel
+# RUN: llvm-dwarfdump --debug-info %t.cu-len.parallel | FileCheck %s --check-prefixes=CHECK,CU --implicit-check-not=DW_TAG_subprogram
+# RUN: llvm-dwarfdump --verify %t.cu-len.parallel
+
+## DW_TAG_partial_unit root, DW_AT_high_pc as an address.
+# RUN: yaml2obj %s -DROOT=DW_TAG_partial_unit -DUNITTYPE=DW_UT_partial -DHIGHPCFORM=DW_FORM_addr -DHIGHPC=0x1160 -DFOO2LOW=0x1140 -o %t.pu-addr.o
+# RUN: llvm-dwarfutil %t.pu-addr.o %t.pu-addr.classic
+# RUN: llvm-dwarfdump --debug-info %t.pu-addr.classic | FileCheck %s --check-prefixes=CHECK,PU --implicit-check-not=DW_TAG_subprogram
+# RUN: llvm-dwarfdump --verify %t.pu-addr.classic
+# RUN: llvm-dwarfutil --linker parallel %t.pu-addr.o %t.pu-addr.parallel
+# RUN: llvm-dwarfdump --debug-info %t.pu-addr.parallel | FileCheck %s --check-prefixes=CHECK,PU --implicit-check-not=DW_TAG_subprogram
+# RUN: llvm-dwarfdump --verify %t.pu-addr.parallel
+
+## DW_TAG_partial_unit root, DW_AT_high_pc as a length.
+# RUN: yaml2obj %s -DROOT=DW_TAG_partial_unit -DUNITTYPE=DW_UT_partial -DHIGHPCFORM=DW_FORM_data8 -DHIGHPC=0x30 -DFOO2LOW=0x1140 -o %t.pu-len.o
+# RUN: llvm-dwarfutil %t.pu-len.o %t.pu-len.classic
+# RUN: llvm-dwarfdump --debug-info %t.pu-len.classic | FileCheck %s --check-prefixes=CHECK,PU --implicit-check-not=DW_TAG_subprogram
+# RUN: llvm-dwarfdump --verify %t.pu-len.classic
+# RUN: llvm-dwarfutil --linker parallel %t.pu-len.o %t.pu-len.parallel
+# RUN: llvm-dwarfdump --debug-info %t.pu-len.parallel | FileCheck %s --check-prefixes=CHECK,PU --implicit-check-not=DW_TAG_subprogram
+# RUN: llvm-dwarfdump --verify %t.pu-len.parallel
+
+## The same inputs with foo2 tombstoned too, so only gvar keeps the unit.
+# RUN: yaml2obj %s -DROOT=DW_TAG_compile_unit -DUNITTYPE=DW_UT_compile -DHIGHPCFORM=DW_FORM_addr -DHIGHPC=0x1160 -DFOO2LOW=0x0 -o %t.drop-cu-addr.o
+# RUN: llvm-dwarfutil %t.drop-cu-addr.o %t.drop-cu-addr.classic
+# RUN: llvm-dwarfdump --debug-info %t.drop-cu-addr.classic | FileCheck %s --check-prefixes=CU,DROP
+# RUN: llvm-dwarfutil --linker parallel %t.drop-cu-addr.o %t.drop-cu-addr.parallel
+# RUN: llvm-dwarfdump --debug-info %t.drop-cu-addr.parallel | FileCheck %s --check-prefixes=CU,DROP
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_compile_unit -DUNITTYPE=DW_UT_compile -DHIGHPCFORM=DW_FORM_data8 -DHIGHPC=0x30 -DFOO2LOW=0x0 -o %t.drop-cu-len.o
+# RUN: llvm-dwarfutil %t.drop-cu-len.o %t.drop-cu-len.classic
+# RUN: llvm-dwarfdump --debug-info %t.drop-cu-len.classic | FileCheck %s --check-prefixes=CU,DROP
+# RUN: llvm-dwarfutil --linker parallel %t.drop-cu-len.o %t.drop-cu-len.parallel
+# RUN: llvm-dwarfdump --debug-info %t.drop-cu-len.parallel | FileCheck %s --check-prefixes=CU,DROP
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_partial_unit -DUNITTYPE=DW_UT_partial -DHIGHPCFORM=DW_FORM_addr -DHIGHPC=0x1160 -DFOO2LOW=0x0 -o %t.drop-pu-addr.o
+# RUN: llvm-dwarfutil %t.drop-pu-addr.o %t.drop-pu-addr.classic
+# RUN: llvm-dwarfdump --debug-info %t.drop-pu-addr.classic | FileCheck %s --check-prefixes=PU,DROP
+# RUN: llvm-dwarfutil --linker parallel %t.drop-pu-addr.o %t.drop-pu-addr.parallel
+# RUN: llvm-dwarfdump --debug-info %t.drop-pu-addr.parallel | FileCheck %s --check-prefixes=PU,DROP
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_partial_unit -DUNITTYPE=DW_UT_partial -DHIGHPCFORM=DW_FORM_data8 -DHIGHPC=0x30 -DFOO2LOW=0x0 -o %t.drop-pu-len.o
+# RUN: llvm-dwarfutil %t.drop-pu-len.o %t.drop-pu-len.classic
+# RUN: llvm-dwarfdump --debug-info %t.drop-pu-len.classic | FileCheck %s --check-prefixes=PU,DROP
+# RUN: llvm-dwarfutil --linker parallel %t.drop-pu-len.o %t.drop-pu-len.parallel
+# RUN: llvm-dwarfdump --debug-info %t.drop-pu-len.parallel | FileCheck %s --check-prefixes=PU,DROP
+
+## The root's range is foo2's.
+# CU: DW_TAG_compile_unit
+# PU: DW_TAG_partial_unit
+# CHECK: DW_AT_producer ("by_hand")
+# CHECK-NEXT: DW_AT_name ("U1")
+# CHECK-NEXT: DW_AT_low_pc (0x0000000000001140)
+# CHECK-NEXT: DW_AT_high_pc (0x0000000000001150)
+# CHECK: DW_TAG_subprogram
+# CHECK-NEXT: DW_AT_name ("foo2")
+# CHECK-NEXT: DW_AT_low_pc (0x0000000000001140)
+# CHECK-NEXT: DW_AT_high_pc (0x0000000000001150)
+
+## With no live function, the root's DW_AT_low_pc and DW_AT_high_pc are
+## dropped.
+# DROP: DW_AT_producer ("by_hand")
+# DROP-NEXT: DW_AT_name ("U1")
+# DROP-NEXT: DW_AT_str_offsets_base (0x00000008)
+# DROP: DW_TAG_variable
+# DROP-NEXT: DW_AT_name ("gvar")
+
+## foo1 and foo3 are tombstoned, and foo2 too when FOO2LOW is 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:
+ - 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_low_pc
+ Form: DW_FORM_addr
+ - Attribute: DW_AT_high_pc
+ Form: [[HIGHPCFORM]]
+ - 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_variable
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_string
+ - Attribute: DW_AT_location
+ Form: DW_FORM_exprloc
+ debug_info:
+ - Version: 5
+ UnitType: [[UNITTYPE]]
+ Entries:
+ - AbbrCode: 1
+ Values:
+ - CStr: by_hand
+ - CStr: U1
+ - Value: 0x1130
+ - Value: [[HIGHPC]]
+ - Value: 0x8
+ - AbbrCode: 2
+ Values:
+ - CStr: foo1
+ - Value: 0x0
+ - Value: 0x10
+ - AbbrCode: 2
+ Values:
+ - CStr: foo2
+ - Value: [[FOO2LOW]]
+ - Value: 0x10
+ - AbbrCode: 2
+ Values:
+ - CStr: foo3
+ - Value: 0x0
+ - Value: 0x10
+ - AbbrCode: 3
+ Values:
+ - CStr: gvar
+ - BlockData: [ 0x03, 0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]
+ - AbbrCode: 0
+...
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-ranges.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-ranges.test
new file mode 100644
index 00000000000000..9b51c05f1236a2
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-ranges.test
@@ -0,0 +1,113 @@
+## Check that both linkers rebuild the DW_AT_ranges of a DW_TAG_partial_unit
+## root from the linked range, as for a DW_TAG_compile_unit. The root covers
+## foo1, foo2 and foo3, but only foo2 is linked. The input has DW_AT_addr_base
+## because the linker does not add a missing one.
+
+## DW_TAG_compile_unit root.
+# 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=CHECK,CU --implicit-check-not=DW_TAG_subprogram
+# RUN: llvm-dwarfdump --verify %t.cu.classic
+# RUN: llvm-dwarfutil --linker parallel %t.cu.o %t.cu.parallel
+# RUN: llvm-dwarfdump --debug-info %t.cu.parallel | FileCheck %s --check-prefixes=CHECK,CU --implicit-check-not=DW_TAG_subprogram
+# RUN: llvm-dwarfdump --verify %t.cu.parallel
+
+## DW_TAG_partial_unit root.
+# 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=CHECK,PU --implicit-check-not=DW_TAG_subprogram
+# RUN: llvm-dwarfdump --verify %t.pu.classic
+# RUN: llvm-dwarfutil --linker parallel %t.pu.o %t.pu.parallel
+# RUN: llvm-dwarfdump --debug-info %t.pu.parallel | FileCheck %s --check-prefixes=CHECK,PU --implicit-check-not=DW_TAG_subprogram
+# RUN: llvm-dwarfdump --verify %t.pu.parallel
+
+## The root's range is foo2's.
+# CU: DW_TAG_compile_unit
+# PU: DW_TAG_partial_unit
+# CHECK: DW_AT_producer ("by_hand")
+# CHECK-NEXT: DW_AT_name ("U1")
+# CHECK-NEXT: DW_AT_ranges (0x0000000c
+# CHECK-NEXT: [0x0000000000001140, 0x0000000000001150))
+# CHECK: DW_TAG_subprogram
+# CHECK-NEXT: DW_AT_name ("foo2")
+# CHECK-NEXT: DW_AT_low_pc (0x0000000000001140)
+# CHECK-NEXT: DW_AT_high_pc (0x0000000000001150)
+
+## The root's range list is one DW_RLE_start_length entry for [0x1130, 0x1160).
+--- !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"
+ - Name: .debug_addr
+ Type: SHT_PROGBITS
+ Flags: [ ]
+ Content: "0c000000050008003011000000000000"
+ - Name: .debug_rnglists
+ Type: SHT_PROGBITS
+ Flags: [ ]
+ Content: "1300000005000800000000000730110000000000003000"
+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_ranges
+ Form: DW_FORM_sec_offset
+ - Attribute: DW_AT_str_offsets_base
+ Form: DW_FORM_sec_offset
+ - 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
+ debug_info:
+ - Version: 5
+ UnitType: [[UNITTYPE]]
+ Entries:
+ - AbbrCode: 1
+ Values:
+ - CStr: by_hand
+ - CStr: U1
+ - Value: 0xc
+ - Value: 0x8
+ - Value: 0x8
+ - AbbrCode: 2
+ Values:
+ - CStr: foo1
+ - Value: 0x0
+ - Value: 0x10
+ - AbbrCode: 2
+ Values:
+ - CStr: foo2
+ - Value: 0x1140
+ - Value: 0x10
+ - AbbrCode: 2
+ Values:
+ - CStr: foo3
+ - Value: 0x0
+ - Value: 0x10
+ - AbbrCode: 0
+...
More information about the llvm-commits
mailing list