[lld] [LLD/AArch64] BTI link-time relaxation: NOP redundant `bti c` for internal functions (PR #187871)

Junjie Peng via llvm-commits llvm-commits at lists.llvm.org
Mon Apr 13 01:00:20 PDT 2026


https://github.com/HacoK updated https://github.com/llvm/llvm-project/pull/187871

>From b2e344774465ac5717888070be4cac9fba0f576c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot at users.noreply.github.com>
Date: Sat, 21 Mar 2026 15:12:46 +0000
Subject: [PATCH 1/3] Initial plan


>From 11262a0cdc575d887b7e3591016f0e266420a156 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot at users.noreply.github.com>
Date: Sat, 21 Mar 2026 16:04:20 +0000
Subject: [PATCH 2/3] Implement BTI link-time relaxation: NOP bti c for
 non-global, non-address-taken functions

Co-authored-by: HacoK <39146962+HacoK at users.noreply.github.com>
Agent-Logs-Url: https://github.com/HacoK/llvm-project/sessions/fb4b478d-d871-4245-9693-c5c384a39cc1
---
 lld/ELF/Arch/AArch64.cpp | 127 +++++++++++++++++++++++++++++++++++++++
 lld/ELF/Relocations.cpp  |   3 +
 lld/ELF/Target.h         |   1 +
 3 files changed, 131 insertions(+)

diff --git a/lld/ELF/Arch/AArch64.cpp b/lld/ELF/Arch/AArch64.cpp
index f85a3f48f2183..f595f4b8ab098 100644
--- a/lld/ELF/Arch/AArch64.cpp
+++ b/lld/ELF/Arch/AArch64.cpp
@@ -1147,6 +1147,133 @@ void AArch64::applyBranchToBranchOpt() const {
                              redirectControlTransferRelocations);
 }
 
+// Returns true if the AArch64 relocation type is a direct branch/call that
+// does not constitute an address-taken use of a function symbol.  A function
+// reached only via such relocations never has its address stored anywhere and
+// therefore does not need a BTI landing pad.
+static bool isAArch64DirectBranchReloc(RelType type) {
+  switch (type) {
+  case R_AARCH64_CALL26:
+  case R_AARCH64_JUMP26:
+  case R_AARCH64_CONDBR19:
+  case R_AARCH64_TSTBR14:
+  case R_AARCH64_PLT32:
+    return true;
+  default:
+    return false;
+  }
+}
+
+// BTI link-time relaxation: replace a redundant "bti c" instruction at the
+// entry of a function with a NOP when the linker can prove that the function
+// is never the target of an indirect branch.
+//
+// The compiler conservatively adds "bti c" to every function with external
+// linkage because it cannot know at compile time whether the function will be
+// called indirectly.  The linker, however, has whole-program visibility and
+// can determine which functions are only ever reached via direct BL/B
+// branches.  For those functions the "bti c" is redundant and can be replaced
+// with a NOP, reducing code size without compromising security.
+//
+// A function's "bti c" is KEPT when any of the following hold:
+//   isGlobal(sym):       preemptible (dynamic interposition), exported to
+//                        .dynsym, or in a shared library where all globally
+//                        visible symbols are potentially called indirectly.
+//   isAddressTaken(sym): has a PLT entry (PLT uses an indirect branch),
+//                        has a GOT entry (address can be loaded and used as a
+//                        function pointer), is an ifunc (always uses IPLT),
+//                        or is referenced by a non-direct-branch relocation
+//                        (address stored in data or computed in code for use
+//                        as a function pointer).
+void elf::relaxAArch64BTI(Ctx &ctx) {
+  if (!ctx.arg.relax)
+    return;
+  if (!(ctx.arg.andFeatures & GNU_PROPERTY_AARCH64_FEATURE_1_BTI))
+    return;
+
+  // Step 1: Scan all processed relocations across all input sections and
+  // collect the set of function symbols whose address is taken via a
+  // non-direct-branch relocation.  Such symbols cannot have their "bti c"
+  // removed because their address may be loaded and used to perform an
+  // indirect call (BLR) at runtime.
+  DenseSet<const Symbol *> addrTaken;
+  for (InputSectionBase *sec : ctx.inputSections) {
+    if (!(sec->flags & SHF_ALLOC) || sec == &InputSection::discarded)
+      continue;
+    for (const Relocation &rel : sec->relocs()) {
+      if (!rel.sym || !rel.sym->isDefined())
+        continue;
+      if (rel.sym->type != STT_FUNC && rel.sym->type != STT_GNU_IFUNC)
+        continue;
+      // A direct branch relocation does not expose the function's address;
+      // any other relocation type potentially does.
+      if (!isAArch64DirectBranchReloc(rel.type))
+        addrTaken.insert(rel.sym);
+    }
+  }
+
+  // Step 2: Collect (section, offset) pairs where a "bti c" instruction
+  // should be replaced with a NOP.
+  DenseMap<InputSection *, SmallVector<uint64_t, 4>> toNOP;
+
+  auto tryRelax = [&](Symbol &sym) {
+    auto *d = dyn_cast<Defined>(&sym);
+    if (!d)
+      return;
+
+    // isGlobal(sym): keep "bti c" for symbols visible outside this link unit.
+    if (d->isPreemptible || d->isExported)
+      return;
+    // In a shared library every non-hidden global symbol is reachable from
+    // outside and may be called indirectly.
+    if (ctx.arg.shared)
+      return;
+
+    // isAddressTaken(sym): keep "bti c" if the address is used indirectly.
+    if (d->isInPlt(ctx) || d->isInGot(ctx) || d->isGnuIFunc())
+      return;
+    if (addrTaken.count(d))
+      return;
+
+    auto *isec = dyn_cast_or_null<InputSection>(d->section);
+    if (!isec || !(isec->flags & SHF_EXECINSTR))
+      return;
+
+    uint64_t off = d->value;
+    if (off + 4 > isec->getSize())
+      return;
+
+    const uint8_t *buf = isec->content().begin();
+    if (!buf || read32le(buf + off) != 0xd503245f) // bti c
+      return;
+
+    toNOP[isec].push_back(off);
+  };
+
+  for (Symbol *sym : ctx.symtab->getSymbols())
+    tryRelax(*sym);
+
+  // Local symbols may also carry a "bti c" when their address was taken in
+  // the source.  The addrTaken set (built above) already accounts for this:
+  // if a local symbol's address is referenced by a non-branch relocation it
+  // will be in addrTaken and will not be relaxed.
+  for (ELFFileBase *file : ctx.objectFiles)
+    for (Symbol *sym : file->getLocalSymbols())
+      tryRelax(*sym);
+
+  // Step 3: For each section that needs modification, allocate a fresh
+  // mutable copy of the content, patch the "bti c" instructions to NOPs,
+  // and redirect the section to use the new buffer.
+  for (auto &[isec, offsets] : toNOP) {
+    ArrayRef<uint8_t> orig = isec->content();
+    uint8_t *newBuf = makeThreadLocalN<uint8_t>(orig.size());
+    memcpy(newBuf, orig.data(), orig.size());
+    for (uint64_t off : offsets)
+      write32le(newBuf + off, 0xd503201f); // nop
+    isec->content_ = newBuf;
+  }
+}
+
 // AArch64 may use security features in variant PLT sequences. These are:
 // Pointer Authentication (PAC), introduced in armv8.3-a and Branch Target
 // Indicator (BTI) introduced in armv8.5-a. The additional instructions used
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index ee6b14f2798ed..6da8e23981a86 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -1431,6 +1431,9 @@ void elf::postScanRelocations(Ctx &ctx) {
 
   if (ctx.arg.branchToBranch)
     ctx.target->applyBranchToBranchOpt();
+
+  if (ctx.arg.emachine == EM_AARCH64)
+    relaxAArch64BTI(ctx);
 }
 
 static bool mergeCmp(const InputSection *a, const InputSection *b) {
diff --git a/lld/ELF/Target.h b/lld/ELF/Target.h
index 8fa8cd13f1b67..c95d7ef39d1cf 100644
--- a/lld/ELF/Target.h
+++ b/lld/ELF/Target.h
@@ -259,6 +259,7 @@ void addPPC64SaveRestore(Ctx &);
 uint64_t getPPC64TocBase(Ctx &ctx);
 uint64_t getAArch64Page(uint64_t expr);
 bool isAArch64BTILandingPad(Ctx &, Symbol &s, int64_t a);
+void relaxAArch64BTI(Ctx &);
 template <typename ELFT> void writeARMCmseImportLib(Ctx &);
 uint64_t getLoongArchPageDelta(uint64_t dest, uint64_t pc, RelType type);
 void riscvFinalizeRelax(int passes);

>From 7b28fa2aa3788827482d58e3623322d20c168b29 Mon Sep 17 00:00:00 2001
From: Junjie Peng <877728156 at qq.com>
Date: Mon, 13 Apr 2026 16:00:10 +0800
Subject: [PATCH 3/3] Add include for SymbolTable in AArch64.cpp

---
 lld/ELF/Arch/AArch64.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lld/ELF/Arch/AArch64.cpp b/lld/ELF/Arch/AArch64.cpp
index f595f4b8ab098..e59aa649dc7bd 100644
--- a/lld/ELF/Arch/AArch64.cpp
+++ b/lld/ELF/Arch/AArch64.cpp
@@ -10,6 +10,7 @@
 #include "OutputSections.h"
 #include "RelocScan.h"
 #include "Symbols.h"
+#include "SymbolTable.h"
 #include "SyntheticSections.h"
 #include "Target.h"
 #include "TargetImpl.h"



More information about the llvm-commits mailing list