[llvm] Improve llvm-gsymutil to set the end address correctly. (PR #225593)

Greg Clayton via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 22 21:58:39 PDT 2026


https://github.com/clayborg created https://github.com/llvm/llvm-project/pull/225593

When converting DWARF in llvm-gsymutil, if the last function info had no size, it would set it to the end of the valid text ranges.  This meant a symbol could get a larger than necessary size in mach-o files. This patch will set the address to the minimum of the last TextRanges or the end of the section that contains the symbol.

>From 909077f69de1875a521a0fea82254b05555df2e4 Mon Sep 17 00:00:00 2001
From: Greg Clayton <clayborg at gmail.com>
Date: Tue, 22 Sep 2026 21:52:56 -0700
Subject: [PATCH] Improve llvm-gsymutil to set the end address correctly.

When converting DWARF in llvm-gsymutil, if the last function info had no size, it would set it to the end of the valid text ranges.  This meant a symbol could get a larger than necessary size in mach-o files. This patch will set the address to the minimum of the last TextRanges or the end of the section that contains the symbol.
---
 .../include/llvm/DebugInfo/GSYM/GsymCreator.h |  14 +-
 llvm/lib/DebugInfo/GSYM/GsymCreator.cpp       |  37 ++++-
 .../X86/macho-last-symbol-section-end.yaml    | 127 ++++++++++++++++++
 llvm/tools/llvm-gsymutil/llvm-gsymutil.cpp    |   6 +-
 4 files changed, 177 insertions(+), 7 deletions(-)
 create mode 100644 llvm/test/tools/llvm-gsymutil/X86/macho-last-symbol-section-end.yaml

diff --git a/llvm/include/llvm/DebugInfo/GSYM/GsymCreator.h b/llvm/include/llvm/DebugInfo/GSYM/GsymCreator.h
index 22d9096f1107ef..87df07b62c022d 100644
--- a/llvm/include/llvm/DebugInfo/GSYM/GsymCreator.h
+++ b/llvm/include/llvm/DebugInfo/GSYM/GsymCreator.h
@@ -27,6 +27,10 @@
 
 namespace llvm {
 
+namespace object {
+class ObjectFile;
+}
+
 namespace gsym {
 class FileWriter;
 class OutputAggregator;
@@ -415,9 +419,17 @@ class GsymCreator {
   ///
   /// \param  OS Output stream to report duplicate function infos, overlapping
   ///         function infos, and function infos that were merged or removed.
+  /// \param  Obj An optional object file that the function infos were created
+  ///         from. The last function info often has no size, and its size gets
+  ///         filled in from the valid text ranges. A valid text range can span
+  ///         more than one section, so the object file is used to find the
+  ///         section that contains the function and keep the size from
+  ///         extending past the end of that section. If no object file is
+  ///         supplied the size is filled in from the valid text ranges alone.
   /// \returns An error object that indicates success or failure of the
   ///          finalize.
-  LLVM_ABI llvm::Error finalize(OutputAggregator &OS);
+  LLVM_ABI llvm::Error finalize(OutputAggregator &OS,
+                                const object::ObjectFile *Obj = nullptr);
 
   /// Set the UUID value.
   ///
diff --git a/llvm/lib/DebugInfo/GSYM/GsymCreator.cpp b/llvm/lib/DebugInfo/GSYM/GsymCreator.cpp
index c7d1d0e91ac016..af0dbb3337098c 100644
--- a/llvm/lib/DebugInfo/GSYM/GsymCreator.cpp
+++ b/llvm/lib/DebugInfo/GSYM/GsymCreator.cpp
@@ -12,6 +12,7 @@
 #include "llvm/DebugInfo/GSYM/LineTable.h"
 #include "llvm/DebugInfo/GSYM/OutputAggregator.h"
 #include "llvm/MC/StringTableBuilder.h"
+#include "llvm/Object/ObjectFile.h"
 #include "llvm/Support/raw_ostream.h"
 
 #include <algorithm>
@@ -155,7 +156,25 @@ void GsymCreator::prepareMergedFunctions(OutputAggregator &Out) {
   std::swap(Funcs, TopLevelFuncs);
 }
 
-llvm::Error GsymCreator::finalize(OutputAggregator &Out) {
+/// Find the end address of the section that contains \a Addr.
+///
+/// \returns The address of the first byte past the end of the section that
+///          contains \a Addr, or std::nullopt if no section contains \a Addr.
+static std::optional<uint64_t>
+getSectionEndAddress(const object::ObjectFile &Obj, uint64_t Addr) {
+  for (const object::SectionRef &Sect : Obj.sections()) {
+    const uint64_t SectSize = Sect.getSize();
+    if (SectSize == 0)
+      continue;
+    const uint64_t SectAddr = Sect.getAddress();
+    if (Addr >= SectAddr && Addr < SectAddr + SectSize)
+      return SectAddr + SectSize;
+  }
+  return std::nullopt;
+}
+
+llvm::Error GsymCreator::finalize(OutputAggregator &Out,
+                                  const object::ObjectFile *Obj) {
   std::lock_guard<std::mutex> Guard(Mutex);
   if (Finalized)
     return createStringError(std::errc::invalid_argument, "already finalized");
@@ -265,9 +284,19 @@ llvm::Error GsymCreator::finalize(OutputAggregator &Out) {
     // help ensure we don't cause lookups to always return the last symbol that
     // has no size when doing lookups.
     if (!Funcs.empty() && Funcs.back().Range.size() == 0 && ValidTextRanges) {
-      if (auto Range =
-              ValidTextRanges->getRangeThatContains(Funcs.back().Range.start())) {
-        Funcs.back().Range = {Funcs.back().Range.start(), Range->end()};
+      const uint64_t StartAddr = Funcs.back().Range.start();
+      if (auto Range = ValidTextRanges->getRangeThatContains(StartAddr)) {
+        uint64_t EndAddr = Range->end();
+        // A valid text range can be made up of more than one section, so
+        // stopping at the end of the range can make the function extend past
+        // the end of the section that it actually lives in. Limit the size to
+        // the end of the containing section when we have an object file to
+        // look the section up in.
+        if (Obj) {
+          if (auto SectEndAddr = getSectionEndAddress(*Obj, StartAddr))
+            EndAddr = std::min(EndAddr, *SectEndAddr);
+        }
+        Funcs.back().Range = {StartAddr, EndAddr};
       }
     }
     Out << "Pruned " << NumBefore - Funcs.size() << " functions, ended with "
diff --git a/llvm/test/tools/llvm-gsymutil/X86/macho-last-symbol-section-end.yaml b/llvm/test/tools/llvm-gsymutil/X86/macho-last-symbol-section-end.yaml
new file mode 100644
index 00000000000000..2b9571aa1ba2e2
--- /dev/null
+++ b/llvm/test/tools/llvm-gsymutil/X86/macho-last-symbol-section-end.yaml
@@ -0,0 +1,127 @@
+## Test that the last function info, which has no size, does not get a size
+## that extends past the end of the section that contains it.
+##
+## Mach-O symbols have no size, so the size of the last function info gets
+## filled in during GsymCreator::finalize() using the valid text ranges. This
+## file has two adjacent code sections, __text [0x100000f90 - 0x100000fb0) and
+## __text_cold [0x100000fb0 - 0x100000fd0), which get combined into the single
+## valid text range [0x100000f90 - 0x100000fd0). The only symbol is "_main" at
+## the start of __text and __text_cold deliberately has no symbols in it, so
+## "main" is the last function info and it has no size. Its size must be
+## limited to the end of __text and must not cover any of __text_cold.
+
+# RUN: yaml2obj %s -o %t
+# RUN: llvm-gsymutil --convert %t -o %t.gsym 2>&1 | FileCheck %s --check-prefix=CONVERT
+# RUN: llvm-gsymutil --address=0x100000f90 --address=0x100000faf --address=0x100000fb0 --address=0x100000fcf %t.gsym 2>&1 | FileCheck %s --check-prefix=ADDR
+# RUN: llvm-gsymutil %t.gsym 2>&1 | FileCheck %s --check-prefix=DUMP
+
+# CONVERT: Input file: {{.*\.yaml\.tmp}}
+# CONVERT: Output file (x86_64): {{.*\.yaml\.tmp\.gsym}}
+# CONVERT: Loaded 1 functions from symbol table.
+# CONVERT: Pruned 0 functions, ended with 1 total
+
+## The last byte of __text resolves to "main", but the first byte of
+## __text_cold must not. If the size was taken from the end of the valid text
+## range instead of the end of the section, the last two lookups would both
+## incorrectly resolve to "main".
+# ADDR:      Looking up addresses in "{{.*\.yaml\.tmp\.gsym}}":
+# ADDR-NEXT: 0x0000000100000f90: main
+# ADDR-NEXT: 0x0000000100000faf: main + 31
+# ADDR-NEXT: 0x0000000100000fb0: error: address 0x100000fb0 is not in GSYM
+# ADDR-NEXT: 0x0000000100000fcf: error: address 0x100000fcf is not in GSYM
+
+## The range must end at 0x100000fb0, the end of __text, not at 0x100000fd0,
+## the end of the valid text range.
+# DUMP: FunctionInfo @ 0x{{[0-9a-f]+}}: [0x0000000100000f90 - 0x0000000100000fb0) "main"
+
+--- !mach-o
+FileHeader:
+  magic:           0xFEEDFACF
+  cputype:         0x1000007
+  cpusubtype:      0x3
+  filetype:        0x2
+  ncmds:           5
+  sizeofcmds:      424
+  flags:           0x200085
+  reserved:        0x0
+LoadCommands:
+  - cmd:             LC_SEGMENT_64
+    cmdsize:         72
+    segname:         __PAGEZERO
+    vmaddr:          0
+    vmsize:          4294967296
+    fileoff:         0
+    filesize:        0
+    maxprot:         0
+    initprot:        0
+    nsects:          0
+    flags:           0
+  - cmd:             LC_SEGMENT_64
+    cmdsize:         232
+    segname:         __TEXT
+    vmaddr:          4294967296
+    vmsize:          4096
+    fileoff:         0
+    filesize:        4096
+    maxprot:         5
+    initprot:        5
+    nsects:          2
+    flags:           0
+    Sections:
+      - sectname:        __text
+        segname:         __TEXT
+        addr:            0x100000F90
+        size:            32
+        offset:          0xF90
+        align:           4
+        reloff:          0x0
+        nreloc:          0
+        flags:           0x80000400
+        reserved1:       0x0
+        reserved2:       0x0
+        reserved3:       0x0
+        content:         'C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3C3'
+      - sectname:        __text_cold
+        segname:         __TEXT
+        addr:            0x100000FB0
+        size:            32
+        offset:          0xFB0
+        align:           4
+        reloff:          0x0
+        nreloc:          0
+        flags:           0x80000400
+        reserved1:       0x0
+        reserved2:       0x0
+        reserved3:       0x0
+        content:         '9090909090909090909090909090909090909090909090909090909090909090'
+  - cmd:             LC_SEGMENT_64
+    cmdsize:         72
+    segname:         __LINKEDIT
+    vmaddr:          4294971392
+    vmsize:          4096
+    fileoff:         4096
+    filesize:        24
+    maxprot:         1
+    initprot:        1
+    nsects:          0
+    flags:           0
+  - cmd:             LC_SYMTAB
+    cmdsize:         24
+    symoff:          4096
+    nsyms:           1
+    stroff:          4112
+    strsize:         8
+  - cmd:             LC_UUID
+    cmdsize:         24
+    uuid:            11111111-2222-3333-4444-555555555555
+LinkEditData:
+  NameList:
+    - n_strx:          2
+      n_type:          0xF
+      n_sect:          1
+      n_desc:          0
+      n_value:         4294971280
+  StringTable:
+    - ' '
+    - _main
+...
diff --git a/llvm/tools/llvm-gsymutil/llvm-gsymutil.cpp b/llvm/tools/llvm-gsymutil/llvm-gsymutil.cpp
index e168c8141def98..cb93054a89273d 100644
--- a/llvm/tools/llvm-gsymutil/llvm-gsymutil.cpp
+++ b/llvm/tools/llvm-gsymutil/llvm-gsymutil.cpp
@@ -545,8 +545,10 @@ static llvm::Error handleObjectFile(ObjectFile &Obj, ObjectFile *SymtabObj,
 
   // Finalize the GSYM to make it ready to save to disk. This will remove
   // duplicate FunctionInfo entries where we might have found an entry from
-  // debug info and also a symbol table entry from the object file.
-  if (auto Err = Gsym.finalize(Out))
+  // debug info and also a symbol table entry from the object file. Pass along
+  // the object file that the symbols came from so the size of a trailing
+  // symbol that has no size doesn't extend past the end of its section.
+  if (auto Err = Gsym.finalize(Out, SymtabObj ? SymtabObj : &Obj))
     return Err;
 
   // Save the GSYM file to disk.



More information about the llvm-commits mailing list