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]