[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
Tue Aug 25 20:35:55 PDT 2026


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

>From ba50f905e90b0b8b3d1319d6134484cb4f53bcc9 Mon Sep 17 00:00:00 2001
From: huangjinjie <huangjinjie at bytedance.com>
Date: Sat, 22 Aug 2026 03:27:24 +0800
Subject: [PATCH 1/3] fix llvm-profgen 64KB pagesize issue

---
 .../AArch64/load-segment-64k-align.test       | 65 +++++++++++++++++++
 llvm/tools/llvm-profgen/PerfReader.cpp        | 27 ++++++--
 llvm/tools/llvm-profgen/ProfiledBinary.cpp    |  9 +--
 3 files changed, 91 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..d4a8e37544309 100644
--- a/llvm/tools/llvm-profgen/PerfReader.cpp
+++ b/llvm/tools/llvm-profgen/PerfReader.cpp
@@ -611,14 +611,29 @@ 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() rounds text segment offsets down to
+      // 4 KiB boundaries. On systems with larger OS page sizes (e.g., 64 KiB on
+      // AArch64), the kernel rounds down the mmap offset to the page boundary.
+      // Thus, the mmap region will start before and fully encompass the
+      // expected 4 KiB-aligned 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..b84d3f809cd14 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 when rounding down PT_LOAD virtual addresses and
+  // file offsets. PerfScriptReader accounts for mmap events aligned to larger
+  // runtime pages.
   uint64_t PageSize = 0x1000;
   bool SeenFirstLoadableSegment = false;
   for (const typename ELFT::Phdr &Phdr : PhdrRange) {

>From 7fff30a32745cde9fb01ed92478493efe7f197f2 Mon Sep 17 00:00:00 2001
From: huangjinjie <huangjinjie at bytedance.com>
Date: Wed, 26 Aug 2026 03:27:21 +0800
Subject: [PATCH 2/3] Initialize PreferredAddresses using derived values and
 calculate RuntimeBaseAddress

---
 .../AArch64/load-segment-64k-align.test       | 15 ++++--
 llvm/tools/llvm-profgen/PerfReader.cpp        | 52 ++++++++++---------
 llvm/tools/llvm-profgen/ProfiledBinary.cpp    | 14 ++---
 llvm/tools/llvm-profgen/ProfiledBinary.h      | 14 ++---
 4 files changed, 48 insertions(+), 47 deletions(-)

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
index 894bd4e27fad1..f126ca0ce19ba 100644
--- a/llvm/test/tools/llvm-profgen/AArch64/load-segment-64k-align.test
+++ b/llvm/test/tools/llvm-profgen/AArch64/load-segment-64k-align.test
@@ -6,15 +6,20 @@
 # RUN:   --perfscript=%t/perfscript --skip-symbolization --format=text \
 # RUN:   --use-offset=0 --output=%t/profile
 # RUN: FileCheck %s --input-file=%t/profile
+# RUN: llvm-profgen --binary=%t/load-segment-64k-align.exe \
+# RUN:   --perfscript=%t/perfscript --skip-symbolization --format=text \
+# RUN:   --use-offset=1 --output=%t/offset-profile
+# RUN: FileCheck %s --check-prefix=OFFSET --input-file=%t/offset-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.
+## Test a 0x10000-aligned runtime mmap containing a PT_LOAD that begins at file
+## offset 0x13de0. Verify that llvm-profgen derives the runtime base address from
+## the mmap address and offset without assuming a fixed page size.
 # CHECK:      0
 # CHECK-NEXT: 1
 # CHECK-NEXT: 143de4->143de0:1
+# OFFSET:      0
+# OFFSET-NEXT: 1
+# OFFSET-NEXT: 13de4->13de0:1
 
 #--- binary.yaml
 --- !ELF
diff --git a/llvm/tools/llvm-profgen/PerfReader.cpp b/llvm/tools/llvm-profgen/PerfReader.cpp
index d4a8e37544309..6aad37241f103 100644
--- a/llvm/tools/llvm-profgen/PerfReader.cpp
+++ b/llvm/tools/llvm-profgen/PerfReader.cpp
@@ -594,46 +594,45 @@ void PerfScriptReader::updateBinaryAddress(const MMapEvent &Event) {
   if (PIDFilter && Event.PID != *PIDFilter)
     return;
 
-  // Drop the event if its image is loaded at the same address
-  if (Event.Address == Binary->getBaseAddress()) {
+  auto MMapContainsFileOffset = [&](uint64_t FileOffset) {
+    return Event.Offset == FileOffset ||
+           (Event.MemProtectionFlag.contains("x") &&
+            Event.Offset < FileOffset &&
+            FileOffset - Event.Offset < Event.Size);
+  };
+  const bool MMapContainsTextSegment =
+      MMapContainsFileOffset(Binary->getTextSegmentOffset());
+  const uint64_t RuntimeBaseAddress = IsKernel || Binary->isCOFF()
+                                          ? Event.Address
+                                          : Event.Address - Event.Offset;
+
+  // Drop the event if its image has the same base address.
+  if ((IsKernel || MMapContainsTextSegment) &&
+      RuntimeBaseAddress == Binary->getBaseAddress()) {
     Binary->setIsLoadedByMMap(true);
     return;
   }
 
-  if (IsKernel || Event.Offset == Binary->getTextSegmentOffset()) {
+  if (IsKernel || MMapContainsTextSegment) {
     // A binary image could be unloaded and then reloaded at different
     // place, so update binary load address.
     // Only update for the first executable segment and assume all other
     // segments are loaded at consecutive memory addresses, which is the case on
     // X64.
-    Binary->setBaseAddress(Event.Address);
+    Binary->setBaseAddress(RuntimeBaseAddress);
     Binary->setIsLoadedByMMap(true);
   } 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() && MMapContainsFileOffset(*It)) {
-      // setPreferredTextSegmentAddresses() rounds text segment offsets down to
-      // 4 KiB boundaries. On systems with larger OS page sizes (e.g., 64 KiB on
-      // AArch64), the kernel rounds down the mmap offset to the page boundary.
-      // Thus, the mmap region will start before and fully encompass the
-      // expected 4 KiB-aligned offset. Translate the segment start to its
-      // runtime address using its offset within the mmap.
+      // The event is for loading a separate executable segment.
       auto I = std::distance(Offsets.begin(), It);
-      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");
-      }
+      const auto &PreferredAddrs = Binary->getPreferredTextSegmentAddresses();
+      uint64_t RuntimeSegmentAddress = Event.Address + (*It - Event.Offset);
+      if (PreferredAddrs[I] !=
+          Binary->canonicalizeVirtualAddress(RuntimeSegmentAddress))
+        exitWithError("Executable segments not loaded consecutively");
     } else {
       if (It == Offsets.begin())
         exitWithError("File offset not found");
@@ -642,7 +641,10 @@ void PerfScriptReader::updateBinaryAddress(const MMapEvent &Event) {
         // via multiple mmap calls with consecutive memory addresses.
         --It;
         assert(*It < Event.Offset);
-        if (Event.Offset - *It != Event.Address - Binary->getBaseAddress())
+        auto I = std::distance(Offsets.begin(), It);
+        const auto &PreferredAddrs = Binary->getPreferredTextSegmentAddresses();
+        if (PreferredAddrs[I] + (Event.Offset - *It) !=
+            Binary->canonicalizeVirtualAddress(Event.Address))
           exitWithError("Segment not loaded by consecutive mmaps");
       }
     }
diff --git a/llvm/tools/llvm-profgen/ProfiledBinary.cpp b/llvm/tools/llvm-profgen/ProfiledBinary.cpp
index b84d3f809cd14..2afc478e0543c 100644
--- a/llvm/tools/llvm-profgen/ProfiledBinary.cpp
+++ b/llvm/tools/llvm-profgen/ProfiledBinary.cpp
@@ -362,26 +362,18 @@ template <class ELFT>
 void ProfiledBinary::setPreferredTextSegmentAddresses(const ELFFile<ELFT> &Obj,
                                                       StringRef FileName) {
   const auto &PhdrRange = unwrapOrError(Obj.program_headers(), FileName);
-  // 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 when rounding down PT_LOAD virtual addresses and
-  // file offsets. PerfScriptReader accounts for mmap events aligned to larger
-  // runtime pages.
-  uint64_t PageSize = 0x1000;
   bool SeenFirstLoadableSegment = false;
   for (const typename ELFT::Phdr &Phdr : PhdrRange) {
     if (Phdr.p_type == ELF::PT_INTERP)
       HasInterp = true;
     if (Phdr.p_type == ELF::PT_LOAD) {
       if (!SeenFirstLoadableSegment) {
-        FirstLoadableAddress = Phdr.p_vaddr & ~(PageSize - 1U);
+        FirstLoadableAddress = Phdr.p_vaddr - Phdr.p_offset;
         SeenFirstLoadableSegment = true;
       }
       if (Phdr.p_flags & ELF::PF_X) {
-        // Segments will always be loaded at a page boundary.
-        PreferredTextSegmentAddresses.push_back(Phdr.p_vaddr &
-                                                ~(PageSize - 1U));
-        TextSegmentOffsets.push_back(Phdr.p_offset & ~(PageSize - 1U));
+        PreferredTextSegmentAddresses.push_back(Phdr.p_vaddr);
+        TextSegmentOffsets.push_back(Phdr.p_offset);
       } else {
         PhdrInfo Info;
         Info.FileOffset = Phdr.p_offset;
diff --git a/llvm/tools/llvm-profgen/ProfiledBinary.h b/llvm/tools/llvm-profgen/ProfiledBinary.h
index d1dea857f3043..53146f39e1983 100644
--- a/llvm/tools/llvm-profgen/ProfiledBinary.h
+++ b/llvm/tools/llvm-profgen/ProfiledBinary.h
@@ -216,9 +216,9 @@ class ProfiledBinary {
   StringRef SymbolizerPath;
   // Options used to configure the symbolizer
   symbolize::LLVMSymbolizer::Options SymbolizerOpts;
-  // The runtime base address that the first executable segment is loaded at.
+  // The runtime base address used to canonicalize sampled addresses.
   uint64_t BaseAddress = 0;
-  // The runtime base address that the first loadabe segment is loaded at.
+  // The preferred base address derived from the first loadable segment.
   uint64_t FirstLoadableAddress = 0;
   // The preferred load address of each executable segment.
   std::vector<uint64_t> PreferredTextSegmentAddresses;
@@ -453,15 +453,17 @@ class ProfiledBinary {
   // Return the build ID used for filtering perfscript addresses.
   StringRef getFilterBuildID() const { return FilterBuildID; }
 
-  // Canonicalize to use preferred load address as base address.
+  // Translate a runtime address to its preferred virtual address.
   uint64_t canonicalizeVirtualAddress(uint64_t Address) {
     return Address - BaseAddress + getPreferredBaseAddress();
   }
-  // Return the preferred load address for the first executable segment.
+  // Return the preferred base used to canonicalize sampled addresses.
   uint64_t getPreferredBaseAddress() const {
-    return PreferredTextSegmentAddresses[0];
+    if (IsCOFF || IsKernel)
+      return PreferredTextSegmentAddresses[0];
+    return PreferredTextSegmentAddresses[0] - TextSegmentOffsets[0];
   }
-  // Return the preferred load address for the first loadable segment.
+  // Return the preferred base address derived from the first loadable segment.
   uint64_t getFirstLoadableAddress() const { return FirstLoadableAddress; }
   // Return the file offset for the first executable segment.
   uint64_t getTextSegmentOffset() const { return TextSegmentOffsets[0]; }

>From 919fa7042c2d7e1b18279136b2eb77798ca97874 Mon Sep 17 00:00:00 2001
From: huangjinjie <huangjinjie at bytedance.com>
Date: Wed, 26 Aug 2026 11:35:32 +0800
Subject: [PATCH 3/3] add some comments

---
 llvm/tools/llvm-profgen/PerfReader.cpp     | 4 ++++
 llvm/tools/llvm-profgen/ProfiledBinary.cpp | 2 ++
 2 files changed, 6 insertions(+)

diff --git a/llvm/tools/llvm-profgen/PerfReader.cpp b/llvm/tools/llvm-profgen/PerfReader.cpp
index 6aad37241f103..63a04df31b7fb 100644
--- a/llvm/tools/llvm-profgen/PerfReader.cpp
+++ b/llvm/tools/llvm-profgen/PerfReader.cpp
@@ -602,6 +602,10 @@ void PerfScriptReader::updateBinaryAddress(const MMapEvent &Event) {
   };
   const bool MMapContainsTextSegment =
       MMapContainsFileOffset(Binary->getTextSegmentOffset());
+
+  // For user-space ELF, subtract the mmap file offset to get the runtime
+  // address corresponding to file offset zero. Kernel and COFF retain their
+  // existing mmap address semantics.
   const uint64_t RuntimeBaseAddress = IsKernel || Binary->isCOFF()
                                           ? Event.Address
                                           : Event.Address - Event.Offset;
diff --git a/llvm/tools/llvm-profgen/ProfiledBinary.cpp b/llvm/tools/llvm-profgen/ProfiledBinary.cpp
index 2afc478e0543c..a541e842f80cb 100644
--- a/llvm/tools/llvm-profgen/ProfiledBinary.cpp
+++ b/llvm/tools/llvm-profgen/ProfiledBinary.cpp
@@ -368,6 +368,8 @@ void ProfiledBinary::setPreferredTextSegmentAddresses(const ELFFile<ELFT> &Obj,
       HasInterp = true;
     if (Phdr.p_type == ELF::PT_LOAD) {
       if (!SeenFirstLoadableSegment) {
+        // Derive the preferred address corresponding to file offset zero
+        // without assuming a page size.
         FirstLoadableAddress = Phdr.p_vaddr - Phdr.p_offset;
         SeenFirstLoadableSegment = true;
       }



More information about the llvm-commits mailing list