[llvm] [DWARFLinker] Emit DW_AT_addr_base for units that had none (PR #219357)

Arcadiy Ivanov via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 27 21:11:04 PDT 2026


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

`llvm-dwarfutil` crashes processing any GCC-produced DWARFv5 object whose compile unit carries `DW_AT_ranges` but no `.debug_addr`, which is every GCC binary built at `-O2` with default debug settings. Reproducer is a one-liner:

```console
$ gcc -g -O2 -o c c.c
$ llvm-dwarfutil c c.out
Didn't find a DW_AT_addr_base in cloned DIE!
UNREACHABLE executed at llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp:2207!
```

DWARFLinker has two backends, classic and parallel, selected with `--linker` and shared with `dsymutil`. Both of them rewrite range lists using `DW_RLE_base_addressx`, which indexes the unit's `.debug_addr` contribution. DWARFv5 section 2.17.3 defines what such an index means:

> In the descriptions that follow, the term address index means the index of an address in the `.debug_addr` section. This index is relative to the value of the `DW_AT_addr_base` attribute of the associated compilation unit.

Section 3.1.1 states the same requirement from the attribute's side, and names that exact range-list form:

> A `DW_AT_addr_base` attribute, whose value is of class addrptr. This attribute points to the beginning of the compilation unit's contribution to the `.debug_addr` section. Indirect references (using `DW_FORM_addrx`, [...] `DW_LLE_base_addressx`, `DW_LLE_startx_endx`, `DW_LLE_startx_length`, `DW_RLE_base_addressx`, `DW_RLE_startx_endx` or `DW_RLE_startx_length`) within the compilation unit are interpreted as indices relative to this base.

Neither backend ever emitted the attribute, though; both only patch one inherited from the input, and GCC's units have none to inherit.

Note, that the two backends fail differently, and only one of them is loud:

1. Classic reaches the `llvm_unreachable` in `patchAddrBase` above.
2. Parallel emits the indexed entries anyway and exits 0. With no base to resolve them the CU's range is read from 0, giving `[0x0, 0x3)` where the subprogram sits at `0x400360`, and `llvm-dwarfdump --verify` rejects the result with "DIE address ranges are not contained in its parent's ranges".

The fix adds `DW_AT_addr_base` while cloning the unit DIE, mirroring the `DW_AT_str_offsets_base` synthesis that already sits a few lines above in both backends. That placement is not incidental: it has to happen before `Die->generateAbbrev()` so the attribute reaches the abbreviation table, and before `computeNextUnitOffset()` freezes the DIE offsets, which is well before the address pool is known to be non-empty. Patching it in later is not an option for that reason.

`emitDebugAddrSection()` consequently emits the `.debug_addr` contribution even when no address was pooled, so the attribute always resolves. A table with no entries past the header is still well formed, per section 7.27:

> This header is followed by a series of segment/address pairs. [...] The `DW_AT_addr_base` attribute points to the first entry following the header. The entries are indexed sequentially from this base entry, starting from 0.

Which DIE gets `DW_AT_addr_base` is decided by asking whether it is the unit's root, not whether its tag is `DW_TAG_compile_unit`. `DWARFContext::compile_units()` hands the linker every non-type unit, so a `DW_TAG_partial_unit` root reaches exactly the same code and fails exactly the same two ways. That is what `dwz` leaves behind, and llvm-dwarfutil is aimed squarely at the kind of binaries that have been through `dwz`. The spec draws no distinction here either: section 3.1.1 is titled "Full and Partial Compilation Unit Entries" and introduces the very attribute list quoted above with "A full or partial compilation unit entry may have the following attributes", while section 3.1.2 lists "A `DW_AT_addr_base` attribute" for skeleton units as well. Keying on the root DIE has the useful side effect that synthesis and emission become the same condition, which is what turns the `llvm_unreachable` in `patchAddrBase` into a real invariant instead of a reachable failure.

`--update` preserves the index tables instead of regenerating them, so nothing is synthesized in that mode and the input attribute keeps its original value.

`DW_AT_addr_base` is added to every DWARFv5 unit that lacks one, not only to units that turn out to use the address pool. Whether the pool ends up non-empty is not known at the point where the attribute has to be added, and a unit whose children carry the ranges is just as affected as one whose own DIE does. The cost is a 4-byte attribute plus an 8-byte empty table header for units that end up indexing nothing, so units built at `-O0`/`-O1` now carry both where they previously carried neither. I am open to narrowing this if a reviewer would rather pay for the extra bookkeeping.

Tests are hand-written DWARFv5 units matching what GCC emits at `-O2`: `DW_AT_ranges` into `.debug_rnglists`, absolute `DW_FORM_addr` addresses, no `DW_AT_addr_base` and no `.debug_addr`. One has a `DW_TAG_compile_unit` root, one a `DW_TAG_partial_unit` root; without the fix classic crashes on both and parallel produces units with no address base whose ranges resolve from 0. Both are checked, and the compile unit case also runs under `--no-garbage-collection` to pin down that nothing is synthesized in update mode. `check-llvm-tools-llvm-dwarfutil`, `check-llvm-tools-dsymutil` and `DWARFLinkerParallelTests` pass.

This change was produced with AI assistance; I have reviewed it and am accountable for it.


>From c8ad62ca366d20ad7afeb66415ad6fac6cbcd94a Mon Sep 17 00:00:00 2001
From: Arcadiy Ivanov <arcadiy at ivanov.biz>
Date: Fri, 28 Aug 2026 00:10:29 -0400
Subject: [PATCH] [DWARFLinker] Emit DW_AT_addr_base for units that had none

The classic and parallel DWARFLinker backends both rewrite range lists
using DW_RLE_base_addressx, which indexes the unit's .debug_addr
contribution. DWARFv5 section 3.1.1 resolves that index relative to
DW_AT_addr_base, but neither backend ever added the attribute -- both
only patched one inherited from the input. GCC emits DWARFv5 units
with no .debug_addr at all, so there is nothing to inherit and the
invariant breaks two different ways:

1. The classic linker reached llvm_unreachable("Didn't find a
   DW_AT_addr_base in cloned DIE!") in patchAddrBase.
2. The parallel linker emitted the indexed entries regardless. With no
   base to resolve them against the unit's ranges are read from 0, so
   llvm-dwarfdump --verify rejects the output with "DIE address ranges
   are not contained in its parent's ranges".

Add DW_AT_addr_base while cloning the unit DIE, before the abbreviation
is generated and before computeNextUnitOffset() freezes the offsets,
mirroring the DW_AT_str_offsets_base handling that already sits a few
lines above in both backends. The address table is now emitted even
when no address was pooled, so the attribute always has a contribution
to point at; a table with no entries following the header is well
formed per DWARFv5 section 7.27.

Key DW_AT_addr_base on the unit's root DIE rather than on
DW_TAG_compile_unit. DWARFContext::compile_units() yields every
non-type unit, so a DW_TAG_partial_unit root reaches the same code and
fails the same two ways. DWARFv5 section 3.1.1 covers full and partial
compilation units alike, and section 3.1.2 lists the attribute for
skeleton units too. Keying on the root DIE also makes the synthesis
and emission conditions identical, so the llvm_unreachable in
patchAddrBase is now an invariant rather than a reachable failure.

--update preserves the index tables rather than regenerating them, so
nothing is synthesized in that mode.

Reproducer is any GCC binary built at -O2:

  $ gcc -g -O2 -o c c.c
  $ llvm-dwarfutil c c.out

Fixes #218825

Assisted-by: Claude Code
---
 llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp  |  57 +++++++-
 .../Parallel/DIEAttributeCloner.cpp           |  54 +++++++-
 .../DWARFLinker/Parallel/DIEAttributeCloner.h |   3 +
 .../Parallel/DWARFLinkerCompileUnit.cpp       |  11 +-
 .../X86/dwarf5-no-addr-base-partial-unit.test | 118 ++++++++++++++++
 .../ELF/X86/dwarf5-no-addr-base.test          | 126 ++++++++++++++++++
 6 files changed, 356 insertions(+), 13 deletions(-)
 create mode 100644 llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-no-addr-base-partial-unit.test
 create mode 100644 llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-no-addr-base.test

diff --git a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
index 46001d7a8a4d5..bafb1484edbcc 100644
--- a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
+++ b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
@@ -1606,7 +1606,7 @@ unsigned DWARFLinker::DIECloner::cloneScalarAttribute(
 
   [[maybe_unused]] dwarf::Form OriginalForm = AttrSpec.Form;
   if (AttrSpec.Form == dwarf::DW_FORM_rnglistx) {
-    // DWARFLinker does not generate .debug_addr table. Thus we need to change
+    // DWARFLinker does not preserve input index tables. Thus we need to change
     // all "addrx" related forms to "addr" version. Change DW_FORM_rnglistx
     // to DW_FORM_sec_offset here.
     std::optional<uint64_t> Index = Val.getAsSectionOffset();
@@ -1627,7 +1627,7 @@ unsigned DWARFLinker::DIECloner::cloneScalarAttribute(
     AttrSpec.Form = dwarf::DW_FORM_sec_offset;
     AttrSize = Unit.getOrigUnit().getFormParams().getDwarfOffsetByteSize();
   } else if (AttrSpec.Form == dwarf::DW_FORM_loclistx) {
-    // DWARFLinker does not generate .debug_addr table. Thus we need to change
+    // DWARFLinker does not preserve input index tables. Thus we need to change
     // all "addrx" related forms to "addr" version. Change DW_FORM_loclistx
     // to DW_FORM_sec_offset here.
     std::optional<uint64_t> Index = Val.getAsSectionOffset();
@@ -1798,13 +1798,13 @@ shouldSkipAttribute(bool Update,
   case dwarf::DW_AT_ranges:
     return !Update && SkipPC;
   case dwarf::DW_AT_rnglists_base:
-    // In case !Update the .debug_addr table is not generated/preserved.
+    // In case !Update the input index tables are not preserved.
     // Thus instead of DW_FORM_rnglistx the DW_FORM_sec_offset is used.
     // Since DW_AT_rnglists_base is used for only DW_FORM_rnglistx the
     // DW_AT_rnglists_base is removed.
     return !Update;
   case dwarf::DW_AT_loclists_base:
-    // In case !Update the .debug_addr table is not generated/preserved.
+    // In case !Update the input index tables are not preserved.
     // Thus instead of DW_FORM_loclistx the DW_FORM_sec_offset is used.
     // Since DW_AT_loclists_base is used for only DW_FORM_loclistx the
     // DW_AT_loclists_base is removed.
@@ -2021,6 +2021,39 @@ DIE *DWARFLinker::DIECloner::cloneDIE(const DWARFDie &InputDIE,
     OutOffset += 4;
   }
 
+  // Comparing against the output unit DIE identifies the root of the unit,
+  // whatever its tag. DWARFv5 section 3.1.1 is titled "Full and Partial
+  // Compilation Unit Entries" and prefaces the attribute list below with "A
+  // full or partial compilation unit entry may have the following
+  // attributes", and section 3.1.2 lists "A DW_AT_addr_base attribute" for
+  // skeleton units, so a DW_TAG_partial_unit or DW_TAG_skeleton_unit root
+  // needs it on the same terms as a full compilation unit.
+  if (!Update && Die == Unit.getOutputUnitDIE() && U.getVersion() >= 5 &&
+      !Die->findAttribute(dwarf::DW_AT_addr_base)) {
+    // Ranges and locations are rewritten using addrx-indexed entries, and
+    // DWARFv5 resolves those indices only through DW_AT_addr_base. Per DWARFv5
+    // section 3.1.1:
+    //
+    //   A DW_AT_addr_base attribute [...] points to the beginning of the
+    //   compilation unit's contribution to the .debug_addr section. Indirect
+    //   references (using DW_FORM_addrx, [...] DW_RLE_base_addressx,
+    //   DW_RLE_startx_endx or DW_RLE_startx_length) within the compilation
+    //   unit are interpreted as indices relative to this base.
+    //
+    // The input unit may have none of its own: GCC emits DWARFv5 units with
+    // no .debug_addr contribution at all. Emitting the rewritten entries
+    // without this attribute would leave their indices with no defined base.
+    // The value is patched in emitDebugAddrSection() once the offset of the
+    // unit's address table contribution is known.
+    //
+    // --update preserves the index tables instead of regenerating them, so the
+    // input attribute (if any) keeps its original value and no attribute is
+    // synthesized; emitDebugAddrSection() likewise emits nothing in that mode.
+    OutOffset += Die->addValue(DIEAlloc, dwarf::DW_AT_addr_base,
+                               dwarf::DW_FORM_sec_offset, DIEInteger(0))
+                     ->sizeOf(U.getFormParams());
+  }
+
   DIEAbbrev NewAbbrev = Die->generateAbbrev();
   if (HasChildren)
     NewAbbrev.setChildrenFlag(dwarf::DW_CHILDREN_yes);
@@ -2216,7 +2249,19 @@ Error DWARFLinker::DIECloner::emitDebugAddrSection(
   if (DwarfVersion < 5)
     return Error::success();
 
-  if (AddrPool.getValues().empty())
+  // A unit which was not cloned has no output unit DIE and therefore no
+  // synthesized DW_AT_addr_base, which is what makes the llvm_unreachable in
+  // patchAddrBase an invariant rather than a reachable failure.
+  //
+  // Every other unit gets a contribution even when no address was pooled,
+  // since otherwise the attribute would point at a table that does not exist.
+  // A contribution with no entries is well formed: DWARFv5 section 7.27
+  // defines the address table as a header "followed by a series of
+  // segment/address pairs", and "the DW_AT_addr_base attribute points to the
+  // first entry following the header", which stays well defined when there are
+  // no entries.
+  DIE *OutputUnitDIE = Unit.getOutputUnitDIE();
+  if (OutputUnitDIE == nullptr)
     return Error::success();
 
   MCSymbol *EndLabel = Emitter->emitDwarfDebugAddrsHeader(Unit);
@@ -2226,7 +2271,7 @@ Error DWARFLinker::DIECloner::emitDebugAddrSection(
     return createStringError(".debug_addr section offset 0x" +
                              Twine::utohexstr(AddrOffset) + " exceeds the " +
                              dwarf::FormatString(FP.Format) + " limit");
-  patchAddrBase(*Unit.getOutputUnitDIE(), DIEInteger(AddrOffset));
+  patchAddrBase(*OutputUnitDIE, DIEInteger(AddrOffset));
   Emitter->emitDwarfDebugAddrs(AddrPool.getValues(),
                                Unit.getOrigUnit().getAddressByteSize());
   Emitter->emitDwarfDebugAddrsFooter(Unit, EndLabel);
diff --git a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
index 68d7b9d72cf61..efb86f0d10331 100644
--- a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
@@ -133,6 +133,51 @@ void DIEAttributeCloner::clone() {
                                 OutUnit->getDebugStrOffsetsHeaderSize())
             .second;
   }
+
+  // Index 0 is the root DIE of the unit, whatever its tag. DWARFv5 section
+  // 3.1.1 is titled "Full and Partial Compilation Unit Entries" and prefaces
+  // the attribute list below with "A full or partial compilation unit entry
+  // may have the following attributes", and section 3.1.2 lists "A
+  // DW_AT_addr_base attribute" for skeleton units, so a DW_TAG_partial_unit
+  // or DW_TAG_skeleton_unit root needs it on the same terms as a full
+  // compilation unit.
+  //
+  // The UpdateIndexTablesOnly test is required, not an optimization:
+  // cloneScalarAttr() returns early in that mode, before the branch which sets
+  // HasAddrBaseAttr, so the flag reads false even when the input has one.
+  if (InputDIEIdx == 0 && InUnit.getVersion() >= 5 &&
+      !AttrInfo.HasAddrBaseAttr &&
+      !InUnit.getGlobalData().getOptions().UpdateIndexTablesOnly) {
+    // Ranges and locations are rewritten using addrx-indexed entries, and
+    // DWARFv5 resolves those indices only through DW_AT_addr_base. Per DWARFv5
+    // section 3.1.1:
+    //
+    //   A DW_AT_addr_base attribute [...] points to the beginning of the
+    //   compilation unit's contribution to the .debug_addr section. Indirect
+    //   references (using DW_FORM_addrx, [...] DW_RLE_base_addressx,
+    //   DW_RLE_startx_endx or DW_RLE_startx_length) within the compilation
+    //   unit are interpreted as indices relative to this base.
+    //
+    // The input unit may have none of its own: GCC emits DWARFv5 units with
+    // no .debug_addr contribution at all. Emitting the rewritten entries
+    // without this attribute would leave their indices with no defined base.
+    //
+    // --update preserves the index tables instead of regenerating them, so the
+    // input attribute (if any) keeps its original value and no attribute is
+    // synthesized; emitDebugAddrSection() likewise emits nothing in that mode.
+    DebugInfoOutputSection.notePatchWithOffsetUpdate(
+        DebugOffsetPatch{
+            AttrOutOffset,
+            &OutUnit->getOrCreateSectionDescriptor(DebugSectionKind::DebugAddr),
+            true},
+        PatchesOffsets);
+
+    AttrOutOffset += Generator
+                         .addScalarAttribute(dwarf::DW_AT_addr_base,
+                                             dwarf::DW_FORM_sec_offset,
+                                             OutUnit->getDebugAddrHeaderSize())
+                         .second;
+  }
 }
 
 bool DIEAttributeCloner::shouldSkipAttribute(
@@ -151,13 +196,13 @@ bool DIEAttributeCloner::shouldSkipAttribute(
     return InUnit.getDIEInfo(InputDIEIdx).getIsInFunctionScope() &&
            !FuncAddressAdjustment.has_value();
   case dwarf::DW_AT_rnglists_base:
-    // In case !Update the .debug_addr table is not generated/preserved.
+    // In case !Update the input index tables are not preserved.
     // Thus instead of DW_FORM_rnglistx the DW_FORM_sec_offset is used.
     // Since DW_AT_rnglists_base is used for only DW_FORM_rnglistx the
     // DW_AT_rnglists_base is removed.
     return !InUnit.getGlobalData().getOptions().UpdateIndexTablesOnly;
   case dwarf::DW_AT_loclists_base:
-    // In case !Update the .debug_addr table is not generated/preserved.
+    // In case !Update the input index tables are not preserved.
     // Thus instead of DW_FORM_loclistx the DW_FORM_sec_offset is used.
     // Since DW_AT_loclists_base is used for only DW_FORM_loclistx the
     // DW_AT_loclists_base is removed.
@@ -444,7 +489,7 @@ size_t DIEAttributeCloner::cloneScalarAttr(
 
   dwarf::Form ResultingForm = AttrSpec.Form;
   if (AttrSpec.Form == dwarf::DW_FORM_rnglistx) {
-    // DWARFLinker does not generate .debug_addr table. Thus we need to change
+    // DWARFLinker does not preserve input index tables. Thus we need to change
     // all "addrx" related forms to "addr" version. Change DW_FORM_rnglistx
     // to DW_FORM_sec_offset here.
     std::optional<uint64_t> Index = Val.getAsSectionOffset();
@@ -462,7 +507,7 @@ size_t DIEAttributeCloner::cloneScalarAttr(
     Value = *Offset;
     ResultingForm = dwarf::DW_FORM_sec_offset;
   } else if (AttrSpec.Form == dwarf::DW_FORM_loclistx) {
-    // DWARFLinker does not generate .debug_addr table. Thus we need to change
+    // DWARFLinker does not preserve input index tables. Thus we need to change
     // all "addrx" related forms to "addr" version. Change DW_FORM_loclistx
     // to DW_FORM_sec_offset here.
     std::optional<uint64_t> Index = Val.getAsSectionOffset();
@@ -532,6 +577,7 @@ size_t DIEAttributeCloner::cloneScalarAttr(
 
     // Use size of .debug_addr header as attribute value. The offset to
     // .debug_addr would be added later while patching.
+    AttrInfo.HasAddrBaseAttr = true;
     return Generator
         .addScalarAttribute(AttrSpec.Attr, AttrSpec.Form,
                             OutUnit->getDebugAddrHeaderSize())
diff --git a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.h b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.h
index 2a5a2250cc5b2..7444bc59c71b2 100644
--- a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.h
+++ b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.h
@@ -39,6 +39,9 @@ struct AttributesInfo {
 
   /// Does the DIE have a string offset attribute?
   bool HasStringOffsetBaseAttr = false;
+
+  /// Does the DIE have an address base attribute?
+  bool HasAddrBaseAttr = false;
 };
 
 /// This class creates clones of input DIE attributes.
diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
index 4138a9fd69aca..c396dad1e2b3b 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
@@ -750,9 +750,14 @@ Error CompileUnit::emitDebugAddrSection() {
   if (getVersion() < 5)
     return Error::success();
 
-  if (DebugAddrIndexMap.empty())
-    return Error::success();
-
+  // Every cloned DWARFv5 unit carries a DW_AT_addr_base, either from the input
+  // or added while cloning (see DIEAttributeCloner::clone()), so the
+  // contribution is emitted even when no address was indexed -- otherwise the
+  // attribute would point at a table that does not exist. A contribution with
+  // no entries is well formed: DWARFv5 section 7.27 defines the address table
+  // as a header "followed by a series of segment/address pairs", and "the
+  // DW_AT_addr_base attribute points to the first entry following the header",
+  // which stays well defined when there are no entries.
   SectionDescriptor &OutAddrSection =
       getOrCreateSectionDescriptor(DebugSectionKind::DebugAddr);
 
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-no-addr-base-partial-unit.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-no-addr-base-partial-unit.test
new file mode 100644
index 0000000000000..163f60a030639
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-no-addr-base-partial-unit.test
@@ -0,0 +1,118 @@
+## Same missing DW_AT_addr_base as dwarf5-no-addr-base.test, but on a unit
+## whose root is DW_TAG_partial_unit rather than DW_TAG_compile_unit. DWARFv5
+## section 3.1.3 gives a partial unit the attributes of a full compilation
+## unit, and DWARFContext::compile_units() hands both kinds to the linker, so
+## the address base has to be synthesized for either root. Keying it on the
+## root DIE rather than on DW_TAG_compile_unit is what makes that hold; before
+## that the classic linker asserted here and the parallel linker emitted
+## addrx-indexed ranges with no base to resolve them.
+
+# RUN: yaml2obj %s -o %t.o
+
+# RUN: llvm-dwarfutil %t.o %t1
+# RUN: llvm-dwarfdump -a --verbose %t1 | FileCheck %s
+
+# RUN: llvm-dwarfutil --linker parallel %t.o %t2
+# RUN: llvm-dwarfdump -a --verbose %t2 | FileCheck %s
+
+## Note that --verify is deliberately not run here. Both backends have further,
+## unrelated and pre-existing gaps on non-DW_TAG_compile_unit roots: no
+## DW_AT_str_offsets_base is synthesized for them, and the output unit header
+## is written as DW_UT_compile whatever the root tag was. Neither is what this
+## test is about, so it checks the address base and the ranges it resolves.
+
+#CHECK: DW_TAG_partial_unit
+#CHECK:   DW_AT_ranges [DW_FORM_sec_offset]
+#CHECK-NEXT: [0x0000000000001130, 0x0000000000001140)
+#CHECK-NEXT: [0x0000000000001150, 0x0000000000001160)
+#CHECK:   DW_AT_addr_base [DW_FORM_sec_offset] (0x00000008)
+
+## The rewritten ranges are emitted as a DW_RLE_base_addressx indexing
+## .debug_addr, followed by DW_RLE_offset_pair entries relative to it. Both
+## functions therefore hang off the single pooled address, and resolving either
+## of them back to its input range depends on DW_AT_addr_base above.
+#CHECK: .debug_addr contents:
+#CHECK: 0x00000000: Address table header: length = {{.*}} version = 0x0005, addr_size = 0x08, seg_size = 0x00
+#CHECK: Addrs: [
+#CHECK-NEXT: 0x0000000000001130
+#CHECK-NEXT: ]
+#CHECK: .debug_rnglists contents:
+#CHECK: [DW_RLE_base_addressx]: 0x0000000000000000
+#CHECK-NEXT: [DW_RLE_offset_pair ]: {{.*}} => [0x0000000000001130, 0x0000000000001140)
+#CHECK-NEXT: [DW_RLE_offset_pair ]: {{.*}} => [0x0000000000001150, 0x0000000000001160)
+
+## A DWARFv5 partial unit carrying two function ranges, with absolute
+## DW_FORM_addr addresses, no DW_AT_addr_base and no .debug_addr.
+--- !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"
+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
+      - 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
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x59
+        - AbbrCode: 2
+          Values:
+            - CStr: foo2
+            - Value: 0x1150
+            - Value: 0x10
+            - Value: 0x59
+        - AbbrCode: 3
+          Values:
+            - CStr: int
+        - AbbrCode: 0
+...
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-no-addr-base.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-no-addr-base.test
new file mode 100644
index 0000000000000..a86df301eb09d
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-no-addr-base.test
@@ -0,0 +1,126 @@
+## Test that a DWARFv5 compile unit which has DW_AT_ranges but neither
+## DW_AT_addr_base nor a .debug_addr contribution is linked correctly.
+## GCC produces such units at -O2: it never emits .debug_addr, but the unit
+## still carries ranges. Rewriting those ranges makes the linker populate its
+## address pool, so a .debug_addr contribution is emitted for the output unit
+## and the output unit must therefore also get a DW_AT_addr_base pointing at
+## it. Previously the classic linker asserted trying to patch a DW_AT_addr_base
+## that the input never had.
+
+# RUN: yaml2obj %s -o %t.o
+
+# RUN: llvm-dwarfutil %t.o %t1
+# RUN: llvm-dwarfdump --verify %t1 | FileCheck %s
+# RUN: llvm-dwarfdump -a --verbose %t1 | FileCheck %s --check-prefix DWARF-CHECK
+
+# RUN: llvm-dwarfutil --linker parallel %t.o %t2
+# RUN: llvm-dwarfdump --verify %t2 | FileCheck %s
+# RUN: llvm-dwarfdump -a --verbose %t2 | FileCheck %s --check-prefix DWARF-CHECK
+
+## --no-garbage-collection preserves the index tables instead of regenerating
+## them, so the ranges are not rewritten into addrx-indexed form and neither
+## backend may synthesize a DW_AT_addr_base: there would be no .debug_addr
+## contribution for it to point at.
+
+# RUN: llvm-dwarfutil --no-garbage-collection %t.o %t3
+# RUN: llvm-dwarfdump --verify %t3 | FileCheck %s
+# RUN: llvm-dwarfdump -a --verbose %t3 | FileCheck %s --check-prefix UPDATE-CHECK \
+# RUN:   --implicit-check-not=DW_AT_addr_base
+
+# RUN: llvm-dwarfutil --linker parallel --no-garbage-collection %t.o %t4
+# RUN: llvm-dwarfdump --verify %t4 | FileCheck %s
+# RUN: llvm-dwarfdump -a --verbose %t4 | FileCheck %s --check-prefix UPDATE-CHECK \
+# RUN:   --implicit-check-not=DW_AT_addr_base
+
+#CHECK: No errors.
+
+#UPDATE-CHECK: DW_TAG_compile_unit
+#UPDATE-CHECK:   DW_AT_name [DW_FORM_string] ("CU1")
+#UPDATE-CHECK:   DW_AT_ranges [DW_FORM_sec_offset]
+#UPDATE-CHECK-NEXT: [0x0000000000001130, 0x0000000000001140))
+
+## The rewritten ranges are emitted with DW_RLE_base_addressx, which indexes
+## .debug_addr, so the unit needs a DW_AT_addr_base resolving to the emitted
+## address table.
+#DWARF-CHECK: DW_TAG_compile_unit
+#DWARF-CHECK:   DW_AT_name [DW_FORM_strx] {{.*}} "CU1"
+#DWARF-CHECK:   DW_AT_ranges [DW_FORM_sec_offset]
+#DWARF-CHECK:   DW_AT_addr_base [DW_FORM_sec_offset] (0x00000008)
+#DWARF-CHECK: .debug_addr contents:
+#DWARF-CHECK: 0x00000000: Address table header: length = {{.*}} version = 0x0005, addr_size = 0x08, seg_size = 0x00
+#DWARF-CHECK: Addrs: [
+#DWARF-CHECK: 0x0000000000001130
+#DWARF-CHECK: ]
+#DWARF-CHECK: .debug_rnglists contents:
+#DWARF-CHECK: [DW_RLE_base_addressx]: 0x0000000000000000
+
+## A DWARFv5 unit as GCC emits it at -O2: DW_AT_ranges into .debug_rnglists,
+## absolute DW_FORM_addr addresses, no DW_AT_addr_base and no .debug_addr.
+--- !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_rnglists
+    Type:            SHT_PROGBITS
+    Flags:           [  ]
+    Content:        "1300000005000800000000000730110000000000001000"
+DWARF:
+  debug_abbrev:
+    - 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_ranges
+            Form:      DW_FORM_sec_offset
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+      - Tag:      DW_TAG_subprogram
+        Children: DW_CHILDREN_yes
+        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_compile
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: CU1
+            - Value:  0xc
+            - Value:  0x0
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x40
+        - AbbrCode: 0
+        - AbbrCode: 3
+          Values:
+            - CStr: int
+        - AbbrCode: 0
+...



More information about the llvm-commits mailing list