framehop/x86_64/instruction_analysis/prologue.rs
1use super::super::unwind_rule::UnwindRuleX86_64;
2
3pub fn unwind_rule_from_detected_prologue(
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 if !is_next_instruction_expected_in_prologue(slice_to_end) {
9 return None;
10 }
11 // We're in a prologue. Find the current stack depth of this frame by
12 // walking backwards. This is risky business, because x86 is a variable
13 // length encoding so you never know what you're looking at if you look
14 // backwards.
15 // Let's do it anyway and hope our heuristics are good enough so that
16 // they work in more cases than they fail in.
17 let mut cursor = slice_from_start.len();
18 let mut sp_offset_by_8 = 0;
19 loop {
20 if cursor >= 4 {
21 // Detect push rbp; mov rbp, rsp [0x55, 0x48 0x89 0xe5]
22 if slice_from_start[cursor - 4..cursor] == [0x55, 0x48, 0x89, 0xe5] {
23 return Some(UnwindRuleX86_64::UseFramePointer);
24 }
25 }
26 if cursor >= 1 {
27 // Detect push rXX with optional prefix
28 let byte = slice_from_start[cursor - 1];
29 if byte & 0xf8 == 0x50 {
30 sp_offset_by_8 += 1;
31 cursor -= 1;
32
33 // Consume prefix, if present
34 if cursor >= 1 && slice_from_start[cursor - 1] & 0xfe == 0x40 {
35 cursor -= 1;
36 }
37
38 continue;
39 }
40 }
41 break;
42 }
43 sp_offset_by_8 += 1; // Add one for popping the return address.
44 Some(UnwindRuleX86_64::OffsetSp { sp_offset_by_8 })
45}
46
47fn is_next_instruction_expected_in_prologue(bytes: &[u8]) -> bool {
48 if bytes.len() < 4 {
49 return false;
50 }
51
52 // Detect push rXX
53 if bytes[0] & 0xf8 == 0x50 {
54 return true;
55 }
56 // Detect push rXX with prefix
57 if bytes[0] & 0xfe == 0x40 && bytes[1] & 0xf8 == 0x50 {
58 return true;
59 }
60 // Detect sub rsp, 0xXX (8-bit immediate operand)
61 if bytes[0..2] == [0x83, 0xec] {
62 return true;
63 }
64 // Detect sub rsp, 0xXX with prefix (8-bit immediate operand)
65 if bytes[0..3] == [0x48, 0x83, 0xec] {
66 return true;
67 }
68 // Detect sub rsp, 0xXX (32-bit immediate operand)
69 if bytes[0..2] == [0x81, 0xec] {
70 return true;
71 }
72 // Detect sub rsp, 0xXX with prefix (32-bit immediate operand)
73 if bytes[0..3] == [0x48, 0x81, 0xec] {
74 return true;
75 }
76 // Detect mov rbp, rsp [0x48 0x89 0xe5]
77 if bytes[0..3] == [0x48, 0x89, 0xe5] {
78 return true;
79 }
80
81 false
82}
83
84// TODO: Write tests for different "sub" types
85// 4e88e40 41 57 push r15
86// 4e88e42 41 56 push r14
87// 4e88e44 53 push rbx
88// 4e88e45 48 81 EC 80 00 00 00 sub rsp, 0x80
89// 4e88e4c 48 89 F3 mov rbx, rsi
90//
91//
92// 4423f9 55 push rbp
93// 4423fa 48 89 E5 mov rbp, rsp
94// 4423fd 41 57 push r15
95// 4423ff 41 56 push r14
96// 442401 41 55 push r13
97// 442403 41 54 push r12
98// 442405 53 push rbx
99// 442406 48 83 EC 18 sub rsp, 0x18
100// 44240a 48 8B 07 mov rax, qword [rdi]