[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