[llvm] [BOLT] Emit DW_AT_high_pc matching the class of its form (PR #227066)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 28 10:56:37 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-bolt
Author: YongKang Zhu (yozhu)
<details>
<summary>Changes</summary>
DW_AT_high_pc holding an offset from DW_AT_low_pc is a DWARF 4 addition; in DWARF 2 and 3 the attribute is always class address. updateLowPCHighPC always wrote "HighPC - LowPC" while reusing whatever form the input DIE had, so a DWARF 2 CU using DW_FORM_addr ended up storing a length where an end address is expected.
BOLT should write the end address when the form is DW_FORM_addr, and the length otherwise. The default form for a new attribute is now DW_FORM_addr below DWARF 4, and size needs to be widened to 64 bits so DW_FORM_data8 won't be truncated. The FunctionRanges.empty() code path no longer treats a raw DW_FORM_addr high_pc as a size.
---
Full diff: https://github.com/llvm/llvm-project/pull/227066.diff
3 Files Affected:
- (modified) bolt/lib/Rewrite/DWARFRewriter.cpp (+25-8)
- (added) bolt/test/AArch64/dwarf2-highpc-addr-form.s (+140)
- (modified) bolt/test/AArch64/go_dwarf.test (+6-1)
``````````diff
diff --git a/bolt/lib/Rewrite/DWARFRewriter.cpp b/bolt/lib/Rewrite/DWARFRewriter.cpp
index acb1ba6c8468b..1da356f16e1c9 100644
--- a/bolt/lib/Rewrite/DWARFRewriter.cpp
+++ b/bolt/lib/Rewrite/DWARFRewriter.cpp
@@ -1131,8 +1131,12 @@ void DWARFRewriter::updateUnitDebugInfo(
dwarf::Attribute AttrLowPC = dwarf::DW_AT_low_pc;
dwarf::Form FormLowPC = dwarf::DW_FORM_addr;
dwarf::Attribute AttrHighPC = dwarf::DW_AT_high_pc;
- dwarf::Form FormHighPC = dwarf::DW_FORM_data4;
- const uint32_t Size = HighPC - LowPC;
+ // DW_AT_high_pc holding an offset from DW_AT_low_pc is a DWARF 4 addition;
+ // before that the attribute is always address.
+ dwarf::Form FormHighPC =
+ Unit.getVersion() >= 4 ? dwarf::DW_FORM_data4 : dwarf::DW_FORM_addr;
+ // Computed before LowPC is possibly replaced by an address table index.
+ const uint64_t Size = HighPC - LowPC;
// Whatever was generated is not low_pc/high_pc, so will reset to
// default for size 1.
if (!LowPCVal || !HighPCVal) {
@@ -1151,15 +1155,20 @@ void DWARFRewriter::updateUnitDebugInfo(
FormLowPC == dwarf::DW_FORM_GNU_addr_index)
LowPC = AddressWriter.getIndexFromAddress(LowPC, Unit);
+ // 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;
+
if (LowPCVal)
DIEBldr.replaceValue(Die, AttrLowPC, FormLowPC, DIEInteger(LowPC));
else
DIEBldr.addValue(Die, AttrLowPC, FormLowPC, DIEInteger(LowPC));
if (HighPCVal) {
- DIEBldr.replaceValue(Die, AttrHighPC, FormHighPC, DIEInteger(Size));
+ DIEBldr.replaceValue(Die, AttrHighPC, FormHighPC,
+ DIEInteger(HighPCEncoded));
} else {
DIEBldr.deleteValue(Die, dwarf::DW_AT_ranges);
- DIEBldr.addValue(Die, AttrHighPC, FormHighPC, DIEInteger(Size));
+ DIEBldr.addValue(Die, AttrHighPC, FormHighPC, DIEInteger(HighPCEncoded));
}
};
@@ -1240,10 +1249,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()) {
- if (LowPCVal && HighPCVal)
- FunctionRanges.push_back({0, HighPCVal.getDIEInteger().getValue()});
- else
- FunctionRanges.push_back({0, 1});
+ // 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.
+ uint64_t OriginalSize = 1;
+ if (LowPCVal && HighPCVal) {
+ if (HighPCVal.getForm() != dwarf::DW_FORM_addr)
+ OriginalSize = HighPCVal.getDIEInteger().getValue();
+ else if (LowPCVal.getForm() == dwarf::DW_FORM_addr)
+ OriginalSize = HighPCVal.getDIEInteger().getValue() -
+ LowPCVal.getDIEInteger().getValue();
+ }
+ FunctionRanges.push_back({0, OriginalSize});
}
if (FunctionRanges.size() == 1 && !opts::AlwaysConvertToRanges) {
diff --git a/bolt/test/AArch64/dwarf2-highpc-addr-form.s b/bolt/test/AArch64/dwarf2-highpc-addr-form.s
new file mode 100644
index 0000000000000..29ec40a384fad
--- /dev/null
+++ b/bolt/test/AArch64/dwarf2-highpc-addr-form.s
@@ -0,0 +1,140 @@
+# Check that llvm-bolt keeps DW_AT_high_pc consistent with its form when it
+# relocates a function described by a DWARF 2 CU.
+#
+# DW_AT_high_pc holding an offset from DW_AT_low_pc was introduced in DWARF 4.
+# Before that the attribute is always class address, so a DWARF 2 producer emits
+# it with DW_FORM_addr. BOLT used to write the size of the function regardless
+# of the form it was reusing, leaving high_pc below low_pc. Consumers derive the
+# size as "high_pc - low_pc", so the subtraction underflowed and the function
+# appeared to span almost the entire address space.
+#
+# go_dwarf.test covers the same form, but there the function keeps its input
+# address. Here it is relocated, so the address arithmetic is covered 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
+# RUN: llvm-bolt %t.exe -o %t.bolt --update-debug-sections
+
+# The CU must be DWARF 2 with an address-form high_pc, otherwise this test
+# would not exercise the address class path.
+# RUN: llvm-dwarfdump --show-form --debug-info %t.exe \
+# RUN: | FileCheck --check-prefix=CHECK-PRE %s
+
+# CHECK-PRE: version = 0x0002
+# CHECK-PRE: DW_TAG_subprogram
+# CHECK-PRE: DW_AT_low_pc [DW_FORM_addr]
+# CHECK-PRE-NEXT: DW_AT_high_pc [DW_FORM_addr]
+
+# The form is preserved, so the value has to remain an end address.
+# RUN: llvm-dwarfdump --show-form --debug-info %t.bolt \
+# RUN: | FileCheck --check-prefix=CHECK-POST %s
+
+# CHECK-POST: DW_TAG_subprogram
+# CHECK-POST: DW_AT_low_pc [DW_FORM_addr]
+# CHECK-POST-NEXT: DW_AT_high_pc [DW_FORM_addr]
+
+# An inverted range is reported as "Invalid address range".
+# RUN: llvm-dwarfdump --verify %t.bolt \
+# RUN: | FileCheck --check-prefix=CHECK-VERIFY %s
+
+# CHECK-VERIFY: No errors.
+
+# Every high_pc must stay above its low_pc, and the code is expected to have
+# moved, so the subprogram addresses must differ from the input.
+# RUN: llvm-dwarfdump --debug-info %t.bolt \
+# RUN: | awk '/DW_AT_low_pc/{gsub(/[()]/,"",$2); L=$2} \
+# RUN: /DW_AT_high_pc/{gsub(/[()]/,"",$2); print L, $2}' > %t.pcs
+# RUN: bash -c "test -s %t.pcs; while read L H; do \
+# RUN: test \$((H)) -gt \$((L)) || exit 1; done < %t.pcs"
+# RUN: llvm-dwarfdump --debug-info %t.exe \
+# RUN: | awk '/DW_AT_low_pc/{print $2}' > %t.pcs-orig
+# RUN: llvm-dwarfdump --debug-info %t.bolt \
+# RUN: | awk '/DW_AT_low_pc/{print $2}' > %t.pcs-bolt
+# RUN: not diff %t.pcs-orig %t.pcs-bolt
+
+ .text
+ .file 1 "dwarf2-highpc-addr-form.s"
+ .globl _start
+ .type _start, %function
+_start:
+.L_start_begin:
+ .cfi_startproc
+ .loc 1 1 0
+ bl foo
+ .loc 1 2 0
+ bl foo
+ .loc 1 3 0
+ ret
+ .cfi_endproc
+.L_start_end:
+ .size _start, .-_start
+
+ .globl foo
+ .type foo, %function
+foo:
+.Lfoo_begin:
+ .cfi_startproc
+ .loc 1 5 0
+ add x0, x0, #1
+ .loc 1 6 0
+ ret
+ .cfi_endproc
+.Lfoo_end:
+ .size foo, .-foo
+
+ .section .debug_abbrev,"", at progbits
+ .uleb128 1 // Abbrev code
+ .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 0x06 // DW_FORM_data4
+ .uleb128 0x11 // DW_AT_low_pc
+ .uleb128 0x01 // DW_FORM_addr
+ .uleb128 0x12 // DW_AT_high_pc
+ .uleb128 0x01 // DW_FORM_addr
+ .uleb128 0
+ .uleb128 0
+
+ .uleb128 2 // Abbrev code
+ .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 0x01 // DW_FORM_addr
+ .uleb128 0x12 // DW_AT_high_pc
+ .uleb128 0x01 // DW_FORM_addr
+ .uleb128 0
+ .uleb128 0
+
+ .byte 0 // End of abbrevs
+
+ .section .debug_info,"", at progbits
+ .long .Lcu_end - .Lcu_start // Length
+.Lcu_start:
+ .short 2 // DWARF version
+ .long 0 // Abbrev offset
+ .byte 8 // Address size
+
+ .uleb128 1 // DW_TAG_compile_unit
+ .asciz "dwarf2-highpc-addr-form.s"
+ .long 0 // DW_AT_stmt_list
+ .quad .L_start_begin
+ .quad .Lfoo_end
+
+ .uleb128 2 // DW_TAG_subprogram
+ .asciz "_start"
+ .quad .L_start_begin
+ .quad .L_start_end
+
+ .uleb128 2 // DW_TAG_subprogram
+ .asciz "foo"
+ .quad .Lfoo_begin
+ .quad .Lfoo_end
+
+ .byte 0 // End of children
+.Lcu_end:
diff --git a/bolt/test/AArch64/go_dwarf.test b/bolt/test/AArch64/go_dwarf.test
index f5c4a4a9f4786..2c962e2f8475d 100644
--- a/bolt/test/AArch64/go_dwarf.test
+++ b/bolt/test/AArch64/go_dwarf.test
@@ -31,6 +31,9 @@ CHECKORIG-NEXT: DW_AT_high_pc (0x0000000000000684)
# Check the bolted binary.
RUN: llvm-dwarfdump -debug-info %t.bolt 2>&1 | FileCheck %s
+RUN: llvm-dwarfdump --verify %t.bolt | FileCheck --check-prefix=CHECK-VERIFY %s
+
+CHECK-VERIFY: No errors.
CHECK: DW_TAG_compile_unit
CHECK-NEXT: DW_AT_producer
@@ -50,4 +53,6 @@ CHECK-NEXT: DW_AT_decl_line (1)
CHECK-NEXT: DW_AT_decl_column (5)
CHECK-NEXT: DW_AT_type
CHECK-NEXT: DW_AT_low_pc (0x0000000000000660)
-CHECK-NEXT: DW_AT_high_pc (0x0000000000000024)
+# DW_AT_high_pc keeps its DW_FORM_addr form, so it holds the end address rather
+# than the size of the function.
+CHECK-NEXT: DW_AT_high_pc (0x0000000000000684)
``````````
</details>
https://github.com/llvm/llvm-project/pull/227066
More information about the llvm-commits
mailing list