Skip to content

Commit 43d0891

Browse files
authored
[BOLT] Fix handling of trailing entries in jump tables (#88444)
If a jump table has entries at the end that are a result of __builtin_unreachable() targets, BOLT can confuse them with function pointers. In such case, we should exclude these targets from the table as we risk incorrectly updating the function pointers. It is safe to exclude them as branching on such targets is considered an undefined behavior.
1 parent 00a4f09 commit 43d0891

File tree

2 files changed

+185
-5
lines changed

2 files changed

+185
-5
lines changed

bolt/lib/Core/BinaryContext.cpp

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,9 @@ bool BinaryContext::analyzeJumpTable(const uint64_t Address,
555555
const uint64_t NextJTAddress,
556556
JumpTable::AddressesType *EntriesAsAddress,
557557
bool *HasEntryInFragment) const {
558+
// Target address of __builtin_unreachable.
559+
const uint64_t UnreachableAddress = BF.getAddress() + BF.getSize();
560+
558561
// Is one of the targets __builtin_unreachable?
559562
bool HasUnreachable = false;
560563

@@ -564,9 +567,15 @@ bool BinaryContext::analyzeJumpTable(const uint64_t Address,
564567
// Number of targets other than __builtin_unreachable.
565568
uint64_t NumRealEntries = 0;
566569

567-
auto addEntryAddress = [&](uint64_t EntryAddress) {
568-
if (EntriesAsAddress)
569-
EntriesAsAddress->emplace_back(EntryAddress);
570+
// Size of the jump table without trailing __builtin_unreachable entries.
571+
size_t TrimmedSize = 0;
572+
573+
auto addEntryAddress = [&](uint64_t EntryAddress, bool Unreachable = false) {
574+
if (!EntriesAsAddress)
575+
return;
576+
EntriesAsAddress->emplace_back(EntryAddress);
577+
if (!Unreachable)
578+
TrimmedSize = EntriesAsAddress->size();
570579
};
571580

572581
ErrorOr<const BinarySection &> Section = getSectionForAddress(Address);
@@ -618,8 +627,8 @@ bool BinaryContext::analyzeJumpTable(const uint64_t Address,
618627
: *getPointerAtAddress(EntryAddress);
619628

620629
// __builtin_unreachable() case.
621-
if (Value == BF.getAddress() + BF.getSize()) {
622-
addEntryAddress(Value);
630+
if (Value == UnreachableAddress) {
631+
addEntryAddress(Value, /*Unreachable*/ true);
623632
HasUnreachable = true;
624633
LLVM_DEBUG(dbgs() << formatv("OK: {0:x} __builtin_unreachable\n", Value));
625634
continue;
@@ -673,6 +682,13 @@ bool BinaryContext::analyzeJumpTable(const uint64_t Address,
673682
addEntryAddress(Value);
674683
}
675684

685+
// Trim direct/normal jump table to exclude trailing unreachable entries that
686+
// can collide with a function address.
687+
if (Type == JumpTable::JTT_NORMAL && EntriesAsAddress &&
688+
TrimmedSize != EntriesAsAddress->size() &&
689+
getBinaryFunctionAtAddress(UnreachableAddress))
690+
EntriesAsAddress->resize(TrimmedSize);
691+
676692
// It's a jump table if the number of real entries is more than 1, or there's
677693
// one real entry and one or more special targets. If there are only multiple
678694
// special targets, then it's not a jump table.

bolt/test/runtime/X86/jt-confusion.s

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# REQUIRES: system-linux
2+
3+
# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %s -o %t.o
4+
# RUN: llvm-strip --strip-unneeded %t.o
5+
# RUN: %clang %cflags -no-pie -nostartfiles -nostdlib -lc %t.o -o %t.exe -Wl,-q
6+
7+
# RUN: llvm-bolt %t.exe -o %t.exe.bolt --relocs=1 --lite=0
8+
9+
# RUN: %t.exe.bolt
10+
11+
## Check that BOLT's jump table detection diffrentiates between
12+
## __builtin_unreachable() targets and function pointers.
13+
14+
## The test case was built from the following two source files and
15+
## modiffied for standalone build. main became _start, etc.
16+
## $ $(CC) a.c -O1 -S -o a.s
17+
## $ $(CC) b.c -O0 -S -o b.s
18+
19+
## a.c:
20+
21+
## typedef int (*fptr)(int);
22+
## void check_fptr(fptr, int);
23+
##
24+
## int foo(int a) {
25+
## check_fptr(foo, 0);
26+
## switch (a) {
27+
## default:
28+
## __builtin_unreachable();
29+
## case 0:
30+
## return 3;
31+
## case 1:
32+
## return 5;
33+
## case 2:
34+
## return 7;
35+
## case 3:
36+
## return 11;
37+
## case 4:
38+
## return 13;
39+
## case 5:
40+
## return 17;
41+
## }
42+
## return 0;
43+
## }
44+
##
45+
## int main(int argc) {
46+
## check_fptr(main, 1);
47+
## return foo(argc);
48+
## }
49+
##
50+
## const fptr funcs[2] = {foo, main};
51+
52+
## b.c.:
53+
54+
## typedef int (*fptr)(int);
55+
## extern const fptr funcs[2];
56+
##
57+
## #define assert(C) { if (!(C)) (*(unsigned long long *)0) = 0; }
58+
## void check_fptr(fptr f, int i) {
59+
## assert(f == funcs[i]);
60+
## }
61+
62+
63+
.text
64+
.globl foo
65+
.type foo, @function
66+
foo:
67+
.LFB0:
68+
.cfi_startproc
69+
pushq %rbx
70+
.cfi_def_cfa_offset 16
71+
.cfi_offset 3, -16
72+
movl %edi, %ebx
73+
movl $0, %esi
74+
movl $foo, %edi
75+
call check_fptr
76+
movl %ebx, %ebx
77+
jmp *.L4(,%rbx,8)
78+
.L8:
79+
movl $5, %eax
80+
jmp .L1
81+
.L7:
82+
movl $7, %eax
83+
jmp .L1
84+
.L6:
85+
movl $11, %eax
86+
jmp .L1
87+
.L5:
88+
movl $13, %eax
89+
jmp .L1
90+
.L3:
91+
movl $17, %eax
92+
jmp .L1
93+
.L10:
94+
movl $3, %eax
95+
.L1:
96+
popq %rbx
97+
.cfi_def_cfa_offset 8
98+
ret
99+
.cfi_endproc
100+
.LFE0:
101+
.size foo, .-foo
102+
.globl _start
103+
.type _start, @function
104+
_start:
105+
.LFB1:
106+
.cfi_startproc
107+
pushq %rbx
108+
.cfi_def_cfa_offset 16
109+
.cfi_offset 3, -16
110+
movl %edi, %ebx
111+
movl $1, %esi
112+
movl $_start, %edi
113+
call check_fptr
114+
movl $1, %edi
115+
call foo
116+
popq %rbx
117+
.cfi_def_cfa_offset 8
118+
callq exit@PLT
119+
.cfi_endproc
120+
.LFE1:
121+
.size _start, .-_start
122+
.globl check_fptr
123+
.type check_fptr, @function
124+
check_fptr:
125+
.LFB2:
126+
.cfi_startproc
127+
pushq %rbp
128+
.cfi_def_cfa_offset 16
129+
.cfi_offset 6, -16
130+
movq %rsp, %rbp
131+
.cfi_def_cfa_register 6
132+
movq %rdi, -8(%rbp)
133+
movl %esi, -12(%rbp)
134+
movl -12(%rbp), %eax
135+
cltq
136+
movq funcs(,%rax,8), %rax
137+
cmpq %rax, -8(%rbp)
138+
je .L33
139+
movl $0, %eax
140+
movq $0, (%rax)
141+
.L33:
142+
nop
143+
popq %rbp
144+
.cfi_def_cfa 7, 8
145+
ret
146+
.cfi_endproc
147+
148+
.section .rodata
149+
.align 8
150+
.align 4
151+
.L4:
152+
.quad .L10
153+
.quad .L8
154+
.quad .L7
155+
.quad .L6
156+
.quad .L5
157+
.quad .L3
158+
159+
.globl funcs
160+
.type funcs, @object
161+
.size funcs, 16
162+
funcs:
163+
.quad foo
164+
.quad _start

0 commit comments

Comments
 (0)