[llvm] [X86] Emit ENDBR at WinEH funclet entries under CET-IBT (PR #200333)
Justin Lebar via llvm-commits
llvm-commits at lists.llvm.org
Thu May 28 23:12:37 PDT 2026
https://github.com/jlebar created https://github.com/llvm/llvm-project/pull/200333
`X86IndirectBranchTracking` adds ENDBR to indirect-branch targets, including landing pads reached through an `EH_LABEL`. WinEH **catch/cleanup funclet entries** are also indirect call targets — the C++ EH runtime dispatcher transfers control into the funclet — but they carry no `EH_LABEL`, so the existing `isEHPad()` handling never inserts an ENDBR there.
With `-fcf-protection=branch`, a thrown exception therefore jumps into a funclet whose first instruction is not `endbr64`, and an IBT-enforcing CPU raises #CP on every C++ exception. (The parent function entry and the catchret target both get `endbr64` correctly; only the funclet entry is missed.)
Fix: handle `isEHFuncletEntry()` blocks explicitly, emitting ENDBR at the funclet entry. The analogous Itanium landing-pad case is already handled.
Found by code inspection (FuzzX). Draft pending review.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
>From c6000c98cf0577ee21ee884308d10548426f47f1 Mon Sep 17 00:00:00 2001
From: Justin Lebar <justin.lebar at gmail.com>
Date: Thu, 28 May 2026 23:12:26 -0700
Subject: [PATCH] [X86] Emit ENDBR at WinEH funclet entries under CET-IBT
X86IndirectBranchTracking adds ENDBR to indirect-branch targets, including
landing pads reached through an EH_LABEL. WinEH catch/cleanup funclet entries
are also indirect call targets -- the C++ EH runtime dispatcher transfers
control into the funclet -- but they carry no EH_LABEL, so the existing
isEHPad() handling never inserts an ENDBR there.
As a result, with -fcf-protection=branch a thrown exception jumps into a
funclet whose first instruction is not endbr64, and an IBT-enforcing CPU
raises #CP. Handle isEHFuncletEntry() blocks explicitly by emitting ENDBR at
the funclet entry.
---
.../Target/X86/X86IndirectBranchTracking.cpp | 6 ++++
.../X86/indirect-branch-tracking-wineh.ll | 30 +++++++++++++++++++
2 files changed, 36 insertions(+)
create mode 100644 llvm/test/CodeGen/X86/indirect-branch-tracking-wineh.ll
diff --git a/llvm/lib/Target/X86/X86IndirectBranchTracking.cpp b/llvm/lib/Target/X86/X86IndirectBranchTracking.cpp
index 6ccad26a890dd..863057666441d 100644
--- a/llvm/lib/Target/X86/X86IndirectBranchTracking.cpp
+++ b/llvm/lib/Target/X86/X86IndirectBranchTracking.cpp
@@ -171,6 +171,12 @@ static bool runIndirectBranchTracking(MachineFunction &MF) {
break;
}
}
+ } else if (MBB.isEHFuncletEntry()) {
+ // WinEH catch/cleanup funclet entries are indirect call targets of the
+ // EH runtime's dispatcher. They carry no EH_LABEL, so the isEHPad()
+ // handling below would miss them; emit ENDBR at the funclet entry so a
+ // CET-IBT-enforcing CPU does not #CP-fault when an exception is thrown.
+ Changed |= addENDBR(MBB, MBB.begin());
} else if (MBB.isEHPad()){
for (MachineBasicBlock::iterator I = MBB.begin(); I != MBB.end(); ++I) {
if (!I->isEHLabel())
diff --git a/llvm/test/CodeGen/X86/indirect-branch-tracking-wineh.ll b/llvm/test/CodeGen/X86/indirect-branch-tracking-wineh.ll
new file mode 100644
index 0000000000000..c65499eabcdce
--- /dev/null
+++ b/llvm/test/CodeGen/X86/indirect-branch-tracking-wineh.ll
@@ -0,0 +1,30 @@
+; RUN: llc -mtriple=x86_64-pc-windows-msvc < %s | FileCheck %s
+
+; Under CET-IBT (-fcf-protection=branch), WinEH catch/cleanup funclet entries
+; are indirect call targets of the C++ EH runtime dispatcher and must begin
+; with endbr64, otherwise throwing an exception #CP-faults on an IBT-enforcing
+; CPU.
+
+declare i32 @__CxxFrameHandler3(...)
+declare void @throws()
+
+define void @f() personality ptr @__CxxFrameHandler3 {
+; CHECK-LABEL: f:
+; CHECK: endbr64
+entry:
+ invoke void @throws() to label %cont unwind label %cd
+cont:
+ ret void
+cd:
+ %cs = catchswitch within none [label %c] unwind to caller
+c:
+ %cp = catchpad within %cs [ptr null, i32 64, ptr null]
+ catchret from %cp to label %cont
+}
+
+; The catch funclet entry must also carry endbr64.
+; CHECK: "?catch${{[^"]*}}":
+; CHECK: endbr64
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 8, !"cf-protection-branch", i32 1}
More information about the llvm-commits
mailing list