[llvm] 5e8490a - [DWARFLinker] Constrain a function's high_pc to its own symbol (#215952)

via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 14 13:59:44 PDT 2026


Author: Jonas Devlieghere
Date: 2026-08-14T13:59:39-07:00
New Revision: 5e8490a1cf89bd94194246d0569c232a923d0161

URL: https://github.com/llvm/llvm-project/commit/5e8490a1cf89bd94194246d0569c232a923d0161
DIFF: https://github.com/llvm/llvm-project/commit/5e8490a1cf89bd94194246d0569c232a923d0161.diff

LOG: [DWARFLinker] Constrain a function's high_pc to its own symbol (#215952)

Mach-O objects built with .subsections_via_symbols make every symbol an
independently placeable atom, and the linker packs atoms without
preserving the spacing they had in the object file.

I have an example where the compiler describes such a subprogram as
extending past its own atom. While it's debatable whether that's a good
idea, it's not invalid in the object file. However, once linked, it is
invalid.

We can make dsymutil resilient against this by looking at the size of
the symbol in the debug map and adjusting the end_pc. I'm doing so
conservatively so that only a collision is repaired. Already
overlapping/invalid ranges remain untouched.

rdar://184768778

Added: 
    

Modified: 
    llvm/include/llvm/DWARFLinker/AddressesMap.h
    llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
    llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
    llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.h
    llvm/test/tools/dsymutil/subprogram-high-pc-past-symbol.test
    llvm/tools/dsymutil/DwarfLinkerForBinary.h

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/DWARFLinker/AddressesMap.h b/llvm/include/llvm/DWARFLinker/AddressesMap.h
index 443e6f5bb4186..d554fef4c6394 100644
--- a/llvm/include/llvm/DWARFLinker/AddressesMap.h
+++ b/llvm/include/llvm/DWARFLinker/AddressesMap.h
@@ -103,24 +103,29 @@ class AddressesMap {
     return std::nullopt;
   }
 
-  /// Constrains the source-space end of a code range, given its start \p LowPC,
-  /// its end \p HighPC as an address, and the \p Adjustment all of its
-  /// addresses shift by in the output.
+  /// Constrains the end of the code range starting at \p LowPC, whose addresses
+  /// shift by \p Adjustment in the output. \p HighPC is an address, not a
+  /// length.
   ///
-  /// Neighbouring symbols shift by 
diff erent amounts, so a range reaching past
-  /// the symbol holding its start can land inside the next symbol in the
-  /// output. Only that collision is repaired. Coverage that overlaps nothing is
-  /// left alone, and a symbol nested in the same extent is never a neighbour,
-  /// so it cannot shorten a range that legitimately spans it.
+  /// The linker places symbols independently, so a range overrunning the symbol
+  /// it starts in can cover a 
diff erent one once linked. Only that overlap is
+  /// repaired.
   uint64_t constrainCodeRangeHighPC(uint64_t LowPC, uint64_t HighPC,
                                     int64_t Adjustment) {
     std::optional<SymbolRange> Symbol = getSymbolRangeForAddress(LowPC);
     if (!Symbol)
       return HighPC;
+    assert(Symbol->LowPC <= LowPC && LowPC < Symbol->HighPC &&
+           "Symbol range must contain the address it was looked up for");
+    uint64_t LinkedSymbolHighPC = Symbol->HighPC + Adjustment;
+    assert(LinkedSymbolHighPC > Symbol->LowPC + Adjustment &&
+           "Adjusting a symbol range must preserve its order");
     std::optional<uint64_t> NextStart =
-        getNextLinkedSymbolStart(Symbol->HighPC + Adjustment);
+        getNextLinkedSymbolStart(LinkedSymbolHighPC);
     if (!NextStart)
       return HighPC;
+    assert(*NextStart >= LinkedSymbolHighPC &&
+           "A range must never be cut short of its own symbol");
     return std::min(HighPC, *NextStart - Adjustment);
   }
 

diff  --git a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
index 5c8f718c8670f..e41b8ef9fd9b9 100644
--- a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
+++ b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
@@ -1426,6 +1426,25 @@ unsigned DWARFLinker::DIECloner::cloneBlockAttribute(
   return Die.addValue(DIEAlloc, Value)->sizeOf(OrigUnit.getFormParams());
 }
 
+/// Returns \p InputDIE's DW_AT_high_pc value \p HighPC, constrained so the code
+/// range it ends stays clear of the symbol the linker places next. \p IsLength
+/// tells whether high_pc is encoded as a length rather than an address, and
+/// \p PCOffset is the amount the range shifts by in the output.
+///
+/// A scope nested in a function inherits the overrun of the function, so it is
+/// constrained as well.
+static uint64_t constrainHighPC(const DWARFDie &InputDIE, uint64_t HighPC,
+                                bool IsLength, int64_t PCOffset,
+                                AddressesMap &Addresses) {
+  std::optional<uint64_t> LowPC =
+      dwarf::toAddress(InputDIE.find(dwarf::DW_AT_low_pc));
+  if (!LowPC)
+    return HighPC;
+  uint64_t Constrained = Addresses.constrainCodeRangeHighPC(
+      *LowPC, IsLength ? *LowPC + HighPC : HighPC, PCOffset);
+  return IsLength ? Constrained - *LowPC : Constrained;
+}
+
 unsigned DWARFLinker::DIECloner::cloneAddressAttribute(
     DIE &Die, const DWARFDie &InputDIE, AttributeSpec AttrSpec,
     unsigned AttrSize, const DWARFFormValue &Val, const CompileUnit &Unit,
@@ -1474,14 +1493,9 @@ unsigned DWARFLinker::DIECloner::cloneAddressAttribute(
     else
       return 0;
   } else {
-    // A nested scope inherits the range its parent function overran, so every
-    // range is constrained, not just the subprogram's own.
-    if (AttrSpec.Attr == dwarf::DW_AT_high_pc) {
-      if (std::optional<uint64_t> LowPC =
-              dwarf::toAddress(InputDIE.find(dwarf::DW_AT_low_pc)))
-        Addr = ObjFile.Addresses->constrainCodeRangeHighPC(*LowPC, *Addr,
-                                                           Info.PCOffset);
-    }
+    if (AttrSpec.Attr == dwarf::DW_AT_high_pc)
+      Addr = constrainHighPC(InputDIE, *Addr, /*IsLength=*/false, Info.PCOffset,
+                             *ObjFile.Addresses);
     *Addr += Info.PCOffset;
   }
 
@@ -1640,13 +1654,12 @@ unsigned DWARFLinker::DIECloner::cloneScalarAttribute(
     return 0;
   }
 
-  if (AttrSpec.Attr == dwarf::DW_AT_high_pc) {
-    if (std::optional<uint64_t> LowPC =
-            dwarf::toAddress(InputDIE.find(dwarf::DW_AT_low_pc)))
-      Value = File.Addresses->constrainCodeRangeHighPC(*LowPC, *LowPC + Value,
-                                                       Info.PCOffset) -
-              *LowPC;
-  }
+  // 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)
+    Value = constrainHighPC(InputDIE, Value, /*IsLength=*/true, Info.PCOffset,
+                            *File.Addresses);
 
   DIE::value_iterator Patch =
       Die.addValue(DIEAlloc, dwarf::Attribute(AttrSpec.Attr),

diff  --git a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
index 77a1d85edb772..e03d339b24013 100644
--- a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
@@ -525,15 +525,11 @@ size_t DIEAttributeCloner::cloneScalarAttr(
       !OutUnit.isCompileUnit())
     return 0;
 
-  // A nested scope inherits the range its parent function overran, so every
-  // range is constrained, not just the subprogram's own.
-  if (AttrSpec.Attr == dwarf::DW_AT_high_pc && FuncAddressAdjustment) {
-    if (std::optional<uint64_t> LowPC =
-            dwarf::toAddress(InUnit.find(InputDieEntry, dwarf::DW_AT_low_pc)))
-      Value = InUnit.getContaingFile().Addresses->constrainCodeRangeHighPC(
-                  *LowPC, *LowPC + Value, *FuncAddressAdjustment) -
-              *LowPC;
-  }
+  // 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)
+    Value = constrainHighPC(Value, /*IsLength=*/true);
 
   auto Result =
       Generator.addScalarAttribute(AttrSpec.Attr, ResultingForm, Value);
@@ -689,12 +685,8 @@ size_t DIEAttributeCloner::cloneAddressAttr(
     else
       return 0;
   } else {
-    if (AttrSpec.Attr == dwarf::DW_AT_high_pc && FuncAddressAdjustment) {
-      if (std::optional<uint64_t> LowPC =
-              dwarf::toAddress(InUnit.find(InputDieEntry, dwarf::DW_AT_low_pc)))
-        Addr = InUnit.getContaingFile().Addresses->constrainCodeRangeHighPC(
-            *LowPC, *Addr, *FuncAddressAdjustment);
-    }
+    if (AttrSpec.Attr == dwarf::DW_AT_high_pc)
+      Addr = constrainHighPC(*Addr, /*IsLength=*/false);
     if (VarAddressAdjustment)
       *Addr += *VarAddressAdjustment;
     else if (FuncAddressAdjustment)
@@ -712,6 +704,19 @@ size_t DIEAttributeCloner::cloneAddressAttr(
       .second;
 }
 
+uint64_t DIEAttributeCloner::constrainHighPC(uint64_t HighPC, bool IsLength) {
+  if (!FuncAddressAdjustment)
+    return HighPC;
+  std::optional<uint64_t> LowPC =
+      dwarf::toAddress(InUnit.find(InputDieEntry, dwarf::DW_AT_low_pc));
+  if (!LowPC)
+    return HighPC;
+  uint64_t Constrained =
+      InUnit.getContaingFile().Addresses->constrainCodeRangeHighPC(
+          *LowPC, IsLength ? *LowPC + HighPC : HighPC, *FuncAddressAdjustment);
+  return IsLength ? Constrained - *LowPC : Constrained;
+}
+
 unsigned DIEAttributeCloner::finalizeAbbreviations(bool HasChildrenToClone) {
   // Add the size of the abbreviation number to the output offset.
   AttrOutOffset +=

diff  --git a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.h b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.h
index b84258a864565..2a5a2250cc5b2 100644
--- a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.h
+++ b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.h
@@ -131,6 +131,15 @@ class DIEAttributeCloner {
   cloneAddressAttr(const DWARFFormValue &Val,
                    const DWARFAbbreviationDeclaration::AttributeSpec &AttrSpec);
 
+  /// Returns the input DIE's DW_AT_high_pc value \p HighPC, constrained so the
+  /// code range it ends stays clear of the symbol the linker places next. \p
+  /// IsLength tells whether high_pc is encoded as a length rather than an
+  /// address.
+  ///
+  /// A scope nested in a function inherits the overrun of the function, so it
+  /// is constrained as well.
+  uint64_t constrainHighPC(uint64_t HighPC, bool IsLength);
+
   /// Returns true if attribute should be skipped.
   bool
   shouldSkipAttribute(DWARFAbbreviationDeclaration::AttributeSpec AttrSpec);

diff  --git a/llvm/test/tools/dsymutil/subprogram-high-pc-past-symbol.test b/llvm/test/tools/dsymutil/subprogram-high-pc-past-symbol.test
index c58f75b0f9159..591a807fd20ad 100644
--- a/llvm/test/tools/dsymutil/subprogram-high-pc-past-symbol.test
+++ b/llvm/test/tools/dsymutil/subprogram-high-pc-past-symbol.test
@@ -3,8 +3,7 @@
 # A DW_AT_high_pc reaching past the code the linker kept for a function must be
 # cut short at the function the linker placed next, otherwise the two overlap in
 # the output and verification fails with "DIEs have overlapping address ranges".
-# A scope nested in the function inherits the overrun, so it has to be cut short
-# with its parent to stay inside it.
+# A scope nested in the function inherits the overrun and is cut short with it.
 
 # RUN: llvm-mc -triple arm64-apple-darwin -filetype=obj \
 # RUN:   %p/Inputs/subprogram-high-pc-past-symbol.s -o %t.o
@@ -16,8 +15,7 @@
 # RUN: echo '     - { sym: _a, objAddr: 0x0, binAddr: 0x1000, size: 0x4 }' >> %t.map
 # RUN: echo '     - { sym: _b, objAddr: 0x8, binAddr: 0x1004, size: 0x4 }' >> %t.map
 
-# _inside starts within the code the linker kept for _a, so it is nested rather
-# than adjacent and must not cut _a short.
+# _inside starts within _a's own code, so it must not cut _a short.
 
 # RUN: echo '     - { sym: _inside, binAddr: 0x1002, size: 0x2 }' >> %t.map
 # RUN: echo '...' >> %t.map
@@ -30,6 +28,27 @@
 # RUN: llvm-dwarfdump -a %t-parallel.out | FileCheck %s
 # RUN: llvm-dwarfdump --verify %t-parallel.out | FileCheck %s --check-prefix=VERIFY
 
+# A compile unit's high_pc spans every symbol in the unit, so it is not a range
+# to constrain. These binary addresses are low enough that constraining it would
+# cut the unit short of _b.
+
+# RUN: echo '---' > %t-unit.map
+# RUN: echo "triple: 'arm64-apple-darwin'" >> %t-unit.map
+# RUN: echo 'objects:' >> %t-unit.map
+# RUN: echo " - filename: '%/t.o'" >> %t-unit.map
+# RUN: echo '   symbols:' >> %t-unit.map
+# RUN: echo '     - { sym: _a, objAddr: 0x0, binAddr: 0x4, size: 0x4 }' >> %t-unit.map
+# RUN: echo '     - { sym: _b, objAddr: 0x8, binAddr: 0x8, size: 0x4 }' >> %t-unit.map
+# RUN: echo '...' >> %t-unit.map
+
+# RUN: dsymutil --linker classic -y %t-unit.map -f -o %t-unit-classic.out
+# RUN: llvm-dwarfdump -a %t-unit-classic.out | FileCheck %s --check-prefix=UNIT
+# RUN: llvm-dwarfdump --verify %t-unit-classic.out | FileCheck %s --check-prefix=VERIFY
+
+# RUN: dsymutil --linker parallel -y %t-unit.map -f -o %t-unit-parallel.out
+# RUN: llvm-dwarfdump -a %t-unit-parallel.out | FileCheck %s --check-prefix=UNIT
+# RUN: llvm-dwarfdump --verify %t-unit-parallel.out | FileCheck %s --check-prefix=VERIFY
+
 # Same again with DW_AT_high_pc as an address rather than a length.
 
 # RUN: llvm-mc -triple arm64-apple-darwin -filetype=obj \
@@ -54,4 +73,16 @@
 # CHECK-NEXT:   DW_AT_low_pc{{.*}}(0x0000000000001004)
 # CHECK-NEXT:   DW_AT_high_pc{{.*}}(0x0000000000001008)
 
+# UNIT:      DW_TAG_compile_unit
+# UNIT:        DW_AT_low_pc{{.*}}(0x0000000000000004)
+# UNIT-NEXT:   DW_AT_high_pc{{.*}}(0x000000000000000c)
+# UNIT:      DW_TAG_subprogram
+# UNIT:        DW_AT_name{{.*}}"a"
+# UNIT-NEXT:   DW_AT_low_pc{{.*}}(0x0000000000000004)
+# UNIT-NEXT:   DW_AT_high_pc{{.*}}(0x0000000000000008)
+# UNIT:      DW_TAG_subprogram
+# UNIT:        DW_AT_name{{.*}}"b"
+# UNIT-NEXT:   DW_AT_low_pc{{.*}}(0x0000000000000008)
+# UNIT-NEXT:   DW_AT_high_pc{{.*}}(0x000000000000000c)
+
 # VERIFY: No errors.

diff  --git a/llvm/tools/dsymutil/DwarfLinkerForBinary.h b/llvm/tools/dsymutil/DwarfLinkerForBinary.h
index 5b422e065a430..4ec281fca5dfe 100644
--- a/llvm/tools/dsymutil/DwarfLinkerForBinary.h
+++ b/llvm/tools/dsymutil/DwarfLinkerForBinary.h
@@ -175,9 +175,8 @@ class DwarfLinkerForBinary {
       } else {
         findValidRelocsInDebugSections(Obj, DMO);
       }
-      // A sizeless symbol has no known extent, so it can bound neither a range
-      // of its own nor a neighbour's. The ranges stand in for the high_pc that
-      // assembly files lack.
+      // Only a sized symbol has a known extent. The ranges stand in for the
+      // high_pc that assembly files lack.
       for (const auto &Entry : DMO.symbols()) {
         const auto &Mapping = Entry.getValue();
         if (!Mapping.Size)


        


More information about the llvm-commits mailing list