Skip to content

Commit 3a62cd8

Browse files
committed
Revisit handling of tail calls
It turns out that we have to be more conservative with tail call identification, as incorrectly identifying a block as the target of a tail call (instead of a branch) can cause other branch classifiers to fail if that block is the target of another jump. Ultimately, we will need to give up some tail call recognition (since they are in general indistinguishable from jumps), and instead only identify known call targets as tail call candidates. With additional global analysis we could do better. Fixes #294
1 parent f9e61a5 commit 3a62cd8

9 files changed

+72
-19
lines changed

base/src/Data/Macaw/Discovery.hs

+8-10
Original file line numberDiff line numberDiff line change
@@ -497,24 +497,22 @@ useExternalTargets bcc = do
497497
-- new classifiers (e.g., classifiers that can produce architecture-specific
498498
-- terminators).
499499
--
500-
-- Note that the direct jump classifier is last; this is to give the other
501-
-- classifiers a chance to run and match code. In particular, tail calls and
502-
-- direct local jumps are very difficult to distinguish from each other. Since
503-
-- we have to choose some order between them, we put the tail call classifier
504-
-- first so that it at least *could* fire without being completely subsumed by
505-
-- direct jumps. This means that some control flow transfers that look like
506-
-- direct jumps could instead be classified as tail calls. It would take some
507-
-- higher-level heuristics (e.g., live registers at the call site, locality) to
508-
-- distinguish the cases.
500+
-- Note that classifiers are an instance of 'Control.Monad.Alternative', so the
501+
-- order they are applied in matters. While several are non-overlapping, at
502+
-- least the order that the direct jump and tail call classifiers are applied in
503+
-- matters, as they look substantially the same to the analysis. Being too eager
504+
-- to flag jumps as tail calls classifies the jump targets as known function
505+
-- entry points, which can interfere with other classifiers later in the
506+
-- function.
509507
defaultClassifier :: BlockClassifier arch ids
510508
defaultClassifier = branchClassifier
511509
<|> noreturnCallClassifier
512510
<|> callClassifier
513511
<|> returnClassifier
514512
<|> jumpTableClassifier
515513
<|> pltStubClassifier
516-
<|> tailCallClassifier
517514
<|> directJumpClassifier
515+
<|> tailCallClassifier
518516

519517
-- | This parses a block that ended with a fetch and execute instruction.
520518
parseFetchAndExecute :: forall arch ids

base/src/Data/Macaw/Discovery/Classifier.hs

+20-2
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,21 @@ returnClassifier = classifierName "Return" $ do
292292
}
293293

294294
-- | Classifies jumps to concrete addresses as unconditional jumps. Note that
295-
-- this logic is substantially similar to the 'callClassifier'; as such, this
296-
-- classifier should always be applied *after* the 'callClassifier'.
295+
-- this logic is substantially similar to the 'tailCallClassifier' in cases
296+
-- where the function does not establish a stack frame (i.e., leaf functions).
297+
--
298+
-- Note that known call targets are not eligible to be intra-procedural jump
299+
-- targets (see 'classifyDirectJump'). This means that we need to conservatively
300+
-- prefer to mis-classify terminators as jumps rather than tail calls. The
301+
-- downside of this choice is that code that could be considered a tail-called
302+
-- function may be duplicated in some cases (i.e., considered part of multiple
303+
-- functions).
304+
--
305+
-- The alternative interpretation (eagerly preferring tail calls) can cause a
306+
-- section of a function to be marked as a tail-called function, thereby
307+
-- blocking the 'directJumpClassifier' or the 'branchClassifier' from
308+
-- recognizing the "callee" as an intra-procedural jump. This results in
309+
-- classification failures that we don't have any mitigations for.
297310
directJumpClassifier :: BlockClassifier arch ids
298311
directJumpClassifier = classifierName "Jump" $ do
299312
bcc <- CMR.ask
@@ -380,6 +393,11 @@ noreturnCallClassifier = classifierName "no return call" $ do
380393
--
381394
-- The current heuristic is that the target looks like a call, except the stack
382395
-- height in the caller is 0.
396+
--
397+
-- Note that, in leaf functions (i.e., with no stack usage), tail calls and
398+
-- jumps look substantially similar. We typically apply the jump classifier
399+
-- first to prefer them, which means that we very rarely recognize tail calls in
400+
-- leaf functions.
383401
tailCallClassifier :: BlockClassifier arch ids
384402
tailCallClassifier = classifierName "Tail call" $ do
385403
bcc <- CMR.ask
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
R { funcs = [ (0x10054, [ (0x10054, 32), (0x10074, 12), (0x10080, 4) ])
2-
, (0x10084, [ (0x10084, 24), (0x1009c, 24) ])
1+
R { funcs = [ (0x10054, [ (0x10054, 32), (0x10074, 12), (0x10080, 4), (0x10084, 24), (0x1009c, 24) ])
32
]
43
, ignoreBlocks = []
54
}

x86/tests/x64/inner_loop_31.exe

4.51 KB
Binary file not shown.
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
R { funcs = [(Addr 0 0x401000, [(Addr 0 0x401000, 34), (Addr 0 0x401022, 76), (Addr 0 0x40106e, 31)])]
2+
, ignoreBlocks = []
3+
}

x86/tests/x64/inner_loop_31.s

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
main:
2+
endbr64
3+
movabs $0x7fffffff80000000,%rcx
4+
movabs $0x800000007fffffff,%r13
5+
movabs $0x7fffffff7fffffff,%r15
6+
target:
7+
cmp %r10,%r8
8+
mov %r8,%rax
9+
mov %r10,%rbx
10+
mov %rcx,%rbp
11+
mov %r13,%r14
12+
cmovb %r10,%r8
13+
cmovb %rax,%r10
14+
cmovb %r13,%rcx
15+
cmovb %rbp,%r13
16+
sub %r10,%r8
17+
sub %r13,%rcx
18+
add %r15,%rcx
19+
test $0x1,%rax
20+
cmove %rax,%r8
21+
cmove %rbx,%r10
22+
cmove %rbp,%rcx
23+
cmove %r14,%r13
24+
shr %r8
25+
add %r13,%r13
26+
sub %r15,%r13
27+
sub $0x1,%edi
28+
jne target
29+
shr $0x20,%r15
30+
mov %ecx,%edx
31+
mov %r13d,%r12d
32+
shr $0x20,%rcx
33+
shr $0x20,%r13
34+
sub %r15,%rdx
35+
sub %r15,%rcx
36+
sub %r15,%r12
37+
sub %r15,%r13
38+
repz retq
+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
R { funcs = [ (Addr 0 0x401000, [(Addr 0 0x401000,33)])
2-
, (Addr 0 0x401021, [(Addr 0 0x401021, 2)])
1+
R { funcs = [ (Addr 0 0x401000, [(Addr 0 0x401000,33), (Addr 0 0x401021, 2)])
32
]
43
, ignoreBlocks = []
54
}

x86/tests/x64/test-plt.exe.expected

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ R { funcs =
88
, (Addr 1 0x610, [(Addr 1 0x610,27),(Addr 1 0x62b,12),(Addr 1 0x637,3),(Addr 1 0x640,2)])
99
, (Addr 1 0x650, [(Addr 1 0x650,40),(Addr 1 0x678,12),(Addr 1 0x684,3),(Addr 1 0x690,2)])
1010
, (Addr 1 0x6a0, [(Addr 1 0x6a0,9),(Addr 1 0x6a9,14),(Addr 1 0x6b7,12),(Addr 1 0x6c3,5),(Addr 1 0x6c8,8), (Addr 1 0x6d0,2)])
11-
, (Addr 1 0x6d0, [(Addr 1 0x6d0,2)])
1211
, (Addr 1 0x6e0, [(Addr 1 0x6e0,13), (Addr 1 0x6ed,5), (Addr 1 0x6f8, 12), (Addr 1 0x704, 6), (Addr 1 0x70a, 6)])
1312
, (Addr 1 0x710, [(Addr 1 0x710,13),(Addr 1 0x71d,4)])
1413
, (Addr 1 0x730, [(Addr 1 0x730,49),(Addr 1 0x761,5),(Addr 1 0x766,10),(Addr 1 0x770,13),(Addr 1
+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
R { funcs = [ (Addr 1 0x2c0, [(Addr 1 0x2c0, 7)])
22
, (Addr 1 0x2d0, [(Addr 1 0x2d0, 11)])
3-
, (Addr 1 0x2e0, [(Addr 1 0x2e0, 11), (Addr 1 0x2eb, 16)])
4-
, (Addr 1 0x2fb, [(Addr 1 0x2fb, 1)])
3+
, (Addr 1 0x2e0, [(Addr 1 0x2e0, 11), (Addr 1 0x2eb, 16), (Addr 1 0x2fb, 1)])
54
]
65
, ignoreBlocks = []
76
}

0 commit comments

Comments
 (0)