[llvm] [llvm-profgen] Handle perf mmaps with page sizes larger than 4 KiB (PR #217887)

Jinjie Huang via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 21 05:24:30 PDT 2026


https://github.com/Jinjie-Huang updated https://github.com/llvm/llvm-project/pull/217887

>From 06e49cb54597f98b00f8546d96e62fa81094cb4b Mon Sep 17 00:00:00 2001
From: huangjinjie <huangjinjie at bytedance.com>
Date: Fri, 21 Aug 2026 20:24:13 +0800
Subject: [PATCH] fix llvm-profgen 64KB pagesize issue

---
 .../AArch64/load-segment-64k-align.test       | 65 +++++++++++++++++++
 llvm/tools/llvm-profgen/PerfReader.cpp        | 25 +++++--
 llvm/tools/llvm-profgen/ProfiledBinary.cpp    |  9 +--
 3 files changed, 89 insertions(+), 10 deletions(-)
 create mode 100644 llvm/test/tools/llvm-profgen/AArch64/load-segment-64k-align.test

diff --git a/llvm/test/tools/llvm-profgen/AArch64/load-segment-64k-align.test b/llvm/test/tools/llvm-profgen/AArch64/load-segment-64k-align.test
new file mode 100644
index 0000000000000..894bd4e27fad1
--- /dev/null
+++ b/llvm/test/tools/llvm-profgen/AArch64/load-segment-64k-align.test
@@ -0,0 +1,65 @@
+# REQUIRES: aarch64-registered-target
+
+# RUN: split-file %s %t
+# RUN: yaml2obj %t/binary.yaml -o %t/load-segment-64k-align.exe
+# RUN: llvm-profgen --binary=%t/load-segment-64k-align.exe \
+# RUN:   --perfscript=%t/perfscript --skip-symbolization --format=text \
+# RUN:   --use-offset=0 --output=%t/profile
+# RUN: FileCheck %s --input-file=%t/profile
+
+## Test a runtime mmap using 0x10000 page alignment instead of the 0x1000
+## baseline used by setPreferredTextSegmentAddresses(). The text segment offset
+## is recorded as 0x13000, while perf reports a 0x10000-aligned mmap starting
+## at file offset 0x10000. Verify that llvm-profgen finds the text segment at
+## offset 0x3000 within that mmap and derives its correct runtime address.
+# CHECK:      0
+# CHECK-NEXT: 1
+# CHECK-NEXT: 143de4->143de0:1
+
+#--- binary.yaml
+--- !ELF
+FileHeader:
+  Class:   ELFCLASS64
+  Data:    ELFDATA2LSB
+  Type:    ET_DYN
+  Machine: EM_AARCH64
+  Entry:   0x0000000000143de0
+Sections:
+  - Name:         .text
+    Type:         SHT_PROGBITS
+    Flags:        [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:      0x0000000000143de0
+    Offset:       0x0000000000013de0
+    AddressAlign: 0x4
+    ## 143de0: nop
+    ## 143de4: ret
+    Content:      1F2003D5C0035FD6
+ProgramHeaders:
+  - Type:     PT_LOAD
+    Flags:    [ PF_R ]
+    Offset:   0x0
+    VAddr:    0x0
+    FileSize: 0x13dd8
+    MemSize:  0x13dd8
+    Align:    0x10000
+  - Type:     PT_LOAD
+    Flags:    [ PF_X, PF_R ]
+    Offset:   0x13de0
+    VAddr:    0x143de0
+    FileSize: 0x8
+    MemSize:  0x8
+    Align:    0x10000
+    FirstSec: .text
+    LastSec:  .text
+Symbols:
+  - Name:    foo
+    Type:    STT_FUNC
+    Section: .text
+    Binding: STB_GLOBAL
+    Value:   0x143de0
+    Size:    0x8
+
+#--- perfscript
+PERF_RECORD_MMAP2 1/1: [0x700000000000(0x10000) @ 0x10000 00:00 0 0]: r-xp /tmp/load-segment-64k-align.exe
+700000003de0 0x700000003de4/0x700000003de0/P/-/-/0
+
diff --git a/llvm/tools/llvm-profgen/PerfReader.cpp b/llvm/tools/llvm-profgen/PerfReader.cpp
index c4fc4fb25166f..caaa75cdb07f9 100644
--- a/llvm/tools/llvm-profgen/PerfReader.cpp
+++ b/llvm/tools/llvm-profgen/PerfReader.cpp
@@ -611,14 +611,27 @@ void PerfScriptReader::updateBinaryAddress(const MMapEvent &Event) {
   } else {
     // Verify segments are loaded consecutively.
     const auto &Offsets = Binary->getTextSegmentOffsets();
+    auto MMapContainsFileOffset = [&](uint64_t FileOffset) {
+      return Event.Offset <= FileOffset &&
+             FileOffset - Event.Offset < Event.Size;
+    };
     auto It = llvm::lower_bound(Offsets, Event.Offset);
-    if (It != Offsets.end() && *It == Event.Offset) {
-      // The event is for loading a separate executable segment.
+    if (It != Offsets.end() && MMapContainsFileOffset(*It)) {
+      // setPreferredTextSegmentAddresses() records text segment offsets using a
+      // 4 KiB baseline. On systems with larger pages (e.g. 64 KiB AArch64), a
+      // perf mmap may start before and contain such an offset. Translate the
+      // segment start to its runtime address using its offset within the mmap.
       auto I = std::distance(Offsets.begin(), It);
-      const auto &PreferredAddrs = Binary->getPreferredTextSegmentAddresses();
-      if (PreferredAddrs[I] - Binary->getPreferredBaseAddress() !=
-          Event.Address - Binary->getBaseAddress())
-        exitWithError("Executable segments not loaded consecutively");
+      uint64_t SegmentLoadAddress = Event.Address + (*It - Event.Offset);
+      if (It == Offsets.begin()) {
+        Binary->setBaseAddress(SegmentLoadAddress);
+        Binary->setIsLoadedByMMap(true);
+      } else {
+        const auto &PreferredAddrs = Binary->getPreferredTextSegmentAddresses();
+        if (PreferredAddrs[I] - Binary->getPreferredBaseAddress() !=
+            SegmentLoadAddress - Binary->getBaseAddress())
+          exitWithError("Executable segments not loaded consecutively");
+      }
     } else {
       if (It == Offsets.begin())
         exitWithError("File offset not found");
diff --git a/llvm/tools/llvm-profgen/ProfiledBinary.cpp b/llvm/tools/llvm-profgen/ProfiledBinary.cpp
index 2d147d0af7353..8750d3c1cdcc0 100644
--- a/llvm/tools/llvm-profgen/ProfiledBinary.cpp
+++ b/llvm/tools/llvm-profgen/ProfiledBinary.cpp
@@ -362,10 +362,11 @@ template <class ELFT>
 void ProfiledBinary::setPreferredTextSegmentAddresses(const ELFFile<ELFT> &Obj,
                                                       StringRef FileName) {
   const auto &PhdrRange = unwrapOrError(Obj.program_headers(), FileName);
-  // FIXME: This should be the page size of the system running profiling.
-  // However such info isn't available at post-processing time, assuming
-  // 4K page now. Note that we don't use EXEC_PAGESIZE from <linux/param.h>
-  // because we may build the tools on non-linux.
+  // The page size of the profiling system cannot be determined from the ELF
+  // binary alone, and using the page size of the post-processing system would
+  // be incorrect. Use 4 KiB as the binary-side baseline. PerfScriptReader
+  // reconciles executable mmap events whose mappings cover the segment start
+  // due to a larger runtime page size.
   uint64_t PageSize = 0x1000;
   bool SeenFirstLoadableSegment = false;
   for (const typename ELFT::Phdr &Phdr : PhdrRange) {



More information about the llvm-commits mailing list