[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