[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