[llvm] [BOLT] Handle the indexed DWARF address forms by class (PR #227591)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 30 00:48:59 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-bolt
Author: YongKang Zhu (yozhu)
<details>
<summary>Changes</summary>
DWARF's address class covers DW_FORM_addr plus DW_FORM_addrx, addrx1 through addrx4 and DW_FORM_GNU_addr_index, where the indexed ones store an index into .debug_addr rather than the address itself. Several places enumerated a subset of those forms instead of testing the class, so addrx1 through addrx4 took wrong code path:
| getLowPC | resolved addrx, GNU_addr_index only
| updateLowPCHighPC, low_pc | indexed addrx, GNU_addr_index only
| patchPC for DW_TAG_call_site | indexed addrx only
| low_pc patch in updateUnitDebugInfo | indexed addrx, GNU_addr_index only
| DW_AT_ranges low_pc nullify guard | skipped addrx, GNU_addr_index only
| convertToRangesPatchDebugInfo | indexed addrx only
The local getHighPC() helper compared against DW_FORM_addr alone, so every indexed form was read as an offset, yielding "LowPC + index". It also never resolved an index at all, unlike the getLowPC() directly above it.
Add isAddressForm() and isIndexedAddressForm(), and route all of the above through them. getHighPC() now takes a DWARFUnit argument so it can resolve an index through .debug_addr the way getLowPC() does.
DIEBuilder::cloneAttribute() did not list addrx1 through addrx4 either, so it dropped those attributes before DWARFRewriter could see them, warning "Unsupported attribute form". It already accepts the string counterparts strx1 through strx4. Add the four cases; cloneAddressAttribute() copies the raw value and preserves the form, so it needs no change.
When writing an index, normalize the fixed-width forms to DW_FORM_addrx. The index is reassigned against a .debug_addr that BOLT rebuilds and which may hold more entries than the input's, and DIEInteger emits a fixed-width form at its declared width with no range check, so an index that outgrew the original form would be silently truncated. DW_FORM_GNU_addr_index is left alone because DW_FORM_addrx is DWARF 5.
Assisted-by: opus
---
Full diff: https://github.com/llvm/llvm-project/pull/227591.diff
3 Files Affected:
- (modified) bolt/lib/Core/DIEBuilder.cpp (+4)
- (modified) bolt/lib/Rewrite/DWARFRewriter.cpp (+87-24)
- (added) bolt/test/AArch64/dwarf5-addrx-lowpc-highpc.s (+175)
``````````diff
diff --git a/bolt/lib/Core/DIEBuilder.cpp b/bolt/lib/Core/DIEBuilder.cpp
index fd2b872d66eae..c5b4dce5c963a 100644
--- a/bolt/lib/Core/DIEBuilder.cpp
+++ b/bolt/lib/Core/DIEBuilder.cpp
@@ -956,6 +956,10 @@ void DIEBuilder::cloneAttribute(
break;
case dwarf::DW_FORM_addr:
case dwarf::DW_FORM_addrx:
+ case dwarf::DW_FORM_addrx1:
+ case dwarf::DW_FORM_addrx2:
+ case dwarf::DW_FORM_addrx3:
+ case dwarf::DW_FORM_addrx4:
case dwarf::DW_FORM_GNU_addr_index:
cloneAddressAttribute(Die, U, AttrSpec, Val);
break;
diff --git a/bolt/lib/Rewrite/DWARFRewriter.cpp b/bolt/lib/Rewrite/DWARFRewriter.cpp
index 1da356f16e1c9..79af4f4008130 100644
--- a/bolt/lib/Rewrite/DWARFRewriter.cpp
+++ b/bolt/lib/Rewrite/DWARFRewriter.cpp
@@ -396,6 +396,41 @@ static cl::opt<bool> AlwaysConvertToRanges(
extern cl::opt<std::string> CompDirOverride;
} // namespace opts
+/// True if \p Form belongs to DWARF's address class. A DW_AT_high_pc using one
+/// of these holds the end address; any other form holds an offset from
+/// DW_AT_low_pc.
+static bool isAddressForm(dwarf::Form Form) {
+ switch (Form) {
+ case dwarf::DW_FORM_addr:
+ case dwarf::DW_FORM_addrx:
+ case dwarf::DW_FORM_addrx1:
+ case dwarf::DW_FORM_addrx2:
+ case dwarf::DW_FORM_addrx3:
+ case dwarf::DW_FORM_addrx4:
+ case dwarf::DW_FORM_GNU_addr_index:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/// True if \p Form stores an index into .debug_addr rather than the address
+/// itself. DW_FORM_LLVM_addrx_offset is deliberately excluded: it packs an
+/// index and an offset into a single value and needs its own handling.
+static bool isIndexedAddressForm(dwarf::Form Form) {
+ switch (Form) {
+ case dwarf::DW_FORM_addrx:
+ case dwarf::DW_FORM_addrx1:
+ case dwarf::DW_FORM_addrx2:
+ case dwarf::DW_FORM_addrx3:
+ case dwarf::DW_FORM_addrx4:
+ case dwarf::DW_FORM_GNU_addr_index:
+ return true;
+ default:
+ return false;
+ }
+}
+
/// If DW_AT_low_pc exists sets LowPC and returns true.
static bool getLowPC(const DIE &Die, const DWARFUnit &DU, uint64_t &LowPC,
uint64_t &SectionIndex) {
@@ -406,8 +441,7 @@ static bool getLowPC(const DIE &Die, const DWARFUnit &DU, uint64_t &LowPC,
dwarf::Form Form = DvalLowPc.getForm();
bool AddrOffset = Form == dwarf::DW_FORM_LLVM_addrx_offset;
uint64_t LowPcValue = DvalLowPc.getDIEInteger().getValue();
- if (Form == dwarf::DW_FORM_GNU_addr_index || Form == dwarf::DW_FORM_addrx ||
- AddrOffset) {
+ if (isIndexedAddressForm(Form) || AddrOffset) {
uint32_t Index = AddrOffset ? (LowPcValue >> 32) : LowPcValue;
std::optional<object::SectionedAddress> SA =
@@ -427,14 +461,27 @@ static bool getLowPC(const DIE &Die, const DWARFUnit &DU, uint64_t &LowPC,
}
/// If DW_AT_high_pc exists sets HighPC and returns true.
-static bool getHighPC(const DIE &Die, const uint64_t LowPC, uint64_t &HighPC) {
+static bool getHighPC(const DIE &Die, const DWARFUnit &DU, const uint64_t LowPC,
+ uint64_t &HighPC) {
DIEValue DvalHighPc = Die.findAttribute(dwarf::DW_AT_high_pc);
if (!DvalHighPc)
return false;
- if (DvalHighPc.getForm() == dwarf::DW_FORM_addr)
- HighPC = DvalHighPc.getDIEInteger().getValue();
- else
- HighPC = LowPC + DvalHighPc.getDIEInteger().getValue();
+ const dwarf::Form Form = DvalHighPc.getForm();
+ const uint64_t Value = DvalHighPc.getDIEInteger().getValue();
+ if (!isAddressForm(Form)) {
+ // Class constant: an offset from DW_AT_low_pc.
+ HighPC = LowPC + Value;
+ return true;
+ }
+ if (!isIndexedAddressForm(Form)) {
+ HighPC = Value;
+ return true;
+ }
+ std::optional<object::SectionedAddress> SA =
+ DU.getAddrOffsetSectionItem(Value);
+ if (!SA)
+ return false;
+ HighPC = SA->Address;
return true;
}
@@ -447,7 +494,7 @@ static bool getLowAndHighPC(const DIE &Die, const DWARFUnit &DU,
uint64_t TempHighPC = HighPC;
uint64_t TempSectionIndex = SectionIndex;
if (getLowPC(Die, DU, TempLowPC, TempSectionIndex) &&
- getHighPC(Die, TempLowPC, TempHighPC)) {
+ getHighPC(Die, DU, TempLowPC, TempHighPC)) {
LowPC = TempLowPC;
HighPC = TempHighPC;
SectionIndex = TempSectionIndex;
@@ -1151,13 +1198,28 @@ void DWARFRewriter::updateUnitDebugInfo(
FormHighPC = HighPCVal.getForm();
}
- if (FormLowPC == dwarf::DW_FORM_addrx ||
- FormLowPC == dwarf::DW_FORM_GNU_addr_index)
+ // A fixed-width indexed form caps the index it can encode, and the index
+ // assigned below comes from a rebuilt .debug_addr that may hold more
+ // entries than the input's. Normalize to the ULEB-encoded form so the
+ // value always fits. DW_FORM_addrx is DWARF 5, so the GNU extension used
+ // by earlier split DWARF keeps its own form.
+ auto normalizeIndexedForm = [](dwarf::Form Form) {
+ return Form == dwarf::DW_FORM_GNU_addr_index ? Form
+ : dwarf::DW_FORM_addrx;
+ };
+
+ if (isIndexedAddressForm(FormLowPC)) {
LowPC = AddressWriter.getIndexFromAddress(LowPC, Unit);
+ FormLowPC = normalizeIndexedForm(FormLowPC);
+ }
// The value has to match the class of the form it is written under.
- const uint64_t HighPCEncoded =
- FormHighPC == dwarf::DW_FORM_addr ? HighPC : Size;
+ uint64_t HighPCEncoded = Size;
+ if (isIndexedAddressForm(FormHighPC)) {
+ HighPCEncoded = AddressWriter.getIndexFromAddress(HighPC, Unit);
+ FormHighPC = normalizeIndexedForm(FormHighPC);
+ } else if (isAddressForm(FormHighPC))
+ HighPCEncoded = HighPC;
if (LowPCVal)
DIEBldr.replaceValue(Die, AttrLowPC, FormLowPC, DIEInteger(LowPC));
@@ -1249,14 +1311,18 @@ void DWARFRewriter::updateUnitDebugInfo(
DIEValue LowPCVal = Die->findAttribute(dwarf::DW_AT_low_pc);
DIEValue HighPCVal = Die->findAttribute(dwarf::DW_AT_high_pc);
if (FunctionRanges.empty()) {
- // There is no output range for this DIE, so point it at address 0 and
- // keep its original size. The stored DW_AT_high_pc is that size only
- // for non-address forms; for DW_FORM_addr it is the end address.
+ // There is no output range for this DIE, so point it at address 0
+ // and keep its original size. The stored DW_AT_high_pc is that size
+ // only for non-address forms; for an address form it is the end
+ // address, and for an indexed one it is an address table index that
+ // cannot be resolved to a size here.
uint64_t OriginalSize = 1;
if (LowPCVal && HighPCVal) {
- if (HighPCVal.getForm() != dwarf::DW_FORM_addr)
+ const dwarf::Form HighForm = HighPCVal.getForm();
+ if (!isAddressForm(HighForm))
OriginalSize = HighPCVal.getDIEInteger().getValue();
- else if (LowPCVal.getForm() == dwarf::DW_FORM_addr)
+ else if (HighForm == dwarf::DW_FORM_addr &&
+ LowPCVal.getForm() == dwarf::DW_FORM_addr)
OriginalSize = HighPCVal.getDIEInteger().getValue() -
LowPCVal.getDIEInteger().getValue();
}
@@ -1330,7 +1396,7 @@ void DWARFRewriter::updateUnitDebugInfo(
UpdatedAddress =
Function->translateInputToOutputAddress(UpdatedAddress);
- if (AttrVal.getForm() == dwarf::DW_FORM_addrx) {
+ if (isIndexedAddressForm(AttrVal.getForm())) {
const uint32_t Index =
AddressWriter.getIndexFromAddress(UpdatedAddress, Unit);
DIEBldr.replaceValue(Die, AttrVal.getAttribute(), AttrVal.getForm(),
@@ -1595,8 +1661,7 @@ void DWARFRewriter::updateUnitDebugInfo(
dwarf::Form Form = LowPCAttrInfo.getForm();
assert(Form != dwarf::DW_FORM_LLVM_addrx_offset &&
"DW_FORM_LLVM_addrx_offset is not supported");
- if (Form == dwarf::DW_FORM_addrx ||
- Form == dwarf::DW_FORM_GNU_addr_index) {
+ if (isIndexedAddressForm(Form)) {
const uint32_t Index = AddressWriter.getIndexFromAddress(
NewAddress ? NewAddress : Address, Unit);
DIEBldr.replaceValue(Die, LowPCAttrInfo.getAttribute(),
@@ -1668,9 +1733,7 @@ void DWARFRewriter::updateDWARFObjectAddressRanges(
DIEInteger(DebugRangesOffset));
if (!RangesBase) {
- if (LowPCAttrInfo &&
- LowPCAttrInfo.getForm() != dwarf::DW_FORM_GNU_addr_index &&
- LowPCAttrInfo.getForm() != dwarf::DW_FORM_addrx)
+ if (LowPCAttrInfo && !isIndexedAddressForm(LowPCAttrInfo.getForm()))
DIEBldr.replaceValue(&Die, dwarf::DW_AT_low_pc, LowPCAttrInfo.getForm(),
DIEInteger(0));
return;
@@ -2465,7 +2528,7 @@ void DWARFRewriter::convertToRangesPatchDebugInfo(
// DW_FORM_addrx. Former is when DW_AT_rnglists_base is present. Latter is
// when it's absent.
if (IsUnitDie) {
- if (LowForm == dwarf::DW_FORM_addrx) {
+ if (isIndexedAddressForm(LowForm)) {
const uint32_t Index = AddressWriter.getIndexFromAddress(0, Unit);
DIEBldr.replaceValue(&Die, LowPCAttrInfo.getAttribute(),
LowPCAttrInfo.getForm(), DIEInteger(Index));
diff --git a/bolt/test/AArch64/dwarf5-addrx-lowpc-highpc.s b/bolt/test/AArch64/dwarf5-addrx-lowpc-highpc.s
new file mode 100644
index 0000000000000..df88e745eca6b
--- /dev/null
+++ b/bolt/test/AArch64/dwarf5-addrx-lowpc-highpc.s
@@ -0,0 +1,175 @@
+# Check that llvm-bolt handles DW_AT_low_pc and DW_AT_high_pc encoded with the
+# fixed-size indexed address forms.
+#
+# DW_FORM_addrx is already covered by the DWARF 5 tests; this test uses
+# DW_FORM_addrx2, and gives "foo" an indexed DW_AT_high_pc so the read side has
+# to resolve an index there too.
+
+# REQUIRES: system-linux
+
+# RUN: llvm-mc --filetype=obj --triple aarch64-unknown-linux %s -o %t.o
+# RUN: ld.lld --no-pie %t.o -o %t.exe -q
+
+# Guard the fixture: if these forms ever stop being emitted the test would
+# quietly stop covering the indexed path.
+# RUN: llvm-dwarfdump --show-form --debug-info %t.exe \
+# RUN: | FileCheck --check-prefix=CHECK-PRE %s
+
+# CHECK-PRE: version = 0x0005
+# CHECK-PRE: DW_AT_name [DW_FORM_string] ("_start")
+# CHECK-PRE-NEXT: DW_AT_low_pc [DW_FORM_addrx2]
+# CHECK-PRE-NEXT: DW_AT_high_pc [DW_FORM_data4]
+# CHECK-PRE: DW_AT_name [DW_FORM_string] ("foo")
+# CHECK-PRE-NEXT: DW_AT_low_pc [DW_FORM_addrx2]
+# CHECK-PRE-NEXT: DW_AT_high_pc [DW_FORM_addrx2]
+
+# The attributes must survive cloning rather than being dropped.
+# RUN: llvm-bolt %t.exe -o %t.bolt --update-debug-sections 2>&1 \
+# RUN: | FileCheck --check-prefix=CHECK-BOLT %s
+
+# CHECK-BOLT-NOT: Unsupported attribute form
+
+# The values stay .debug_addr indices, but the fixed-width form is normalized
+# to DW_FORM_addrx: the index is reassigned against a rebuilt .debug_addr that
+# may hold more entries than the input's, and a fixed-width form would silently
+# truncate one that no longer fits.
+# RUN: llvm-dwarfdump --show-form --debug-info %t.bolt \
+# RUN: | FileCheck --check-prefix=CHECK-POST %s \
+# RUN: --implicit-check-not=DW_FORM_addrx2
+
+# CHECK-POST: DW_AT_name [DW_FORM_string] ("_start")
+# CHECK-POST-NEXT: DW_AT_low_pc [DW_FORM_addrx]
+# CHECK-POST-NEXT: DW_AT_high_pc [DW_FORM_data4]
+# CHECK-POST: DW_AT_name [DW_FORM_string] ("foo")
+# CHECK-POST-NEXT: DW_AT_low_pc [DW_FORM_addrx]
+# CHECK-POST-NEXT: DW_AT_high_pc [DW_FORM_addrx]
+
+# RUN: llvm-dwarfdump --verify %t.bolt \
+# RUN: | FileCheck --check-prefix=CHECK-VERIFY %s
+
+# CHECK-VERIFY: No errors.
+
+# Each index has to resolve to where the function actually ended up, and foo's
+# high_pc to the address one past its end.
+# RUN: llvm-dwarfdump --debug-info %t.bolt > %t.dump
+# RUN: llvm-nm %t.bolt | awk '$3=="_start"{print $1}' > %t.start-addr
+# RUN: llvm-nm %t.bolt | awk '$3=="foo"{print $1}' > %t.foo-addr
+# RUN: bash -c "read A < %t.start-addr; \
+# RUN: grep -q \"DW_AT_low_pc.*(0x\$A)\" %t.dump"
+# RUN: bash -c "read A < %t.foo-addr; \
+# RUN: grep -q \"DW_AT_low_pc.*(0x\$A)\" %t.dump"
+# RUN: bash -c "read A < %t.foo-addr; \
+# RUN: E=\$(printf '%%016x' \$((0x\$A + 8))); \
+# RUN: grep -q \"DW_AT_high_pc.*(0x\$E)\" %t.dump"
+
+ .text
+ .file 1 "dwarf5-addrx-lowpc-highpc.s"
+ .globl _start
+ .type _start, %function
+_start:
+.L_start_begin:
+ .cfi_startproc
+ .loc 1 1 0
+ bl foo
+ .loc 1 2 0
+ ret
+ .cfi_endproc
+.L_start_end:
+ .size _start, .-_start
+
+ .globl foo
+ .type foo, %function
+foo:
+.Lfoo_begin:
+ .cfi_startproc
+ .loc 1 4 0
+ add x0, x0, #1
+ .loc 1 5 0
+ ret
+ .cfi_endproc
+.Lfoo_end:
+ .size foo, .-foo
+
+ .section .debug_addr,"", at progbits
+.Ldebug_addr_start:
+ .long .Ldebug_addr_end - .Ldebug_addr_version // unit_length
+.Ldebug_addr_version:
+ .short 5 // version
+ .byte 8 // address_size
+ .byte 0 // segment_selector_size
+.Laddr_base:
+ .quad .L_start_begin // index 0
+ .quad .Lfoo_begin // index 1
+ .quad .Lfoo_end // index 2
+.Ldebug_addr_end:
+
+ .section .debug_abbrev,"", at progbits
+ .uleb128 1 // abbrev code: compile unit
+ .uleb128 0x11 // DW_TAG_compile_unit
+ .byte 1 // DW_CHILDREN_yes
+ .uleb128 0x03 // DW_AT_name
+ .uleb128 0x08 // DW_FORM_string
+ .uleb128 0x10 // DW_AT_stmt_list
+ .uleb128 0x17 // DW_FORM_sec_offset
+ .uleb128 0x73 // DW_AT_addr_base
+ .uleb128 0x17 // DW_FORM_sec_offset
+ .uleb128 0x11 // DW_AT_low_pc
+ .uleb128 0x1b // DW_FORM_addrx
+ .uleb128 0x12 // DW_AT_high_pc
+ .uleb128 0x06 // DW_FORM_data4
+ .uleb128 0
+ .uleb128 0
+
+ .uleb128 2 // abbrev code: low_pc addrx2, high_pc data4
+ .uleb128 0x2e // DW_TAG_subprogram
+ .byte 0 // DW_CHILDREN_no
+ .uleb128 0x03 // DW_AT_name
+ .uleb128 0x08 // DW_FORM_string
+ .uleb128 0x11 // DW_AT_low_pc
+ .uleb128 0x2a // DW_FORM_addrx2
+ .uleb128 0x12 // DW_AT_high_pc
+ .uleb128 0x06 // DW_FORM_data4
+ .uleb128 0
+ .uleb128 0
+
+ .uleb128 3 // abbrev code: low_pc addrx2, high_pc addrx2
+ .uleb128 0x2e // DW_TAG_subprogram
+ .byte 0 // DW_CHILDREN_no
+ .uleb128 0x03 // DW_AT_name
+ .uleb128 0x08 // DW_FORM_string
+ .uleb128 0x11 // DW_AT_low_pc
+ .uleb128 0x2a // DW_FORM_addrx2
+ .uleb128 0x12 // DW_AT_high_pc
+ .uleb128 0x2a // DW_FORM_addrx2
+ .uleb128 0
+ .uleb128 0
+
+ .byte 0 // end of abbrevs
+
+ .section .debug_info,"", at progbits
+ .long .Lcu_end - .Lcu_start // unit_length
+.Lcu_start:
+ .short 5 // DWARF version
+ .byte 1 // DW_UT_compile
+ .byte 8 // address size
+ .long 0 // abbrev offset
+
+ .uleb128 1 // DW_TAG_compile_unit
+ .asciz "dwarf5-addrx-lowpc-highpc.s"
+ .long 0 // DW_AT_stmt_list
+ .long .Laddr_base - .Ldebug_addr_start // DW_AT_addr_base
+ .uleb128 0 // DW_AT_low_pc -> addr[0]
+ .long .Lfoo_end - .L_start_begin // DW_AT_high_pc
+
+ .uleb128 2 // DW_TAG_subprogram
+ .asciz "_start"
+ .short 0 // DW_AT_low_pc -> addr[0]
+ .long .L_start_end - .L_start_begin // DW_AT_high_pc
+
+ .uleb128 3 // DW_TAG_subprogram
+ .asciz "foo"
+ .short 1 // DW_AT_low_pc -> addr[1]
+ .short 2 // DW_AT_high_pc -> addr[2]
+
+ .byte 0 // end of children
+.Lcu_end:
``````````
</details>
https://github.com/llvm/llvm-project/pull/227591
More information about the llvm-commits
mailing list