[llvm-branch-commits] [llvm] [BOLT][BTI] Patch LLD-generated PLTs to contain BTI landing pad (PR #173245)
Gergely Bálint via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Wed Jan 7 10:12:21 PST 2026
https://github.com/bgergely0 updated https://github.com/llvm/llvm-project/pull/173245
>From 0d36997c70f91bc52fcbba021571b865de7b07ba Mon Sep 17 00:00:00 2001
From: Gergely Balint <gergely.balint at arm.com>
Date: Fri, 19 Dec 2025 10:53:56 +0000
Subject: [PATCH 1/3] [BOLT][BTI] Patch LLD-generated PLTs to contain BTI
landing pad
This patch adds the patchPLTEntryForBTI to enable patching PLT entries
generated by LLD.
Context:
To keep BTI consistent, targets of stubs inserted in LongJmp need to be
patched. As PLTs are not optimized and emitted by BOLT, this patch adds
a helper for patching them in the original location.
For PLTs generated by LLD, this is safe as LLD inserts extra nops to
PLTs which don't already contain a BTI.
PLT entry before patching:
adrp x16, Page(&(.got.plt[n]))
ldr x17, [x16, Offset(&(.got.plt[n]))]
add x16, x16, Offset(&(.got.plt[n]))
br x17
nop
nop
PLT entry after patching:
bti c
adrp x16, Page(&(.got.plt[n]))
ldr x17, [x16, Offset(&(.got.plt[n]))]
add x16, x16, Offset(&(.got.plt[n]))
br x17
nop
Safety considerations:
The PLT entry can become incorrect if shifting the ADRP moves it
across a page boundary.
The PLT entry is 24 bytes, and page size is 4096 (or 16384) bytes.
Their GCD is 8 bytes, meaning that shifting the ADRP is safe, as long as
it's shifted less than 8 bytes.
The introduced function only shifts the ADRP by one instruction (4 bytes),
meaning there is no need to recompute the ADRP offset.
---
bolt/include/bolt/Core/MCPlusBuilder.h | 4 ++
bolt/lib/Passes/LongJmp.cpp | 5 ++
.../Target/AArch64/AArch64MCPlusBuilder.cpp | 61 +++++++++++++++++++
bolt/test/runtime/AArch64/long-jmp-bti-plt.c | 37 +++++++++--
4 files changed, 103 insertions(+), 4 deletions(-)
diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h
index 31bf73c839c3a..fdd86deea4c5d 100644
--- a/bolt/include/bolt/Core/MCPlusBuilder.h
+++ b/bolt/include/bolt/Core/MCPlusBuilder.h
@@ -1784,6 +1784,10 @@ class MCPlusBuilder {
return 0;
}
+ virtual void patchPLTEntryForBTI(BinaryFunction &PLTFunction, MCInst &Call) {
+ llvm_unreachable("not implemented");
+ }
+
virtual bool analyzeVirtualMethodCall(InstructionIterator Begin,
InstructionIterator End,
std::vector<MCInst *> &MethodFetchInsns,
diff --git a/bolt/lib/Passes/LongJmp.cpp b/bolt/lib/Passes/LongJmp.cpp
index 9fedf919dd489..798e1ba08918a 100644
--- a/bolt/lib/Passes/LongJmp.cpp
+++ b/bolt/lib/Passes/LongJmp.cpp
@@ -499,6 +499,11 @@ Error LongJmpPass::relaxStub(BinaryBasicBlock &StubBB, bool &Modified) {
<< RealTargetSym->getName() << "\n";
exit(1);
}
+ if (TargetFunction && TargetFunction->isPLTFunction()) {
+ BC.MIB->patchPLTEntryForBTI(*TargetFunction,
+ *StubBB.getLastNonPseudoInstr());
+ return;
+ }
if (TargetFunction && TargetFunction->isIgnored()) {
// Includes PLT functions.
BC.errs() << "BOLT-ERROR: Cannot add BTI landing pad to ignored function "
diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
index 03fb4ddc2f238..a56cf0fc6d9cb 100644
--- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
+++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
@@ -1669,6 +1669,67 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
return Base + Offset;
}
+ /// PLT entry before patching:
+ ///
+ /// adrp x16, Page(&(.got.plt[n]))
+ /// ldr x17, [x16, Offset(&(.got.plt[n]))]
+ /// add x16, x16, Offset(&(.got.plt[n]))
+ /// br x17
+ /// nop
+ /// nop
+ ///
+ /// PLT entry after patching:
+ ///
+ /// bti c
+ /// adrp x16, Page(&(.got.plt[n]))
+ /// ldr x17, [x16, Offset(&(.got.plt[n]))]
+ /// add x16, x16, Offset(&(.got.plt[n]))
+ /// br x17
+ /// nop
+ ///
+ /// Safety considerations:
+ ///
+ /// The PLT entry will become incorrect if shifting the ADRP by one
+ /// instruction (4 bytes) moves it across a page boundary.
+ ///
+ /// The PLT entry is 24 bytes, and page size is 4096 (or 16384) bytes.
+ /// Their GCD is 8 bytes, meaning that shifting the ADRP is safe, as long as
+ /// it is shifted by less than 8 bytes.
+ ///
+ /// If the PLT entry does not contain extra nops, this function will create an
+ /// error. This can happen in binaries linked using BFD.
+ void patchPLTEntryForBTI(BinaryFunction &PLTFunction, MCInst &Call) override {
+ BinaryContext &BC = PLTFunction.getBinaryContext();
+ assert(PLTFunction.isPLTFunction() &&
+ "patchPLTEntryForBTI called on a non-PLT function");
+ // Checking if the PLT entry already starts with the BTI needed for Call.
+ auto FirstBBI = PLTFunction.begin();
+ auto FirstII = FirstBBI->begin();
+ assert(FirstII != FirstBBI->end() && "Cannot patch empty PLT entry");
+ if (isCallCoveredByBTI(Call, *FirstII))
+ return;
+ // Checking if there are extra nops at the end. If not, BOLT cannot patch
+ // the PLT entry.
+ auto LastBBI = std::prev(PLTFunction.end());
+ auto LastII = std::prev(LastBBI->end());
+ if (!isNoop(*LastII)) {
+ errs() << "BOLT-ERROR: Cannot patch PLT entry "
+ << PLTFunction.getPrintName()
+ << " to have a BTI landing pad. Relink the workload using LLD.\n";
+ exit(1);
+ }
+ // If the PLT does not have a BTI, and it has nops, create a new instruction
+ // sequence to patch the entry with.
+ InstructionListType NewPLTSeq;
+ MCInst BTIInst;
+ createBTI(BTIInst, BTIKind::C);
+ NewPLTSeq.push_back(BTIInst);
+ for (auto II = FirstBBI->begin(); II != FirstBBI->end(); ++II) {
+ NewPLTSeq.push_back(*II);
+ }
+ BC.createInstructionPatch(PLTFunction.getAddress(), NewPLTSeq);
+ }
+
unsigned getInvertedBranchOpcode(unsigned Opcode) const {
switch (Opcode) {
default:
diff --git a/bolt/test/runtime/AArch64/long-jmp-bti-plt.c b/bolt/test/runtime/AArch64/long-jmp-bti-plt.c
index 6d3e1e7a3e820..4e8aebf6c4f53 100644
--- a/bolt/test/runtime/AArch64/long-jmp-bti-plt.c
+++ b/bolt/test/runtime/AArch64/long-jmp-bti-plt.c
@@ -1,5 +1,13 @@
-/* This test checks that LongJmp can (not) add BTI instructions to PLT entries
- when targeting them using stubs.
+// This test checks that LongJmp can add BTI instructions to PLT entries when
+// targeting them using stubs.
+//
+// The test uses a non-default layout, where the .plt section is placed before
+// the text section. This is needed so the new code placed under the .data
+// section is far enough from the PLt to trigger shortJmp stubs.
+//
+// In practice, the PLT section can also be placed after the original text
+// section. In this scenario, the text section has to be large enough to trigger
+// shortJmp stubs.
// REQUIRES: system-linux
@@ -8,10 +16,31 @@
// RUN: %clang --target=aarch64-unknown-linux-gnu -mbranch-protection=standard \
// RUN: -no-pie %t/bti-plt.c -o %t.exe -Wl,-q -fuse-ld=lld \
// RUN: -Wl,-T,%t/link.ld -Wl,-z,force-bti
-// RUN: not llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck %s
+// RUN: llvm-bolt %t.exe -o %t.bolt | FileCheck %s
// CHECK: BOLT-INFO: binary is using BTI
-// CHECK: BOLT-ERROR: Cannot add BTI landing pad to ignored function abort at PLT
+// Checking PLT entries before running BOLT
+// RUN: llvm-objdump -d -j .plt %t.exe | FileCheck %s --check-prefix=CHECK-EXE
+// CHECK-EXE: <abort at plt>
+// CHECK-EXE-NEXT: adrp x16, 0x8230000
+// CHECK-EXE-NEXT: ldr x17, [x16, #0xaf0]
+// CHECK-EXE-NEXT: add x16, x16, #0xaf0
+// CHECK-EXE-NEXT: br x17
+// CHECK-EXE-NEXT: nop
+// CHECK-EXE-NEXT: nop
+
+// Checking PLT entries after patching them in BOLT
+// RUN: llvm-objdump -d -j .plt %t.bolt | FileCheck %s \
+// RUN: --check-prefix=CHECK-BOLT
+// CHECK-BOLT: <abort at plt>
+// CHECK-BOLT-NEXT: bti c
+// CHECK-BOLT-NEXT: adrp x16, 0x8230000
+// CHECK-BOLT-NEXT: ldr x17, [x16, #0xaf0]
+// CHECK-BOLT-NEXT: add x16, x16, #0xaf0
+// CHECK-BOLT-NEXT: br x17
+// CHECK-BOLT-NEXT: nop
+
+/*
#--- link.ld
SECTIONS {
>From 75730a8e8570338cc9018e8deaccbcb79d9958ea Mon Sep 17 00:00:00 2001
From: Gergely Balint <gergely.balint at arm.com>
Date: Tue, 6 Jan 2026 11:15:38 +0000
Subject: [PATCH 2/3] Extra comment
---
bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp | 2 ++
1 file changed, 2 insertions(+)
diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
index a56cf0fc6d9cb..9099167b7a08e 100644
--- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
+++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
@@ -1724,6 +1724,8 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
MCInst BTIInst;
createBTI(BTIInst, BTIKind::C);
NewPLTSeq.push_back(BTIInst);
+ // Only adding the instructions from the first BB (adrp, ldr, add, br) to
+ // NewPLTSeq.
for (auto II = FirstBBI->begin(); II != FirstBBI->end(); ++II) {
NewPLTSeq.push_back(*II);
}
>From 9f3b6e67fec75809c202c9bd1ff1c956a3eb0a07 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gergely=20B=C3=A1lint?= <gergely.balint at arm.com>
Date: Wed, 7 Jan 2026 19:12:12 +0100
Subject: [PATCH 3/3] Apply suggestions from code review
Co-authored-by: Paschalis Mpeis <paschalis.mpeis at arm.com>
---
bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
index 9099167b7a08e..7ba4750068872 100644
--- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
+++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
@@ -1715,7 +1715,7 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
if (!isNoop(*LastII)) {
errs() << "BOLT-ERROR: Cannot patch PLT entry "
<< PLTFunction.getPrintName()
- << " to have a BTI landing pad. Relink the workload using LLD.\n";
+ << " to have a BTI landing pad. Relink the binary using LLD.\n";
exit(1);
}
// If the PLT does not have a BTI, and it has nops, create a new instruction
@@ -1726,9 +1726,7 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
NewPLTSeq.push_back(BTIInst);
// Only adding the instructions from the first BB (adrp, ldr, add, br) to
// NewPLTSeq.
- for (auto II = FirstBBI->begin(); II != FirstBBI->end(); ++II) {
- NewPLTSeq.push_back(*II);
- }
+NewPLTSeq.insert(NewPLTSeq.end(), FirstBBI->begin(), FirstBBI->end());
BC.createInstructionPatch(PLTFunction.getAddress(), NewPLTSeq);
}
More information about the llvm-branch-commits
mailing list