Skip to content

[BOLT] Skip out-of-range pending relocations #116964

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bolt/include/bolt/Core/Relocation.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ class Relocation {
/// Adjust value depending on relocation type (make it PC relative or not).
static uint64_t encodeValue(uint32_t Type, uint64_t Value, uint64_t PC);

/// Return true if there are enough bits to encode the relocation value.
static bool canEncodeValue(uint32_t Type, uint64_t Value, uint64_t PC);

/// Extract current relocated value from binary contents. This is used for
/// RISC architectures where values are encoded in specific bits depending
/// on the relocation value. For X86, we limit to sign extending the value
Expand Down
2 changes: 2 additions & 0 deletions bolt/lib/Core/BinaryFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1795,6 +1795,8 @@ bool BinaryFunction::scanExternalRefs() {
// Create relocation for every fixup.
for (const MCFixup &Fixup : Fixups) {
std::optional<Relocation> Rel = BC.MIB->createRelocation(Fixup, *BC.MAB);
// Can be skipped in case of overlow during relocation value encoding.
Rel->setOptional();
if (!Rel) {
Success = false;
continue;
Expand Down
27 changes: 26 additions & 1 deletion bolt/lib/Core/BinarySection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "bolt/Core/BinarySection.h"
#include "bolt/Core/BinaryContext.h"
#include "bolt/Utils/CommandLineOpts.h"
#include "bolt/Utils/Utils.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/Support/CommandLine.h"
Expand All @@ -22,8 +23,8 @@ using namespace llvm;
using namespace bolt;

namespace opts {
extern cl::opt<bool> PrintRelocations;
extern cl::opt<bool> HotData;
extern cl::opt<bool> PrintRelocations;
} // namespace opts

uint64_t BinarySection::Count = 0;
Expand Down Expand Up @@ -174,11 +175,30 @@ void BinarySection::flushPendingRelocations(raw_pwrite_stream &OS,
OS.pwrite(Patch.Bytes.data(), Patch.Bytes.size(),
SectionFileOffset + Patch.Offset);

uint64_t SkippedPendingRelocations = 0;
for (Relocation &Reloc : PendingRelocations) {
uint64_t Value = Reloc.Addend;
if (Reloc.Symbol)
Value += Resolver(Reloc.Symbol);

// Safely skip any optional pending relocation that cannot be encoded.
if (Reloc.isOptional() &&
!Relocation::canEncodeValue(Reloc.Type, Value,
SectionAddress + Reloc.Offset)) {

// A successful run of 'scanExternalRefs' means that all pending
// relocations are flushed. Otherwise, PatchEntries should run.
if (!opts::ForcePatch) {
BC.errs()
<< "BOLT-ERROR: cannot encode relocation for symbol "
<< Reloc.Symbol->getName()
<< " as it is out-of-range. To proceed must use -force-patch\n";
exit(1);
}

++SkippedPendingRelocations;
continue;
}
Value = Relocation::encodeValue(Reloc.Type, Value,
SectionAddress + Reloc.Offset);

Expand All @@ -197,6 +217,11 @@ void BinarySection::flushPendingRelocations(raw_pwrite_stream &OS,
}

clearList(PendingRelocations);

if (SkippedPendingRelocations > 0 && opts::Verbosity >= 1) {
BC.outs() << "BOLT-INFO: skipped " << SkippedPendingRelocations
<< " out-of-range optional relocations\n";
}
}

BinarySection::~BinarySection() { updateContents(nullptr, 0); }
Expand Down
33 changes: 33 additions & 0 deletions bolt/lib/Core/Relocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,16 @@ static uint64_t encodeValueX86(uint32_t Type, uint64_t Value, uint64_t PC) {
return Value;
}

static bool canEncodeValueAArch64(uint32_t Type, uint64_t Value, uint64_t PC) {
switch (Type) {
default:
llvm_unreachable("unsupported relocation");
case ELF::R_AARCH64_CALL26:
case ELF::R_AARCH64_JUMP26:
return isInt<28>(Value - PC);
}
}

static uint64_t encodeValueAArch64(uint32_t Type, uint64_t Value, uint64_t PC) {
switch (Type) {
default:
Expand Down Expand Up @@ -303,6 +313,16 @@ static uint64_t encodeValueAArch64(uint32_t Type, uint64_t Value, uint64_t PC) {
return Value;
}

static uint64_t canEncodeValueRISCV(uint32_t Type, uint64_t Value,
uint64_t PC) {
switch (Type) {
default:
llvm_unreachable("unsupported relocation");
case ELF::R_RISCV_64:
return true;
}
}

static uint64_t encodeValueRISCV(uint32_t Type, uint64_t Value, uint64_t PC) {
switch (Type) {
default:
Expand Down Expand Up @@ -739,6 +759,19 @@ uint64_t Relocation::encodeValue(uint32_t Type, uint64_t Value, uint64_t PC) {
}
}

bool Relocation::canEncodeValue(uint32_t Type, uint64_t Value, uint64_t PC) {
switch (Arch) {
default:
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return canEncodeValueAArch64(Type, Value, PC);
case Triple::riscv64:
return canEncodeValueRISCV(Type, Value, PC);
case Triple::x86_64:
return true;
}
}

uint64_t Relocation::extractValue(uint32_t Type, uint64_t Contents,
uint64_t PC) {
switch (Arch) {
Expand Down
62 changes: 62 additions & 0 deletions bolt/unittests/Core/BinaryContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//

#include "bolt/Core/BinaryContext.h"
#include "bolt/Utils/CommandLineOpts.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
#include "llvm/Support/TargetSelect.h"
Expand Down Expand Up @@ -161,6 +162,67 @@ TEST_P(BinaryContextTester, FlushPendingRelocJUMP26) {
<< "Wrong forward branch value\n";
}

TEST_P(BinaryContextTester,
FlushOptionalOutOfRangePendingRelocCALL26_ForcePatchOff) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();

// Tests that flushPendingRelocations exits if any pending relocation is out
// of range and PatchEntries hasn't run. Pending relocations are added by
// scanExternalRefs, so this ensures that either all scanExternalRefs
// relocations were flushed or PatchEntries ran.

BinarySection &BS = BC->registerOrUpdateSection(
".text", ELF::SHT_PROGBITS, ELF::SHF_EXECINSTR | ELF::SHF_ALLOC);
// Create symbol 'Func0x4'
MCSymbol *RelSymbol = BC->getOrCreateGlobalSymbol(4, "Func");
ASSERT_TRUE(RelSymbol);
Relocation Reloc{8, RelSymbol, ELF::R_AARCH64_CALL26, 0, 0};
Reloc.setOptional();
BS.addPendingRelocation(Reloc);

SmallVector<char> Vect;
raw_svector_ostream OS(Vect);

// Resolve relocation symbol to a high value so encoding will be out of range.
EXPECT_EXIT(BS.flushPendingRelocations(
OS, [&](const MCSymbol *S) { return 0x800000F; }),
::testing::ExitedWithCode(1),
"BOLT-ERROR: cannot encode relocation for symbol Func0x4 as it is"
" out-of-range. To proceed must use -force-patch");
}

TEST_P(BinaryContextTester,
FlushOptionalOutOfRangePendingRelocCALL26_ForcePatchOn) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();

// Tests that flushPendingRelocations can skip flushing any optional pending
// relocations that cannot be encoded, given that PatchEntries runs.
opts::ForcePatch = true;

opts::Verbosity = 1;
testing::internal::CaptureStdout();

BinarySection &BS = BC->registerOrUpdateSection(
".text", ELF::SHT_PROGBITS, ELF::SHF_EXECINSTR | ELF::SHF_ALLOC);
MCSymbol *RelSymbol = BC->getOrCreateGlobalSymbol(4, "Func");
ASSERT_TRUE(RelSymbol);
Relocation Reloc{8, RelSymbol, ELF::R_AARCH64_CALL26, 0, 0};
Reloc.setOptional();
BS.addPendingRelocation(Reloc);

SmallVector<char> Vect;
raw_svector_ostream OS(Vect);

// Resolve relocation symbol to a high value so encoding will be out of range.
BS.flushPendingRelocations(OS, [&](const MCSymbol *S) { return 0x800000F; });
outs().flush();
std::string CapturedStdOut = testing::internal::GetCapturedStdout();
EXPECT_EQ(CapturedStdOut,
"BOLT-INFO: skipped 1 out-of-range optional relocations\n");
}

#endif

TEST_P(BinaryContextTester, BaseAddress) {
Expand Down
1 change: 1 addition & 0 deletions bolt/unittests/Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ target_link_libraries(CoreTests
LLVMBOLTCore
LLVMBOLTRewrite
LLVMBOLTProfile
LLVMBOLTUtils
LLVMTestingSupport
)

Expand Down