https://github.com/bgergely0 created https://github.com/llvm/llvm-project/pull/177165
indirect branches When applying BTI fixups to indirect branch targets, ignored functions are considered a special case: - these hold no instructions, - have no CFG, - and are not emitted in the new text section. The solution is to patch the entry points in the original location. If such a situation occurs in a binary, recompilation using the -fpatchable-function-entry flag is required. This will place a nop at all function starts, which BOLT can use to patch the original section. Without the extra nop, BOLT cannot safely patch the original .text section. An alternative solution could be to also ignore the function from which the stub starts. This has not been tried as LongJmp pass - where most stubs are inserted - is currently not equipped to ignore functions. Testing: both the success and failure cases are covered with lit tests. From e593fb1e1c07fd3c40db0c3a01f8de2e612055f1 Mon Sep 17 00:00:00 2001 From: Gergely Balint <[email protected]> Date: Thu, 8 Jan 2026 14:54:35 +0000 Subject: [PATCH] [BOLT][BTI] Patch ignored functions in place when targeting them with indirect branches When applying BTI fixups to indirect branch targets, ignored functions are considered a special case: - these hold no instructions, - have no CFG, - and are not emitted in the new text section. The solution is to patch the entry points in the original location. If such a situation occurs in a binary, recompilation using the -fpatchable-function-entry flag is required. This will place a nop at all function starts, which BOLT can use to patch the original section. Without the extra nop, BOLT cannot safely patch the original .text section. An alternative solution could be to also ignore the function from which the stub starts. This has not been tried as LongJmp pass - where most stubs are inserted - is currently not equipped to ignore functions. Testing: both the success and failure cases are covered with lit tests. --- bolt/include/bolt/Core/MCPlusBuilder.h | 5 ++ .../Target/AArch64/AArch64MCPlusBuilder.cpp | 61 ++++++++++++++----- bolt/test/AArch64/long-jmp-bti-ignored-nop.s | 39 ++++++++++++ bolt/test/AArch64/long-jmp-bti-ignored.s | 6 +- 4 files changed, 91 insertions(+), 20 deletions(-) create mode 100644 bolt/test/AArch64/long-jmp-bti-ignored-nop.s diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h index b78270b312980..d8debb65b4546 100644 --- a/bolt/include/bolt/Core/MCPlusBuilder.h +++ b/bolt/include/bolt/Core/MCPlusBuilder.h @@ -1787,6 +1787,11 @@ class MCPlusBuilder { llvm_unreachable("not implemented"); } + virtual void patchFunctionEntryForBTI(BinaryFunction &Function, + MCInst &Call) { + llvm_unreachable("not implemented"); + } + virtual void applyBTIFixupToTarget(BinaryBasicBlock &StubBB) { llvm_unreachable("not implemented"); } diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp index fea3a937046ad..6c8644f3ea634 100644 --- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp +++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp @@ -23,6 +23,7 @@ #include "bolt/Core/MCPlusBuilder.h" #include "llvm/BinaryFormat/ELF.h" #include "llvm/MC/MCContext.h" +#include "llvm/MC/MCDisassembler/MCDisassembler.h" #include "llvm/MC/MCInstBuilder.h" #include "llvm/MC/MCInstrInfo.h" #include "llvm/MC/MCRegister.h" @@ -1733,6 +1734,46 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { BC.createInstructionPatch(PLTFunction.getAddress(), NewPLTSeq); } + /// Decode entry instruction of \p Function without CFG. If it's a BTI + /// matching \p Call, do nothing. If it's a nop, patch it to a BTI. If it's + /// neither, emit an error. + void patchFunctionEntryForBTI(BinaryFunction &Function, + MCInst &Call) override { + BinaryContext &BC = Function.getBinaryContext(); + const uint64_t InstrAddr = Function.getAddress(); + ErrorOr<ArrayRef<uint8_t>> FunctionData = Function.getData(); + if (!FunctionData) { + errs() << "BOLT-ERROR: corresponding section is non-executable or " + << "empty for function " << Function.getPrintName(); + exit(1); + } + // getInstruction writes this, its value doesn't matter here. + uint64_t InstrSize = 0; + MCInst FirstInst; + if (FunctionData->empty() || + !BC.DisAsm->getInstruction(FirstInst, InstrSize, *FunctionData, + InstrAddr, nulls())) { + errs() << "BOLT-ERROR: unable to disassemble first instruction of " + << Function.getPrintName() + << formatv(" at address {0:x}\n", InstrAddr); + exit(1); + } + if (isCallCoveredByBTI(Call, FirstInst)) + return; + if (!isNoop(FirstInst)) { + errs() << "BOLT-ERROR: Cannot add BTI to function without CFG " + << Function.getPrintName() + << ". Recompile the binary using -fpatchable-function-entry 1 to " + "include a nop at the entry"; + exit(1); + } + InstructionListType NewEntry; + MCInst BTIInst; + createBTI(BTIInst, BTIKind::C); + NewEntry.push_back(BTIInst); + BC.createInstructionPatch(Function.getAddress(), NewEntry); + } + void applyBTIFixupToSymbol(BinaryContext &BC, const MCSymbol *TargetSymbol, MCInst &Call) override { BinaryFunction *TargetFunction = BC.getFunctionForSymbol(TargetSymbol); @@ -1762,22 +1803,10 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { patchPLTEntryForBTI(*TargetFunction, Call); return; } - if (TargetFunction && TargetFunction->isIgnored()) { - errs() << "BOLT-ERROR: Cannot add BTI landing pad to ignored function " - << TargetFunction->getPrintName() << "\n"; - exit(1); - } - if (TargetFunction && !TargetFunction->hasCFG()) { - if (TargetFunction->hasInstructions()) { - auto FirstII = TargetFunction->instrs().begin(); - MCInst FirstInst = FirstII->second; - if (isCallCoveredByBTI(Call, FirstInst)) - return; - } - errs() - << "BOLT-ERROR: Cannot add BTI landing pad to function without CFG: " - << TargetFunction->getPrintName() << "\n"; - exit(1); + if (TargetFunction && + (TargetFunction->isIgnored() || !TargetFunction->hasCFG())) { + patchFunctionEntryForBTI(*TargetFunction, Call); + return; } if (!TargetBB) // !TargetBB -> TargetFunction is not a nullptr diff --git a/bolt/test/AArch64/long-jmp-bti-ignored-nop.s b/bolt/test/AArch64/long-jmp-bti-ignored-nop.s new file mode 100644 index 0000000000000..9ef67274c7c19 --- /dev/null +++ b/bolt/test/AArch64/long-jmp-bti-ignored-nop.s @@ -0,0 +1,39 @@ +# This test checks the situation where LongJmp adds a stub targeting an ignored (skipped) function. +# As far_away_func has a nop at entry, BOLT can patch it to a BTI. + +# REQUIRES: system-linux, asserts + +# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown \ +# RUN: -mattr=+bti -aarch64-mark-bti-property %s -o %t.o +# RUN: %clang %cflags -O0 %t.o -o %t.exe -Wl,-q -Wl,-z,force-bti +# RUN: llvm-bolt %t.exe -o %t.bolt \ +# RUN: --align-text=0x10000000 --skip-funcs=far_away_func 2>&1 | FileCheck %s + +# CHECK-NOT: BOLT-ERROR: Cannot add BTI to function without CFG far_away_func. Recompile the binary using -fpatchable-function-entry 1 to include a nop at the entry + +# RUN: llvm-objdump -d -j .bolt.org.text %t.bolt | FileCheck %s --check-prefix=OBJDUMP +# OBJDUMP: <far_away_func>: +# OBJDUMP-NEXT: bti c +# OBJDUMP-NEXT: add x0, x0, #0x1 + + .section .text + .align 4 + .global _start + .type _start, %function +_start: + bti c + bl far_away_func + ret + +# This is skipped, so it stays in the .bolt.org.text. +# The .text produced by BOLT is aligned to 0x10000000, +# so _start will need a stub to jump here. + .global far_away_func + .type far_away_func, %function +far_away_func: + nop + add x0, x0, #1 + ret + +.reloc 0, R_AARCH64_NONE + diff --git a/bolt/test/AArch64/long-jmp-bti-ignored.s b/bolt/test/AArch64/long-jmp-bti-ignored.s index f498ee77ec895..6ff59b30568b9 100644 --- a/bolt/test/AArch64/long-jmp-bti-ignored.s +++ b/bolt/test/AArch64/long-jmp-bti-ignored.s @@ -1,7 +1,5 @@ # This test checks the situation where LongJmp adds a stub targeting an ignored (skipped) function. -# The problem is that by default BOLT cannot modify ignored functions, so it cannot add the needed BTI. - -# Current behaviour is to emit an error. +# As far_away_func does not have a nop at the entry, BOLT cannot safely patch it. # REQUIRES: system-linux, asserts @@ -11,7 +9,7 @@ # RUN: not llvm-bolt %t.exe -o %t.bolt \ # RUN: --align-text=0x10000000 --skip-funcs=far_away_func 2>&1 | FileCheck %s -# CHECK: BOLT-ERROR: Cannot add BTI landing pad to ignored function far_away_func +# CHECK: BOLT-ERROR: Cannot add BTI to function without CFG far_away_func. Recompile the binary using -fpatchable-function-entry 1 to include a nop at the entry .section .text .align 4 _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
