Skip to content

[AArch64][BTI] Mark EH landing pads as jump targets #149680

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

shashforge
Copy link
Contributor

@shashforge shashforge commented Jul 19, 2025

Clang wasn’t putting a BTI “jump” hint at the start of C++ catch/cleanup blocks.
With GCC 14’s runtime, those pads are entered via an indirect br, and BTI-enforced kernels kill the program with SIGILL.

Fix
Treat every isEHPad() block as a possible indirect-jump target in AArch64BranchTargets.cpp, so the existing pass adds bti j.


- if (AddrTaken || JumpTableTargets.count(&MBB))
+ if (AddrTaken || JumpTableTargets.count(&MBB) || MBB.isEHPad())
     CouldJump = true;

Impact

  • One 4-byte bti j in cold EH blocks, only when BTI is requested.
  • No change for builds without -mbranch-protection=bti.

Test

New lit test bti-ehpad.ll checks that the landing pad starts with bti.

No other code paths touched; full check-all passes.

Fixes #149267

@shashforge shashforge force-pushed the aarch64-bti-ehpad branch 2 times, most recently from 8ea51a3 to baf4849 Compare July 20, 2025 22:29
Copy link

github-actions bot commented Jul 20, 2025

✅ With the latest revision this PR passed the undef deprecator.

@shashforge shashforge changed the title [AArch64] Emit BTI j on EH landing pads () [AArch64][BTI] Mark EH landing pads as jump targets Jul 20, 2025
Landing pads reached by the unwinder are entered via 'br', so they must
start with BTI j when -mbranch-protection requests BTI.  Add isEHPad()
to the jump-target test.

Size/perf: +4 B per pad only when BTI is enabled.

Test: new CodeGen/AArch64/bti-ehpad.ll.
Signed-off-by: Shashi Shankar <[email protected]>
@shashforge shashforge marked this pull request as ready for review July 20, 2025 22:48
@llvmbot
Copy link
Member

llvmbot commented Jul 20, 2025

@llvm/pr-subscribers-backend-aarch64

Author: Shashi Shankar (shashforge)

Changes

Clang wasn’t putting a BTI “jump” hint at the start of C++ catch/cleanup blocks.
With GCC 14’s runtime, those pads are entered via an indirect br, and BTI-enforced kernels kill the program with SIGILL.

Fix
Treat every isEHPad() block as a possible indirect-jump target in AArch64BranchTargets.cpp, so the existing pass adds bti j.


- if (AddrTaken || JumpTableTargets.count(&amp;MBB))
+ if (AddrTaken || JumpTableTargets.count(&amp;MBB) || MBB.isEHPad())
     CouldJump = true;

Impact

  • One 4-byte bti j in cold EH blocks, only when BTI is requested.
  • No change for builds without -mbranch-protection=bti.

Test

New lit test bti-ehpad.ll checks that the landing pad starts with bti.

No other code paths touched; full check-all passes.

Fixes #149267


Full diff: https://github.com/llvm/llvm-project/pull/149680.diff

2 Files Affected:

  • (modified) llvm/lib/Target/AArch64/AArch64BranchTargets.cpp (+1-1)
  • (added) llvm/test/CodeGen/AArch64/bti-ehpad.ll (+26)
diff --git a/llvm/lib/Target/AArch64/AArch64BranchTargets.cpp b/llvm/lib/Target/AArch64/AArch64BranchTargets.cpp
index 3436dc9ef4521..1999195051aa5 100644
--- a/llvm/lib/Target/AArch64/AArch64BranchTargets.cpp
+++ b/llvm/lib/Target/AArch64/AArch64BranchTargets.cpp
@@ -100,7 +100,7 @@ bool AArch64BranchTargets::runOnMachineFunction(MachineFunction &MF) {
     // If the block itself is address-taken, it could be indirectly branched
     // to, but not called.
     if (MBB.isMachineBlockAddressTaken() || MBB.isIRBlockAddressTaken() ||
-        JumpTableTargets.count(&MBB))
+        JumpTableTargets.count(&MBB) || MBB.isEHPad())
       CouldJump = true;
 
     if (CouldCall || CouldJump) {
diff --git a/llvm/test/CodeGen/AArch64/bti-ehpad.ll b/llvm/test/CodeGen/AArch64/bti-ehpad.ll
new file mode 100644
index 0000000000000..70e43ff5c5d5d
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/bti-ehpad.ll
@@ -0,0 +1,26 @@
+; llvm/test/CodeGen/AArch64/bti-ehpad.ll
+; REQUIRES: aarch64-registered-target
+; RUN: llc -mtriple=aarch64-none-linux-gnu %s -o - | FileCheck %s
+
+declare i32 @__gxx_personality_v0(...)
+
+define void @test() #0 personality ptr @__gxx_personality_v0 {
+entry:
+  invoke void @may_throw()
+          to label %ret unwind label %lpad
+lpad:                               ; catch.dispatch
+  landingpad { ptr, i32 }
+          cleanup
+  ret void
+ret:
+  ret void
+}
+
+declare void @may_throw()
+
+attributes #0 = { "branch-target-enforcement"="true" }
+
+; Function needs both the architectural feature *and* the enforcement request.
+attributes #0 = { "branch-target-enforcement"="true" "target-features"="+bti" }
+
+; CHECK:      bti

@ozbenh
Copy link

ozbenh commented Jul 20, 2025

Thanks ! I'll will verify locally as well and report back

@ozbenh
Copy link

ozbenh commented Jul 21, 2025

So I tested a quick backport to llvm15 (default on AL2023) and it didn't work:

+--- a/llvm/lib/Target/AArch64/AArch64BranchTargets.cpp
++++ b/llvm/lib/Target/AArch64/AArch64BranchTargets.cpp
+@@ -91,7 +91,7 @@ bool AArch64BranchTargets::runOnMachineFunction(MachineFunction &MF) {
+ 
+     // If the block itself is address-taken, it could be indirectly branched
+     // to, but not called.
+-    if (MBB.hasAddressTaken() || JumpTableTargets.count(&MBB))
++    if (MBB.hasAddressTaken() || JumpTableTargets.count(&MBB) || MBB.isEHPad())
+       CouldJump = true;
+ 
+     if (CouldCall || CouldJump) {

That said I might have made a mistake, juggling too many plates today. I'll test on Fedora's llvm20 asap and check llvm15 again & will report.

@ozbenh
Copy link

ozbenh commented Jul 21, 2025

So it looks like (with clang15) the bti j is generated but when throwing, the unwinder is landing on the instruction after the newly inserted bti ... at least from a quick "next" with gdb (it's possible that gdb go that wrong, I haven't single stepped the whole way, I probably won't have time today).

@ozbenh
Copy link

ozbenh commented Jul 21, 2025

Same with llvm20 on Fedora:
Before:

.../...
  400b70:	97ffff8a 	bl	400998 <__cxa_throw@plt>
  400b74:	14000021 	b	400bf8 <_fini>
  400b78:	f9000be0 	str	x0, [sp, #16]
  400b7c:	2a0103e8 	mov	w8, w1
.../...

After:

  400b70:	97ffff8a 	bl	400998 <__cxa_throw@plt>
  400b74:	14000023 	b	400c00 <_fini>
  400b78:	d503249f 	bti	j
  400b7c:	f9000be0 	str	x0, [sp, #16]
  400b80:	2a0103e8 	mov	w8, w1

But it's still crashing. I've confirmed that libgcc branches past the bti instruction:

=> 0xfffff7c57350 <_Unwind_RaiseException+260>:	br	x6
(gdb) p/x $x6
$2 = 0x400b7c
(gdb) x/i $x6
   0x400b7c <main()+80>:	str	x0, [sp, #16]
(gdb) x/i $x6-8
   0x400b74 <main()+72>:	b	0x400c00 <_fini>
(gdb) 
   0x400b78 <main()+76>:	bti	j
(gdb) 
   0x400b7c <main()+80>:	str	x0, [sp, #16]

@ozbenh
Copy link

ozbenh commented Jul 21, 2025

Additionally with that patch, clang20 build on f42 fails with

********************
Failed Tests (1):
  Clang-Unit :: Interpreter/ExceptionTests/./ClangReplInterpreterExceptionTests/0/1

Is there another change elsewhere that also needs to be backported or is this fix simply incomplete ? Next I will try to build all of llvm from git, but that will require more time.

@efriedma-quic
Copy link
Collaborator

Probably the bti instruction is getting inserted before the EH_LABEL pseudo-instruction.

@ozbenh
Copy link

ozbenh commented Jul 22, 2025

So bear with me, I have no idea what I'm doing, never looked at LLVM code that deep, and this is probably completely wrong ... but hacking AArch64BranchTargets::addBTI() this way:

-  BuildMI(MBB, MBB.begin(), MBB.findDebugLoc(MBB.begin()),
-          TII->get(AArch64::HINT))
+  for (MBBI = MBB.begin() ; MBBI != MBB.end() && MBB.isEHPad(); MBBI++)
+         if (MBBI->getOpcode() == TargetOpcode::EH_LABEL) {
+                 MBBI++;
+                 break;
+         }
+  BuildMI(MBB, MBBI, MBB.findDebugLoc(MBBI), TII->get(AArch64::HINT))
       .addImm(HintNum);

Seems to do the trick :-)

I don't know how the HasWinCFI bit right before is affected if at all, again I have no idea what I'm doing, I mostly just grepped EH_LABEL and tried to make uniformed deductions :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

clang++ 20 Exceptions crash with (recent) libgcc and aarch64 BTI (Linux)
4 participants