[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
Mon Dec 22 04:06:06 PST 2025
https://github.com/bgergely0 created https://github.com/llvm/llvm-project/pull/173245
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.
>From c36af167ad206bda8c77507d420be729468b9f38 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] [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 {
More information about the llvm-branch-commits
mailing list