Skip to content

Commit 81360ec

Browse files
authored
[CFI] Fix Direct Call Issues in CFI Dispatch Table (#69663)
I discovered two issues for when a CFI dispatch table entry is used as a direct call. # Inlining There is the possibility that the dispatch table entry contains only a single function pointer: ``` ; Function Attrs: naked nocf_check define private void @.cfi.jumptable() #6 align 8 { entry: call void asm sideeffect "jmp ${0:c}@plt\0Aint3\0Aint3\0Aint3\0A", "s"(ptr @_Z7throw_ei) unreachable } ``` If this function is inlined, the unreachable follows and ruins the containing function. # Exception Handling The dispatch table is always marked NoUnwind. This is fine if the entries are never used directly, but if a direct call is used which the containing function expects to throw, it will no longer throw and the exception handling code will be lost.
1 parent e1fa2fe commit 81360ec

File tree

8 files changed

+446
-24
lines changed

8 files changed

+446
-24
lines changed

llvm/lib/Transforms/IPO/LowerTypeTests.cpp

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,9 +1467,19 @@ void LowerTypeTestsModule::createJumpTable(
14671467
SmallVector<Value *, 16> AsmArgs;
14681468
AsmArgs.reserve(Functions.size() * 2);
14691469

1470-
for (GlobalTypeMember *GTM : Functions)
1470+
// Check if all entries have the NoUnwind attribute.
1471+
// If all entries have it, we can safely mark the
1472+
// cfi.jumptable as NoUnwind, otherwise, direct calls
1473+
// to the jump table will not handle exceptions properly
1474+
bool areAllEntriesNounwind = true;
1475+
for (GlobalTypeMember *GTM : Functions) {
1476+
if (!llvm::cast<llvm::Function>(GTM->getGlobal())
1477+
->hasFnAttribute(llvm::Attribute::NoUnwind)) {
1478+
areAllEntriesNounwind = false;
1479+
}
14711480
createJumpTableEntry(AsmOS, ConstraintOS, JumpTableArch, AsmArgs,
14721481
cast<Function>(GTM->getGlobal()));
1482+
}
14731483

14741484
// Align the whole table by entry size.
14751485
F->setAlignment(Align(getJumpTableEntrySize()));
@@ -1512,8 +1522,13 @@ void LowerTypeTestsModule::createJumpTable(
15121522
// -fcf-protection=.
15131523
if (JumpTableArch == Triple::x86 || JumpTableArch == Triple::x86_64)
15141524
F->addFnAttr(Attribute::NoCfCheck);
1515-
// Make sure we don't emit .eh_frame for this function.
1516-
F->addFnAttr(Attribute::NoUnwind);
1525+
1526+
// Make sure we don't emit .eh_frame for this function if it isn't needed.
1527+
if (areAllEntriesNounwind)
1528+
F->addFnAttr(Attribute::NoUnwind);
1529+
1530+
// Make sure we do not inline any calls to the cfi.jumptable.
1531+
F->addFnAttr(Attribute::NoInline);
15171532

15181533
BasicBlock *BB = BasicBlock::Create(M.getContext(), "entry", F);
15191534
IRBuilder<> IRB(BB);

llvm/test/Transforms/LowerTypeTests/aarch64-jumptable.ll

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --include-generated-funcs --version 2
12
; RUN: opt -S -passes=lowertypetests -mtriple=aarch64-unknown-linux-gnu %s | FileCheck --check-prefixes=AARCH64 %s
23

34
; Test for the jump table generation with branch protection on AArch64
@@ -6,7 +7,6 @@ target datalayout = "e-p:64:64"
67

78
@0 = private unnamed_addr constant [2 x ptr] [ptr @f, ptr @g], align 16
89

9-
; AARCH64: @f = alias void (), ptr @[[JT:.*]]
1010

1111
define void @f() !type !0 {
1212
ret void
@@ -29,11 +29,30 @@ define i1 @foo(ptr %p) {
2929

3030
!1 = !{i32 4, !"branch-target-enforcement", i32 1}
3131

32-
; AARCH64: define private void @[[JT]]() #[[ATTR:.*]] align 8 {
3332

34-
; AARCH64: bti c
35-
; AARCH64-SAME: b $0
36-
; AARCH64-SAME: bti c
37-
; AARCH64-SAME: b $1
38-
39-
; AARCH64: attributes #[[ATTR]] = { naked nounwind "branch-target-enforcement"="false" "sign-return-address"="none"
33+
; AARCH64-LABEL: define hidden void @f.cfi() !type !1 {
34+
; AARCH64-NEXT: ret void
35+
;
36+
;
37+
; AARCH64-LABEL: define internal void @g.cfi() !type !1 {
38+
; AARCH64-NEXT: ret void
39+
;
40+
;
41+
; AARCH64-LABEL: define i1 @foo
42+
; AARCH64-SAME: (ptr [[P:%.*]]) {
43+
; AARCH64-NEXT: [[TMP1:%.*]] = ptrtoint ptr [[P]] to i64
44+
; AARCH64-NEXT: [[TMP2:%.*]] = sub i64 [[TMP1]], ptrtoint (ptr @.cfi.jumptable to i64)
45+
; AARCH64-NEXT: [[TMP3:%.*]] = lshr i64 [[TMP2]], 3
46+
; AARCH64-NEXT: [[TMP4:%.*]] = shl i64 [[TMP2]], 61
47+
; AARCH64-NEXT: [[TMP5:%.*]] = or i64 [[TMP3]], [[TMP4]]
48+
; AARCH64-NEXT: [[TMP6:%.*]] = icmp ule i64 [[TMP5]], 1
49+
; AARCH64-NEXT: ret i1 [[TMP6]]
50+
;
51+
;
52+
; AARCH64: Function Attrs: naked noinline
53+
; AARCH64-LABEL: define private void @.cfi.jumptable
54+
; AARCH64-SAME: () #[[ATTR1:[0-9]+]] align 8 {
55+
; AARCH64-NEXT: entry:
56+
; AARCH64-NEXT: call void asm sideeffect "bti c\0Ab $0\0Abti c\0Ab $1\0A", "s,s"(ptr @f.cfi, ptr @g.cfi)
57+
; AARCH64-NEXT: unreachable
58+
;
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --include-generated-funcs --version 2
2+
; RUN: opt < %s -passes='lowertypetests,default<O3>' -S | FileCheck %s
3+
4+
; This IR is based of the following C++
5+
; which was compiled with:
6+
; clang -cc1 -fexceptions -fcxx-exceptions \
7+
; -std=c++11 -internal-isystem llvm-project/build/lib/clang/17/include \
8+
; -nostdsysteminc -triple x86_64-unknown-linux -fsanitize=cfi-icall \
9+
; -fsanitize-cfi-cross-dso -fsanitize-trap=cfi-icall -Oz -S -emit-llvm
10+
; int (*catch_ptr)(int);
11+
; int nothrow_e (int num) noexcept {
12+
; if (num) return 1;
13+
; return 0;
14+
; }
15+
; int call_catch(int num) {
16+
; catch_ptr = &nothrow_e;
17+
; return catch_ptr(num);
18+
; }
19+
20+
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
21+
target triple = "x86_64-unknown-linux"
22+
23+
@catch_ptr = local_unnamed_addr global ptr null, align 8
24+
@llvm.used = appending global [1 x ptr] [ptr @__cfi_check_fail], section "llvm.metadata"
25+
26+
; Function Attrs: minsize mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none)
27+
define dso_local noundef i32 @_Z9nothrow_ei(i32 noundef %num) #0 !type !4 !type !5 !type !6 {
28+
entry:
29+
%tobool.not = icmp ne i32 %num, 0
30+
%. = zext i1 %tobool.not to i32
31+
ret i32 %.
32+
}
33+
34+
; Function Attrs: minsize mustprogress nounwind optsize
35+
define dso_local noundef i32 @_Z10call_catchi(i32 noundef %num) local_unnamed_addr #1 !type !4 !type !5 !type !6 {
36+
entry:
37+
store ptr @_Z9nothrow_ei, ptr @catch_ptr, align 8, !tbaa !7
38+
%0 = tail call i1 @llvm.type.test(ptr nonnull @_Z9nothrow_ei, metadata !"_ZTSFiiE"), !nosanitize !11
39+
br i1 %0, label %cfi.cont, label %cfi.slowpath, !prof !12, !nosanitize !11
40+
41+
cfi.slowpath: ; preds = %entry
42+
tail call void @__cfi_slowpath(i64 5174074510188755522, ptr nonnull @_Z9nothrow_ei) #5, !nosanitize !11
43+
br label %cfi.cont, !nosanitize !11
44+
45+
cfi.cont: ; preds = %cfi.slowpath, %entry
46+
%tobool.not.i = icmp ne i32 %num, 0
47+
%..i = zext i1 %tobool.not.i to i32
48+
ret i32 %..i
49+
}
50+
51+
; Function Attrs: mustprogress nocallback nofree nosync nounwind speculatable willreturn memory(none)
52+
declare i1 @llvm.type.test(ptr, metadata) #2
53+
54+
declare void @__cfi_slowpath(i64, ptr) local_unnamed_addr
55+
56+
; Function Attrs: minsize optsize
57+
define weak_odr hidden void @__cfi_check_fail(ptr noundef %0, ptr noundef %1) #3 {
58+
entry:
59+
%.not = icmp eq ptr %0, null, !nosanitize !11
60+
br i1 %.not, label %trap, label %cont, !nosanitize !11
61+
62+
trap: ; preds = %cont, %entry
63+
tail call void @llvm.ubsantrap(i8 2) #6, !nosanitize !11
64+
unreachable, !nosanitize !11
65+
66+
cont: ; preds = %entry
67+
%2 = load i8, ptr %0, align 4, !nosanitize !11
68+
%switch = icmp ult i8 %2, 5
69+
br i1 %switch, label %trap, label %cont6
70+
71+
cont6: ; preds = %cont
72+
ret void, !nosanitize !11
73+
}
74+
75+
; Function Attrs: cold noreturn nounwind
76+
declare void @llvm.ubsantrap(i8 immarg) #4
77+
78+
define weak void @__cfi_check(i64 %0, ptr %1, ptr %2) local_unnamed_addr {
79+
entry:
80+
tail call void @llvm.trap()
81+
unreachable
82+
}
83+
84+
; Function Attrs: cold noreturn nounwind
85+
declare void @llvm.trap() #4
86+
87+
attributes #0 = { minsize mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none) "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
88+
attributes #1 = { minsize mustprogress nounwind optsize "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
89+
attributes #2 = { mustprogress nocallback nofree nosync nounwind speculatable willreturn memory(none) }
90+
attributes #3 = { minsize optsize "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
91+
attributes #4 = { cold noreturn nounwind }
92+
attributes #5 = { nounwind }
93+
attributes #6 = { noreturn nounwind }
94+
95+
!llvm.module.flags = !{!0, !1, !2}
96+
!llvm.ident = !{!3}
97+
98+
!0 = !{i32 1, !"wchar_size", i32 4}
99+
!1 = !{i32 4, !"Cross-DSO CFI", i32 1}
100+
!2 = !{i32 4, !"CFI Canonical Jump Tables", i32 0}
101+
!3 = !{!"clang version 17.0.2"}
102+
!4 = !{i64 0, !"_ZTSFiiE"}
103+
!5 = !{i64 0, !"_ZTSFiiE.generalized"}
104+
!6 = !{i64 0, i64 5174074510188755522}
105+
!7 = !{!8, !8, i64 0}
106+
!8 = !{!"any pointer", !9, i64 0}
107+
!9 = !{!"omnipotent char", !10, i64 0}
108+
!10 = !{!"Simple C++ TBAA"}
109+
!11 = !{}
110+
!12 = !{!"branch_weights", i32 1048575, i32 1}
111+
; CHECK: Function Attrs: minsize mustprogress nofree norecurse nosync nounwind optsize willreturn memory(none)
112+
; CHECK-LABEL: define dso_local noundef i32 @_Z9nothrow_ei
113+
; CHECK-SAME: (i32 noundef [[NUM:%.*]]) #[[ATTR0:[0-9]+]] !type !4 !type !5 !type !6 {
114+
; CHECK-NEXT: entry:
115+
; CHECK-NEXT: [[TOBOOL_NOT:%.*]] = icmp ne i32 [[NUM]], 0
116+
; CHECK-NEXT: [[DOT:%.*]] = zext i1 [[TOBOOL_NOT]] to i32
117+
; CHECK-NEXT: ret i32 [[DOT]]
118+
;
119+
;
120+
; CHECK: Function Attrs: minsize mustprogress nofree norecurse nosync nounwind optsize willreturn memory(write, argmem: none, inaccessiblemem: none)
121+
; CHECK-LABEL: define dso_local noundef i32 @_Z10call_catchi
122+
; CHECK-SAME: (i32 noundef [[NUM:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] !type !4 !type !5 !type !6 {
123+
; CHECK-NEXT: entry:
124+
; CHECK-NEXT: store ptr @_Z9nothrow_ei.cfi_jt, ptr @catch_ptr, align 8, !tbaa [[TBAA7:![0-9]+]]
125+
; CHECK-NEXT: [[TOBOOL_NOT_I:%.*]] = icmp ne i32 [[NUM]], 0
126+
; CHECK-NEXT: [[DOT_I:%.*]] = zext i1 [[TOBOOL_NOT_I]] to i32
127+
; CHECK-NEXT: ret i32 [[DOT_I]]
128+
;
129+
;
130+
; CHECK: Function Attrs: minsize optsize
131+
; CHECK-LABEL: define weak_odr hidden void @__cfi_check_fail
132+
; CHECK-SAME: (ptr noundef [[TMP0:%.*]], ptr noundef [[TMP1:%.*]]) #[[ATTR2:[0-9]+]] {
133+
; CHECK-NEXT: entry:
134+
; CHECK-NEXT: [[DOTNOT:%.*]] = icmp eq ptr [[TMP0]], null, !nosanitize !11
135+
; CHECK-NEXT: br i1 [[DOTNOT]], label [[TRAP:%.*]], label [[CONT:%.*]], !nosanitize !11
136+
; CHECK: trap:
137+
; CHECK-NEXT: tail call void @llvm.ubsantrap(i8 2) #[[ATTR5:[0-9]+]], !nosanitize !11
138+
; CHECK-NEXT: unreachable, !nosanitize !11
139+
; CHECK: cont:
140+
; CHECK-NEXT: [[TMP2:%.*]] = load i8, ptr [[TMP0]], align 4, !nosanitize !11
141+
; CHECK-NEXT: [[SWITCH:%.*]] = icmp ult i8 [[TMP2]], 5
142+
; CHECK-NEXT: br i1 [[SWITCH]], label [[TRAP]], label [[CONT6:%.*]]
143+
; CHECK: cont6:
144+
; CHECK-NEXT: ret void, !nosanitize !11
145+
;
146+
;
147+
; CHECK-LABEL: define weak void @__cfi_check
148+
; CHECK-SAME: (i64 [[TMP0:%.*]], ptr [[TMP1:%.*]], ptr [[TMP2:%.*]]) local_unnamed_addr {
149+
; CHECK-NEXT: entry:
150+
; CHECK-NEXT: tail call void @llvm.trap()
151+
; CHECK-NEXT: unreachable
152+
;
153+
;
154+
; CHECK: Function Attrs: naked nocf_check noinline nounwind
155+
; CHECK-LABEL: define internal void @_Z9nothrow_ei.cfi_jt
156+
; CHECK-SAME: () #[[ATTR4:[0-9]+]] align 8 {
157+
; CHECK-NEXT: entry:
158+
; CHECK-NEXT: tail call void asm sideeffect "jmp ${0:c}@plt\0Aint3\0Aint3\0Aint3\0A", "s"(ptr nonnull @_Z9nothrow_ei) #[[ATTR6:[0-9]+]]
159+
; CHECK-NEXT: unreachable
160+
;

0 commit comments

Comments
 (0)