[BOLT] Register PIE indirect goto relocations (#206819)

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.
This commit is contained in:
Fabian Parzefall 2026-07-08 14:21:12 -07:00 committed by GitHub
parent 790fbc8fe5
commit 0bd936eee2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 50 additions and 24 deletions

View file

@ -681,6 +681,12 @@ private:
return !ExternallyReferencedOffsets.empty();
}
/// True if there are references to \p Offset inside this function from data,
/// e.g. from indirect goto references.
bool hasInternalReferenceAt(uint64_t Offset) const {
return ExternallyReferencedOffsets.count(Offset) != 0;
}
/// Return an entry ID corresponding to a symbol known to belong to
/// the function.
///

View file

@ -2569,7 +2569,10 @@ Error BinaryFunction::buildCFG(MCPlusBuilder::AllocatorIdTy AllocatorId) {
setSimple(false);
}
clearList(ExternallyReferencedOffsets);
// Clear externally referenced offsets only if there are no relocations
// targeting internal references (from indirect goto).
if (InternalRefDataRelocations.empty())
clearList(ExternallyReferencedOffsets);
clearList(UnknownIndirectBranchOffsets);
return Error::success();
@ -3270,6 +3273,7 @@ bool BinaryFunction::requiresAddressMap() const {
return false;
return opts::UpdateDebugSections || isMultiEntry() ||
!getInternalRefDataRelocations().empty() ||
requiresAddressTranslation();
}

View file

@ -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"
@ -6239,13 +6244,17 @@ 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 another entry point of the function or the target of
// an indirect goto, then BOLT could get the new address.
bool HasInternalRelocationTarget =
BF->hasInternalReferenceAt(OldAddress - BF->getAddress());
if (HasInternalRelocationTarget || BF->isMultiEntry()) {
for (const BinaryBasicBlock &BB : *BF) {
const uint64_t BBAddr = BF->getAddress() + BB.getOffset();
if ((HasInternalRelocationTarget || BB.isEntryPoint()) &&
BBAddr == OldAddress)
return BB.getOutputStartAddress();
}
}
BC->errs() << "BOLT-ERROR: unable to get new address corresponding to "
"input address 0x"

View file

@ -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

View file

@ -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

View file

@ -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.