[llvm-branch-commits] [llvm] [BOLT][BTI] Patch ignored functions in place when targeting them with indirect branches (PR #177165)

Gergely Bálint via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Feb 10 05:34:54 PST 2026


https://github.com/bgergely0 updated https://github.com/llvm/llvm-project/pull/177165

>From 945b8fa1b5dee0ee1ba2fa58084ce885da8d7feb Mon Sep 17 00:00:00 2001
From: Gergely Balint <gergely.balint at arm.com>
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/bti-long-jmp-ignored.s      |  6 +-
 bolt/test/AArch64/long-jmp-bti-ignored-nop.s  | 39 ++++++++++++
 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 ea043c12583c5..0f0f6016192fc 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)
       // No need to check TargetFunction for nullptr, because
diff --git a/bolt/test/AArch64/bti-long-jmp-ignored.s b/bolt/test/AArch64/bti-long-jmp-ignored.s
index f498ee77ec895..6ff59b30568b9 100644
--- a/bolt/test/AArch64/bti-long-jmp-ignored.s
+++ b/bolt/test/AArch64/bti-long-jmp-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
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
+



More information about the llvm-branch-commits mailing list