[llvm] [BOLT] Register PIE indirect goto relocations (PR #206819)
Fabian Parzefall via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 2 16:48:35 PDT 2026
https://github.com/pzfl updated https://github.com/llvm/llvm-project/pull/206819
>From 1d66111a658258afbcc39ebd50098aef01b81f97 Mon Sep 17 00:00:00 2001
From: Fabian Parzefall <parzefall at meta.com>
Date: Fri, 26 Jun 2026 11:40:36 -0700
Subject: [PATCH] [BOLT] Register PIE indirect goto relocations
PIE binaries use R_*_RELATIVE dynamic relocations for indirect goto jump
tables. BOLT creates entry points for their targets but does not mark
these addresses as targets from data relocations, so indirect jumps with
unknown control flow (which originates from indirect goto) have no CFG
successors and are interpreted as potential tail calls, while the jump
targets are incorrectly identified as additional entry points.
Add these offsets to the function's list of non-entry relocation
references instead of marking them as entry points. Extend relocation
rewriting to also check whether a block is externally referenced. This
mirrors behavior of data-to-code relocations in non-pie binaries.
---
bolt/include/bolt/Core/BinaryFunction.h | 12 ++++----
bolt/include/bolt/Rewrite/RewriteInstance.h | 8 ++++--
bolt/lib/Core/BinaryContext.cpp | 2 +-
bolt/lib/Core/BinaryFunction.cpp | 5 ++--
bolt/lib/Rewrite/RewriteInstance.cpp | 32 ++++++++++++++-------
bolt/test/AArch64/computed-goto.s | 19 ++++++------
bolt/test/X86/indirect-goto.test | 6 ++++
bolt/test/indirect-goto-relocs.test | 14 +++++----
8 files changed, 61 insertions(+), 37 deletions(-)
diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h
index 84fbd5661fd0a..82f0c11a8cd80 100644
--- a/bolt/include/bolt/Core/BinaryFunction.h
+++ b/bolt/include/bolt/Core/BinaryFunction.h
@@ -289,8 +289,8 @@ class BinaryFunction {
/// jump table relocations and computed goto tables.
///
/// Since relocations can be removed/deallocated, we store relocation offsets
- /// instead of pointers.
- DenseSet<uint64_t> InternalRefDataRelocations;
+ /// instead of pointers. Each entry maps relocation address to target address.
+ DenseMap<uint64_t, uint64_t> InternalRefDataRelocations;
/// Offsets of indirect branches with unknown destinations.
std::set<uint64_t> UnknownIndirectBranchOffsets;
@@ -662,7 +662,7 @@ class BinaryFunction {
uint64_t RelOffset) {
assert(FuncOffset != 0 && "Relocation should reference function internals");
registerReferencedOffset(FuncOffset);
- InternalRefDataRelocations.insert(RelOffset);
+ InternalRefDataRelocations.insert({RelOffset, getAddress() + FuncOffset});
const MCSymbol *ReferencedSymbol =
getOrCreateLocalLabel(getAddress() + FuncOffset);
@@ -1342,9 +1342,9 @@ class BinaryFunction {
void addRelocation(uint64_t Address, MCSymbol *Symbol, uint32_t RelType,
uint64_t Addend, uint64_t Value);
- /// Return locations (offsets) of data section relocations targeting internals
- /// of this functions.
- const DenseSet<uint64_t> &getInternalRefDataRelocations() const {
+ /// Return locations (offsets -> target address) of data section relocations
+ /// targeting internals of this functions.
+ const DenseMap<uint64_t, uint64_t> &getInternalRefDataRelocations() const {
return InternalRefDataRelocations;
}
diff --git a/bolt/include/bolt/Rewrite/RewriteInstance.h b/bolt/include/bolt/Rewrite/RewriteInstance.h
index 3873d944e01ba..eb22307801c30 100644
--- a/bolt/include/bolt/Rewrite/RewriteInstance.h
+++ b/bolt/include/bolt/Rewrite/RewriteInstance.h
@@ -241,8 +241,12 @@ class RewriteInstance {
uint64_t getNewFunctionAddress(uint64_t OldAddress);
/// Return address of a function or moved data in the new binary
- /// corresponding to \p OldAddress address in the original binary.
- uint64_t getNewFunctionOrDataAddress(uint64_t OldAddress);
+ /// corresponding to \p OldAddress address in the original binary. If \p
+ /// RelocationOffset is non-zero, this function will also translate an
+ /// internal label targeted by that relocation. This supports indirect goto
+ /// where data relocation reference non-entry basic blocks.
+ uint64_t getNewFunctionOrDataAddress(uint64_t OldAddress,
+ uint64_t RelocationOffset = 0);
/// Return value for the symbol \p Name in the output.
uint64_t getNewValueForSymbol(const StringRef Name);
diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
index 130c523bcda54..4c44d88b7ba2c 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -1553,7 +1553,7 @@ void BinaryContext::foldFunction(BinaryFunction &ChildBF,
ChildBF.getSymbols().clear();
// Reset function mapping for local symbols.
- for (uint64_t RelOffset : ChildBF.getInternalRefDataRelocations()) {
+ for (auto [RelOffset, _] : ChildBF.getInternalRefDataRelocations()) {
const Relocation *Rel = getRelocationAt(RelOffset);
if (!Rel || !Rel->Symbol)
continue;
diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index 200e286d8e80e..e8c3b143d0600 100644
--- a/bolt/lib/Core/BinaryFunction.cpp
+++ b/bolt/lib/Core/BinaryFunction.cpp
@@ -2105,7 +2105,7 @@ bool BinaryFunction::validateInternalRefDataRelocations() {
if (opts::StrictMode)
return true;
- DenseSet<uint64_t> UnclaimedRelocations(InternalRefDataRelocations);
+ DenseMap<uint64_t, uint64_t> UnclaimedRelocations(InternalRefDataRelocations);
for (const JumpTable *JT : llvm::make_second_range(JumpTables)) {
uint64_t EntryAddress = JT->getAddress();
while (EntryAddress < JT->getAddress() + JT->getSize()) {
@@ -2123,7 +2123,7 @@ bool BinaryFunction::validateInternalRefDataRelocations() {
<< " remain against function " << *this;
if (opts::Verbosity) {
BC.errs() << ":\n";
- for (uint64_t RelocationAddress : UnclaimedRelocations) {
+ for (auto [RelocationAddress, _] : UnclaimedRelocations) {
const Relocation *Relocation = BC.getRelocationAt(RelocationAddress);
BC.errs() << " ";
if (Relocation)
@@ -3270,6 +3270,7 @@ bool BinaryFunction::requiresAddressMap() const {
return false;
return opts::UpdateDebugSections || isMultiEntry() ||
+ !getInternalRefDataRelocations().empty() ||
requiresAddressTranslation();
}
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index 9e00d48e03853..f1cd6185343fb 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -2805,6 +2805,8 @@ void RewriteInstance::readDynamicRelocations(const SectionRef &Section,
if (Symbol)
SymbolIndex[Symbol] = getRelocationSymbol(InputFile, Rel);
+ // Check if this relocation targets an address within a function. This
+ // happens with indirect goto.
const uint64_t ReferencedAddress = SymbolAddress + Addend;
BinaryFunction *Func =
BC->getBinaryFunctionContainingAddress(ReferencedAddress);
@@ -2814,7 +2816,10 @@ void RewriteInstance::readDynamicRelocations(const SectionRef &Section,
if (!Func->isInConstantIsland(ReferencedAddress)) {
if (const uint64_t ReferenceOffset =
ReferencedAddress - Func->getAddress()) {
- Func->addEntryPointAtOffset(ReferenceOffset);
+ assert(!BC->getBinaryFunctionContainingAddress(Rel.getOffset()) &&
+ "Relative relocation to code only from data");
+ Func->registerInternalRefDataRelocation(ReferenceOffset,
+ Rel.getOffset());
}
} else {
BC->errs() << "BOLT-ERROR: referenced address at 0x"
@@ -5779,7 +5784,8 @@ void RewriteInstance::patchELFAllocatableRelrSection(
return;
// No fixup needed if symbol address was not changed
- const uint64_t Addend = getNewFunctionOrDataAddress(Rel.Addend);
+ const uint64_t Addend = getNewFunctionOrDataAddress(
+ Rel.Addend, Section.getAddress() + Rel.Offset);
if (!Addend)
return;
@@ -5915,7 +5921,8 @@ RewriteInstance::patchELFAllocatableRelaSections(ELFObjectFile<ELFT> *File) {
SymbolIdx = getOutputDynamicSymbolIndex(Symbol);
} else {
// Usually this case is used for R_*_(I)RELATIVE relocations
- const uint64_t Address = getNewFunctionOrDataAddress(Addend);
+ const uint64_t Address =
+ getNewFunctionOrDataAddress(Addend, SectionAddress + Rel.Offset);
if (Address)
Addend = Address;
}
@@ -6228,7 +6235,9 @@ uint64_t RewriteInstance::getNewFunctionAddress(uint64_t OldAddress) {
return Function->getOutputAddress();
}
-uint64_t RewriteInstance::getNewFunctionOrDataAddress(uint64_t OldAddress) {
+uint64_t
+RewriteInstance::getNewFunctionOrDataAddress(uint64_t OldAddress,
+ uint64_t RelocationOffset) {
if (uint64_t Function = getNewFunctionAddress(OldAddress))
return Function;
@@ -6239,13 +6248,16 @@ uint64_t RewriteInstance::getNewFunctionOrDataAddress(uint64_t OldAddress) {
if (const BinaryFunction *BF =
BC->getBinaryFunctionContainingAddress(OldAddress)) {
if (BF->isEmitted()) {
- // If OldAddress is the another entry point of
- // the function, then BOLT could get the new address.
- if (BF->isMultiEntry()) {
- for (const BinaryBasicBlock &BB : *BF)
- if (BB.isEntryPoint() &&
- (BF->getAddress() + BB.getOffset()) == OldAddress)
+ // If OldAddress is the another entry point of the function or the target
+ // of an indirect goto, then BOLT could get the new address.
+ uint64_t RelocationTarget =
+ BF->getInternalRefDataRelocations().lookup(RelocationOffset);
+ if (RelocationTarget == OldAddress || BF->isMultiEntry()) {
+ for (const BinaryBasicBlock &BB : *BF) {
+ const uint64_t BBAddr = BF->getAddress() + BB.getOffset();
+ if ((RelocationTarget || BB.isEntryPoint()) && BBAddr == OldAddress)
return BB.getOutputStartAddress();
+ }
}
BC->errs() << "BOLT-ERROR: unable to get new address corresponding to "
"input address 0x"
diff --git a/bolt/test/AArch64/computed-goto.s b/bolt/test/AArch64/computed-goto.s
index 5d775b9a6aeae..3a97ddf1804c6 100644
--- a/bolt/test/AArch64/computed-goto.s
+++ b/bolt/test/AArch64/computed-goto.s
@@ -1,12 +1,12 @@
-// This test checks that BOLT creates entry points for addresses
+// This test checks that BOLT recognizes blocks for addresses
// referenced by dynamic relocations.
// The test also checks that BOLT can map addresses inside functions.
-// Checks for error and entry points.
+// Checks for error and cfg.
# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o
# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q
# RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck %s
-# RUN: llvm-bolt %t.exe -o %t.bolt --print-cfg | FileCheck --check-prefix=CHECK-ENTRIES %s
+# RUN: llvm-bolt %t.exe -o %t.bolt --print-cfg | FileCheck --check-prefix=CHECK-CFG %s
// Checks for dynamic relocations.
# RUN: llvm-readelf -dr %t.bolt > %t.out.txt
@@ -30,13 +30,12 @@
# CHECK-RELOCS: [[#ADDR]] <unknown>
# CHECK-RELOCS: [[#ADDR + 8]] <unknown>
-// Check that BOLT registers extra entry points for dynamic relocations.
-# CHECK-ENTRIES: Binary Function "main" after building cfg {
-# CHECK-ENTRIES: IsMultiEntry: 1
-# CHECK-ENTRIES: .Ltmp0 {{.*}}
-# CHECK-ENTRIES-NEXT: Secondary Entry Point: {{.*}}
-# CHECK-ENTRIES: .Ltmp1 {{.*}}
-# CHECK-ENTRIES-NEXT: Secondary Entry Point: {{.*}}
+// Check that BOLT recognizes indirect targeted blocks.
+# CHECK-CFG: Binary Function "main" after building cfg {
+# CHECK-CFG: IsMultiEntry: 0
+# CHECK-CFG: BB Count : 3
+# CHECK-CFG: .Ltmp0 {{.*}}
+# CHECK-CFG: .Ltmp1 {{.*}}
.globl main
.p2align 2
diff --git a/bolt/test/X86/indirect-goto.test b/bolt/test/X86/indirect-goto.test
index aeb89de3f2fc2..c7dae07446dfd 100644
--- a/bolt/test/X86/indirect-goto.test
+++ b/bolt/test/X86/indirect-goto.test
@@ -4,6 +4,12 @@ RUN: llvm-bolt %t -o %t.null --relocs=1 --print-cfg --print-only=main \
RUN: --strict \
RUN: 2>&1 | FileCheck %s
+## Check indirect goto works in pie
+RUN: %clang %cflags -fPIE -pie %S/../Inputs/indirect_goto.c -o %t.pie -Wl,-q
+RUN: llvm-bolt %t.pie -o %t.pie.null --relocs=1 --print-cfg --print-only=main \
+RUN: --strict \
+RUN: 2>&1 | FileCheck %s
+
## Check that all possible destinations are included as successors.
CHECK: jmpq *%rax # UNKNOWN CONTROL FLOW
CHECK: Successors: .Ltmp0, .Ltmp1, .Ltmp2
diff --git a/bolt/test/indirect-goto-relocs.test b/bolt/test/indirect-goto-relocs.test
index e28d64135b44e..e48c3529192d7 100644
--- a/bolt/test/indirect-goto-relocs.test
+++ b/bolt/test/indirect-goto-relocs.test
@@ -1,12 +1,14 @@
-// This test checks that BOLT creates entry points from sources
-// that use indirect goto.
+// This test checks that BOLT recognizes unknown control flow from sources that
+// use indirect goto.
REQUIRES: system-linux
RUN: %clang %cflags -pie %S/Inputs/indirect_goto.c -o %t.exe -Wl,-q
-RUN: llvm-bolt %t.exe -o %t.bolt --print-cfg | FileCheck --check-prefix=CHECK-PIE %s
+RUN: llvm-bolt %t.exe -o %t.bolt --print-cfg --print-only=main \
+RUN: | FileCheck --check-prefix=CHECK-PIE %s
-// Check that BOLT registers extra entry points for dynamic relocations with PIE.
+// Check that BOLT discovers basic blocks for dynamic relocations with PIE.
CHECK-PIE: Binary Function "main" after building cfg {
-CHECK-PIE: IsMultiEntry: 1
-CHECK-PIE: Secondary Entry Points : {{.*}}
+CHECK-PIE: IsMultiEntry: 0
+CHECK-PIE: BB Count : {{5|6}}
+// BB Count can vary across architectures.
More information about the llvm-commits
mailing list