[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