framehop/x86_64/instruction_analysis/epilogue.rs
1use super::super::unwind_rule::UnwindRuleX86_64;
2
3pub fn unwind_rule_from_detected_epilogue(
4 text_bytes: &[u8],
5 pc_offset: usize,
6) -> Option<UnwindRuleX86_64> {
7 let (slice_from_start, slice_to_end) = text_bytes.split_at(pc_offset);
8
9 let mut sp_offset_by_8 = 0;
10 let mut bp_offset_by_8 = None;
11 let mut bytes = slice_to_end;
12 loop {
13 if bytes.is_empty() {
14 return None;
15 }
16
17 // Detect ret
18 if bytes[0] == 0xc3 {
19 break;
20 }
21 // Detect jmp
22 if bytes[0] == 0xeb || bytes[0] == 0xe9 || bytes[0] == 0xff {
23 // This could be a tail call, or just a regular jump inside the current function.
24 // Ideally, we would check whether the jump target is inside this function.
25 // But this would require having an accurate idea of where the current function
26 // starts and ends.
27 // For now, we instead use the following heuristic: Any jmp that directly follows
28 // a `pop` instruction is treated as a tail call.
29 if sp_offset_by_8 != 0 {
30 // We have detected a pop in the previous loop iteration.
31 break;
32 }
33 // This must be the first iteration. Look backwards.
34 if let Some(potential_pop_byte) = slice_from_start.last() {
35 // Get the previous byte. We have no idea how long the previous instruction
36 // is, so we might be looking at a random last byte of a wider instruction.
37 // Let's just pray that this is not the case.
38 if potential_pop_byte & 0xf8 == 0x58 {
39 // Assuming we haven't just misinterpreted the last byte of a wider
40 // instruction, this is a `pop rXX`.
41 break;
42 }
43 }
44 return None;
45 }
46 // Detect pop rbp
47 if bytes[0] == 0x5d {
48 bp_offset_by_8 = Some(sp_offset_by_8 as i16);
49 sp_offset_by_8 += 1;
50 bytes = &bytes[1..];
51 continue;
52 }
53 // Detect pop rXX
54 if (0x58..=0x5f).contains(&bytes[0]) {
55 sp_offset_by_8 += 1;
56 bytes = &bytes[1..];
57 continue;
58 }
59 // Detect pop rXX with prefix
60 if bytes.len() >= 2 && bytes[0] & 0xfe == 0x40 && bytes[1] & 0xf8 == 0x58 {
61 sp_offset_by_8 += 1;
62 bytes = &bytes[2..];
63 continue;
64 }
65 // Unexpected instruction.
66 // This probably means that we weren't in an epilogue after all.
67 return None;
68 }
69
70 // We've found the return or the tail call.
71 let rule = if sp_offset_by_8 == 0 {
72 UnwindRuleX86_64::JustReturn
73 } else {
74 sp_offset_by_8 += 1; // Add one for popping the return address.
75 if let Some(bp_storage_offset_from_sp_by_8) = bp_offset_by_8 {
76 UnwindRuleX86_64::OffsetSpAndRestoreBp {
77 sp_offset_by_8,
78 bp_storage_offset_from_sp_by_8,
79 }
80 } else {
81 UnwindRuleX86_64::OffsetSp { sp_offset_by_8 }
82 }
83 };
84 Some(rule)
85}