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

Jonas Devlieghere via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 13 10:51:28 PDT 2026


https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/215952

>From 49279cef339fb65799f9b3c842118f7a60b2b1ea Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Wed, 12 Aug 2026 22:06:40 -0700
Subject: [PATCH] [DWARFLinker] Constrain a function's high_pc to its own
 symbol

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
---
 llvm/include/llvm/DWARFLinker/AddressesMap.h  | 41 ++++++++--
 llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp  | 23 +++++-
 .../Parallel/DIEAttributeCloner.cpp           | 16 ++++
 .../Parallel/DependencyTracker.cpp            | 13 +++-
 .../subprogram-high-pc-past-symbol-dwarf2.s   | 72 ++++++++++++++++++
 .../Inputs/subprogram-high-pc-past-symbol.s   | 76 +++++++++++++++++++
 .../subprogram-high-pc-past-symbol.test       | 57 ++++++++++++++
 llvm/tools/dsymutil/DwarfLinkerForBinary.h    | 31 ++++++--
 8 files changed, 309 insertions(+), 20 deletions(-)
 create mode 100644 llvm/test/tools/dsymutil/Inputs/subprogram-high-pc-past-symbol-dwarf2.s
 create mode 100644 llvm/test/tools/dsymutil/Inputs/subprogram-high-pc-past-symbol.s
 create mode 100644 llvm/test/tools/dsymutil/subprogram-high-pc-past-symbol.test

diff --git a/llvm/include/llvm/DWARFLinker/AddressesMap.h b/llvm/include/llvm/DWARFLinker/AddressesMap.h
index 38b9a67b124c1..443e6f5bb4186 100644
--- a/llvm/include/llvm/DWARFLinker/AddressesMap.h
+++ b/llvm/include/llvm/DWARFLinker/AddressesMap.h
@@ -83,22 +83,47 @@ class AddressesMap {
   /// Erases all data.
   virtual void clear() = 0;
 
-  /// This is used for assembly files where labels may not have high_pc
-  /// but the debug map has range information from symbols.
-  struct AssemblyRange {
-    AssemblyRange(uint64_t LowPC, uint64_t HighPC)
+  /// The extent the linker gave a symbol, in source address space.
+  struct SymbolRange {
+    SymbolRange(uint64_t LowPC, uint64_t HighPC)
         : LowPC(LowPC), HighPC(HighPC) {}
     uint64_t LowPC;
     uint64_t HighPC;
   };
 
-  /// Returns the address range containing \p Addr if available.
-  /// \returns the range [LowPC, HighPC) containing Addr.
-  virtual std::optional<AssemblyRange>
-  getAssemblyRangeForAddress(uint64_t Addr) {
+  /// Returns the symbol range [LowPC, HighPC) containing \p Addr, if known.
+  virtual std::optional<SymbolRange> getSymbolRangeForAddress(uint64_t Addr) {
     return std::nullopt;
   }
 
+  /// Returns the linked address of the first symbol placed at or after
+  /// \p LinkedAddr, if one is known.
+  virtual std::optional<uint64_t>
+  getNextLinkedSymbolStart(uint64_t LinkedAddr) {
+    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.
+  ///
+  /// Neighbouring symbols shift by different 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.
+  uint64_t constrainCodeRangeHighPC(uint64_t LowPC, uint64_t HighPC,
+                                    int64_t Adjustment) {
+    std::optional<SymbolRange> Symbol = getSymbolRangeForAddress(LowPC);
+    if (!Symbol)
+      return HighPC;
+    std::optional<uint64_t> NextStart =
+        getNextLinkedSymbolStart(Symbol->HighPC + Adjustment);
+    if (!NextStart)
+      return HighPC;
+    return std::min(HighPC, *NextStart - Adjustment);
+  }
+
   /// This function checks whether variable has DWARF expression containing
   /// operation referencing live address(f.e. DW_OP_addr, DW_OP_addrx...).
   /// \returns first is true if the expression has an operation referencing an
diff --git a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
index 24289e0906586..5c8f718c8670f 100644
--- a/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
+++ b/llvm/lib/DWARFLinker/Classic/DWARFLinker.cpp
@@ -672,7 +672,7 @@ unsigned DWARFLinker::shouldKeepSubprogramDIE(
     // function ranges when available, falling back to labels otherwise.
     if (Unit.getLanguage() == dwarf::DW_LANG_Mips_Assembler ||
         Unit.getLanguage() == dwarf::DW_LANG_Assembly) {
-      if (auto Range = RelocMgr.getAssemblyRangeForAddress(*LowPc)) {
+      if (auto Range = RelocMgr.getSymbolRangeForAddress(*LowPc)) {
         Unit.addFunctionRange(Range->LowPC, Range->HighPC, MyInfo.AddrAdjust);
       } else {
         Unit.addLabelLowPc(*LowPc, MyInfo.AddrAdjust);
@@ -698,7 +698,10 @@ unsigned DWARFLinker::shouldKeepSubprogramDIE(
   }
 
   // Replace the debug map range with a more accurate one.
-  Unit.addFunctionRange(*LowPc, *HighPc, MyInfo.AddrAdjust);
+  Unit.addFunctionRange(
+      *LowPc,
+      RelocMgr.constrainCodeRangeHighPC(*LowPc, *HighPc, MyInfo.AddrAdjust),
+      MyInfo.AddrAdjust);
   return Flags;
 }
 
@@ -1471,6 +1474,14 @@ 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);
+    }
     *Addr += Info.PCOffset;
   }
 
@@ -1629,6 +1640,14 @@ 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;
+  }
+
   DIE::value_iterator Patch =
       Die.addValue(DIEAlloc, dwarf::Attribute(AttrSpec.Attr),
                    dwarf::Form(AttrSpec.Form), DIEInteger(Value));
diff --git a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
index c58d2937f3ed0..93f60a1d030d5 100644
--- a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
@@ -525,6 +525,16 @@ 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;
+  }
+
   auto Result =
       Generator.addScalarAttribute(AttrSpec.Attr, ResultingForm, Value);
   // Record DW_AT_LLVM_stmt_sequence so the attribute value can be
@@ -656,6 +666,12 @@ 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 (VarAddressAdjustment)
       *Addr += *VarAddressAdjustment;
     else if (FuncAddressAdjustment)
diff --git a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
index 124e73b1a15bd..0993497ff080b 100644
--- a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
@@ -952,14 +952,15 @@ bool DependencyTracker::isLiveSubprogramEntry(const UnitEntryPairTy &Entry) {
 
       // For assembly-language CUs there are typically no DW_TAG_subprogram
       // DIEs, so labels are the only addresses we see. Fall back to the
-      // assembly-range lookup to recover a function range for the line-table
+      // symbol-range lookup to recover a function range for the line-table
       // filter; otherwise the output line table would be empty.
       uint16_t Language = dwarf::toUnsigned(
           Entry.CU->getOrigUnit().getUnitDIE().find(dwarf::DW_AT_language), 0);
       if (Language == dwarf::DW_LANG_Mips_Assembler ||
           Language == dwarf::DW_LANG_Assembly) {
-        if (auto Range = Entry.CU->getContaingFile()
-                             .Addresses->getAssemblyRangeForAddress(*LowPc))
+        if (auto Range =
+                Entry.CU->getContaingFile().Addresses->getSymbolRangeForAddress(
+                    *LowPc))
           Entry.CU->addFunctionRange(Range->LowPC, Range->HighPC,
                                      *RelocAdjustment);
       }
@@ -974,6 +975,10 @@ bool DependencyTracker::isLiveSubprogramEntry(const UnitEntryPairTy &Entry) {
   if (!Info.getTrackLiveness() || DIE.getTag() == dwarf::DW_TAG_label)
     return true;
 
-  Entry.CU->addFunctionRange(*LowPc, *HighPc, *RelocAdjustment);
+  Entry.CU->addFunctionRange(
+      *LowPc,
+      Entry.CU->getContaingFile().Addresses->constrainCodeRangeHighPC(
+          *LowPc, *HighPc, *RelocAdjustment),
+      *RelocAdjustment);
   return true;
 }
diff --git a/llvm/test/tools/dsymutil/Inputs/subprogram-high-pc-past-symbol-dwarf2.s b/llvm/test/tools/dsymutil/Inputs/subprogram-high-pc-past-symbol-dwarf2.s
new file mode 100644
index 0000000000000..49933495d7dc6
--- /dev/null
+++ b/llvm/test/tools/dsymutil/Inputs/subprogram-high-pc-past-symbol-dwarf2.s
@@ -0,0 +1,72 @@
+; The DWARF 2 form of subprogram-high-pc-past-symbol.s, where DW_AT_high_pc is
+; an address rather than a length.
+
+	.text
+	.globl	_a
+	.p2align 2
+_a:
+	ret
+_filler:
+	nop
+	.globl	_b
+	.p2align 2
+_b:
+	ret
+
+	.section __DWARF,__debug_abbrev,regular,debug
+	.byte	1                       ; abbrev 1: DW_TAG_compile_unit
+	.byte	0x11
+	.byte	1                       ; DW_CHILDREN_yes
+	.byte	0x25, 0x08              ; DW_AT_producer,  DW_FORM_string
+	.byte	0x13, 0x0b              ; DW_AT_language,  DW_FORM_data1
+	.byte	0x03, 0x08              ; DW_AT_name,      DW_FORM_string
+	.byte	0x11, 0x01              ; DW_AT_low_pc,    DW_FORM_addr
+	.byte	0x12, 0x01              ; DW_AT_high_pc,   DW_FORM_addr
+	.byte	0, 0
+	.byte	2                       ; abbrev 2: DW_TAG_subprogram
+	.byte	0x2e
+	.byte	1                       ; DW_CHILDREN_yes
+	.byte	0x03, 0x08              ; DW_AT_name,      DW_FORM_string
+	.byte	0x11, 0x01              ; DW_AT_low_pc,    DW_FORM_addr
+	.byte	0x12, 0x01              ; DW_AT_high_pc,   DW_FORM_addr
+	.byte	0x3f, 0x0c              ; DW_AT_external,  DW_FORM_flag
+	.byte	0, 0
+	.byte	3                       ; abbrev 3: DW_TAG_lexical_block
+	.byte	0x0b
+	.byte	0                       ; DW_CHILDREN_no
+	.byte	0x11, 0x01              ; DW_AT_low_pc,    DW_FORM_addr
+	.byte	0x12, 0x01              ; DW_AT_high_pc,   DW_FORM_addr
+	.byte	0, 0
+	.byte	0
+
+	.section __DWARF,__debug_info,regular,debug
+Lcu_begin:
+	.long	Lcu_end-Lcu_version
+Lcu_version:
+	.short	2
+	.long	0
+	.byte	8
+	.byte	1                       ; DW_TAG_compile_unit
+	.asciz	"hand-written"
+	.byte	0x0c                    ; DW_LANG_C99
+	.asciz	"t.c"
+	.quad	_a
+	.quad	0xc                     ; _a, _filler and _b together
+	.byte	2                       ; DW_TAG_subprogram "a"
+	.asciz	"a"
+	.quad	_a
+	.quad	0x8                     ; four bytes past the end of _a
+	.byte	1
+	.byte	3                       ; DW_TAG_lexical_block in "a"
+	.quad	_a
+	.quad	0x8                     ; reaches as far as its parent
+	.byte	0                       ; end of "a"'s children
+	.byte	2                       ; DW_TAG_subprogram "b"
+	.asciz	"b"
+	.quad	_b
+	.quad	0xc
+	.byte	1
+	.byte	0                       ; end of "b"'s children
+	.byte	0                       ; end of the compile unit's children
+Lcu_end:
+	.subsections_via_symbols
diff --git a/llvm/test/tools/dsymutil/Inputs/subprogram-high-pc-past-symbol.s b/llvm/test/tools/dsymutil/Inputs/subprogram-high-pc-past-symbol.s
new file mode 100644
index 0000000000000..f7d02714a2940
--- /dev/null
+++ b/llvm/test/tools/dsymutil/Inputs/subprogram-high-pc-past-symbol.s
@@ -0,0 +1,76 @@
+; _filler is an unreferenced atom between the two functions, so the linker drops
+; it and places _b four bytes after _a. DW_AT_high_pc for _a reaches all the way
+; to _b, past the code the linker keeps for it, and the lexical block inside _a
+; ends with its parent.
+;
+; The DWARF is hand written because a producer normally agrees with the linker.
+
+	.text
+	.globl	_a
+	.p2align 2
+_a:
+	ret
+_filler:
+	nop
+	.globl	_b
+	.p2align 2
+_b:
+	ret
+
+	.section __DWARF,__debug_abbrev,regular,debug
+	.byte	1                       ; abbrev 1: DW_TAG_compile_unit
+	.byte	0x11
+	.byte	1                       ; DW_CHILDREN_yes
+	.byte	0x25, 0x08              ; DW_AT_producer,  DW_FORM_string
+	.byte	0x13, 0x0b              ; DW_AT_language,  DW_FORM_data1
+	.byte	0x03, 0x08              ; DW_AT_name,      DW_FORM_string
+	.byte	0x11, 0x01              ; DW_AT_low_pc,    DW_FORM_addr
+	.byte	0x12, 0x06              ; DW_AT_high_pc,   DW_FORM_data4
+	.byte	0, 0
+	.byte	2                       ; abbrev 2: DW_TAG_subprogram
+	.byte	0x2e
+	.byte	1                       ; DW_CHILDREN_yes
+	.byte	0x03, 0x08              ; DW_AT_name,      DW_FORM_string
+	.byte	0x11, 0x01              ; DW_AT_low_pc,    DW_FORM_addr
+	.byte	0x12, 0x06              ; DW_AT_high_pc,   DW_FORM_data4
+	.byte	0x3f, 0x0c              ; DW_AT_external,  DW_FORM_flag
+	.byte	0, 0
+	.byte	3                       ; abbrev 3: DW_TAG_lexical_block
+	.byte	0x0b
+	.byte	0                       ; DW_CHILDREN_no
+	.byte	0x11, 0x01              ; DW_AT_low_pc,    DW_FORM_addr
+	.byte	0x12, 0x06              ; DW_AT_high_pc,   DW_FORM_data4
+	.byte	0, 0
+	.byte	0
+
+	.section __DWARF,__debug_info,regular,debug
+Lcu_begin:
+	.long	Lcu_end-Lcu_version
+Lcu_version:
+	.short	4
+	.long	0
+	.byte	8
+	.byte	1                       ; DW_TAG_compile_unit
+	.asciz	"hand-written"
+	.byte	0x0c                    ; DW_LANG_C99
+	.asciz	"t.c"
+	.quad	_a
+	.long	0xc                     ; _a, _filler and _b together
+	.byte	2                       ; DW_TAG_subprogram "a"
+	.asciz	"a"
+	.quad	_a
+	.long	0x8                     ; four bytes past the end of _a
+	.byte	1
+	.byte	3                       ; DW_TAG_lexical_block in "a"
+	.quad	_a
+	.long	0x8                     ; reaches as far as its parent
+	.byte	0                       ; end of "a"'s children
+	.byte	2                       ; DW_TAG_subprogram "b"
+	.asciz	"b"
+	.quad	_b
+	.long	0x4
+	.byte	1
+	.byte	0                       ; end of "b"'s children
+	.byte	0                       ; end of the compile unit's children
+Lcu_end:
+	.subsections_via_symbols
diff --git a/llvm/test/tools/dsymutil/subprogram-high-pc-past-symbol.test b/llvm/test/tools/dsymutil/subprogram-high-pc-past-symbol.test
new file mode 100644
index 0000000000000..c58f75b0f9159
--- /dev/null
+++ b/llvm/test/tools/dsymutil/subprogram-high-pc-past-symbol.test
@@ -0,0 +1,57 @@
+# REQUIRES: aarch64-registered-target
+
+# 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.
+
+# RUN: llvm-mc -triple arm64-apple-darwin -filetype=obj \
+# RUN:   %p/Inputs/subprogram-high-pc-past-symbol.s -o %t.o
+# RUN: echo '---' > %t.map
+# RUN: echo "triple: 'arm64-apple-darwin'" >> %t.map
+# RUN: echo 'objects:' >> %t.map
+# RUN: echo " - filename: '%/t.o'" >> %t.map
+# RUN: echo '   symbols:' >> %t.map
+# 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.
+
+# RUN: echo '     - { sym: _inside, binAddr: 0x1002, size: 0x2 }' >> %t.map
+# RUN: echo '...' >> %t.map
+
+# RUN: dsymutil --linker classic -y %t.map -f -o %t-classic.out
+# RUN: llvm-dwarfdump -a %t-classic.out | FileCheck %s
+# RUN: llvm-dwarfdump --verify %t-classic.out | FileCheck %s --check-prefix=VERIFY
+
+# RUN: dsymutil --linker parallel -y %t.map -f -o %t-parallel.out
+# RUN: llvm-dwarfdump -a %t-parallel.out | FileCheck %s
+# RUN: llvm-dwarfdump --verify %t-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 \
+# RUN:   %p/Inputs/subprogram-high-pc-past-symbol-dwarf2.s -o %t.o
+# RUN: dsymutil --linker classic -y %t.map -f -o %t-classic2.out
+# RUN: llvm-dwarfdump -a %t-classic2.out | FileCheck %s
+# RUN: llvm-dwarfdump --verify %t-classic2.out | FileCheck %s --check-prefix=VERIFY
+
+# RUN: dsymutil --linker parallel -y %t.map -f -o %t-parallel2.out
+# RUN: llvm-dwarfdump -a %t-parallel2.out | FileCheck %s
+# RUN: llvm-dwarfdump --verify %t-parallel2.out | FileCheck %s --check-prefix=VERIFY
+
+# CHECK:      DW_TAG_subprogram
+# CHECK:        DW_AT_name{{.*}}"a"
+# CHECK-NEXT:   DW_AT_low_pc{{.*}}(0x0000000000001000)
+# CHECK-NEXT:   DW_AT_high_pc{{.*}}(0x0000000000001004)
+# CHECK:        DW_TAG_lexical_block
+# CHECK-NEXT:     DW_AT_low_pc{{.*}}(0x0000000000001000)
+# CHECK-NEXT:     DW_AT_high_pc{{.*}}(0x0000000000001004)
+# CHECK:      DW_TAG_subprogram
+# CHECK:        DW_AT_name{{.*}}"b"
+# CHECK-NEXT:   DW_AT_low_pc{{.*}}(0x0000000000001004)
+# CHECK-NEXT:   DW_AT_high_pc{{.*}}(0x0000000000001008)
+
+# VERIFY: No errors.
diff --git a/llvm/tools/dsymutil/DwarfLinkerForBinary.h b/llvm/tools/dsymutil/DwarfLinkerForBinary.h
index 446860abe150c..5b422e065a430 100644
--- a/llvm/tools/dsymutil/DwarfLinkerForBinary.h
+++ b/llvm/tools/dsymutil/DwarfLinkerForBinary.h
@@ -128,6 +128,9 @@ class DwarfLinkerForBinary {
     /// Address ranges for symbols with sizes (used for assembly file support).
     RangesTy AddressRanges;
 
+    /// Sorted linked start addresses of the symbols with a known size.
+    std::vector<uint64_t> LinkedSymbolStarts;
+
     /// Returns list of valid relocations from \p Relocs,
     /// between \p StartOffset and \p NextOffset.
     ///
@@ -172,15 +175,22 @@ class DwarfLinkerForBinary {
       } else {
         findValidRelocsInDebugSections(Obj, DMO);
       }
-      // Populate address ranges from debug map symbols that have sizes.
-      // This is used for assembly files where labels may not have high_pc.
+      // 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.
       for (const auto &Entry : DMO.symbols()) {
         const auto &Mapping = Entry.getValue();
-        if (Mapping.Size && Mapping.ObjectAddress)
+        if (!Mapping.Size)
+          continue;
+        LinkedSymbolStarts.push_back(Mapping.BinaryAddress);
+        if (Mapping.ObjectAddress)
           AddressRanges.insert(
               {*Mapping.ObjectAddress, *Mapping.ObjectAddress + Mapping.Size},
               int64_t(Mapping.BinaryAddress) - *Mapping.ObjectAddress);
       }
+      llvm::sort(LinkedSymbolStarts);
+      LinkedSymbolStarts.erase(llvm::unique(LinkedSymbolStarts),
+                               LinkedSymbolStarts.end());
     }
     ~AddressManager() override { clear(); }
 
@@ -241,14 +251,23 @@ class DwarfLinkerForBinary {
       ValidDebugInfoRelocs.clear();
       ValidDebugAddrRelocs.clear();
       AddressRanges.clear();
+      LinkedSymbolStarts.clear();
     }
 
-    std::optional<AssemblyRange>
-    getAssemblyRangeForAddress(uint64_t Addr) override {
+    std::optional<SymbolRange>
+    getSymbolRangeForAddress(uint64_t Addr) override {
       if (auto Range = AddressRanges.getRangeThatContains(Addr))
-        return AssemblyRange(Range->Range.start(), Range->Range.end());
+        return SymbolRange(Range->Range.start(), Range->Range.end());
       return std::nullopt;
     }
+
+    std::optional<uint64_t>
+    getNextLinkedSymbolStart(uint64_t LinkedAddr) override {
+      auto It = llvm::lower_bound(LinkedSymbolStarts, LinkedAddr);
+      if (It == LinkedSymbolStarts.end())
+        return std::nullopt;
+      return *It;
+    }
   };
 
 private:



More information about the llvm-commits mailing list