framehop/x86_64/
unwind_rule.rs

1use super::register_ordering;
2use super::unwindregs::{Reg, UnwindRegsX86_64};
3use crate::add_signed::checked_add_signed;
4use crate::error::Error;
5use crate::unwind_rule::UnwindRule;
6use arrayvec::ArrayVec;
7
8/// For all of these: return address is *(new_sp - 8)
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum UnwindRuleX86_64 {
11    EndOfStack,
12    /// (sp, bp) = (sp + 8, bp)
13    JustReturn,
14    /// (sp, bp) = if is_first_frame (sp + 8, bp) else (bp + 16, *bp)
15    JustReturnIfFirstFrameOtherwiseFp,
16    /// (sp, bp) = (sp + 8x, bp)
17    OffsetSp {
18        sp_offset_by_8: u16,
19    },
20    /// (sp, bp) = (sp + 8x, *(sp + 8y))
21    OffsetSpAndRestoreBp {
22        sp_offset_by_8: u16,
23        bp_storage_offset_from_sp_by_8: i16,
24    },
25    /// (sp, bp) = (bp + 16, *bp)
26    UseFramePointer,
27    /// (sp, ...) = (sp + 8 * (offset + register count), ... popped according to encoded ordering)
28    /// This supports the common case of pushed callee-saved registers followed by a stack
29    /// allocation. Up to 8 registers can be stored, which covers all callee-saved registers (aside
30    /// from RSP which is implicit).
31    ///
32    /// The registers are stored in a separate compressed ordering to facilitate restoring register
33    /// values if desired. If not for this we could simply store the total offset.
34    OffsetSpAndPopRegisters {
35        /// The additional stack pointer offset to undo before popping the registers, divided by 8 bytes.
36        sp_offset_by_8: u16,
37        /// The number of registers to pop from the stack.
38        register_count: u8,
39        /// An encoded ordering of the callee-save registers to pop from the stack, see register_ordering.
40        encoded_registers_to_pop: u16,
41    },
42}
43
44pub enum OffsetOrPop {
45    None,
46    OffsetBy8(u16),
47    Pop(Reg),
48}
49
50impl UnwindRuleX86_64 {
51    /// Get the rule which represents the given operations, if possible.
52    pub fn for_sequence_of_offset_or_pop<I, T>(iter: I) -> Option<Self>
53    where
54        I: Iterator<Item = T>,
55        T: Into<OffsetOrPop>,
56    {
57        let mut iter = iter.map(Into::into).peekable();
58        let sp_offset_by_8 = if let Some(&OffsetOrPop::OffsetBy8(offset)) = iter.peek() {
59            iter.next();
60            offset
61        } else {
62            0
63        };
64
65        let mut regs = ArrayVec::<Reg, 8>::new();
66        for i in iter {
67            if let OffsetOrPop::Pop(reg) = i {
68                // If try_push errors we've exceeded the number of supported registers: there's no
69                // way to encode these operations as an unwind rule.
70                regs.try_push(reg).ok()?;
71            } else {
72                return None;
73            }
74        }
75
76        if regs.is_empty() && sp_offset_by_8 == 0 {
77            Some(Self::JustReturn)
78        } else {
79            let (register_count, encoded_registers_to_pop) = register_ordering::encode(&regs)?;
80            Some(Self::OffsetSpAndPopRegisters {
81                sp_offset_by_8,
82                register_count,
83                encoded_registers_to_pop,
84            })
85        }
86    }
87}
88
89impl UnwindRule for UnwindRuleX86_64 {
90    type UnwindRegs = UnwindRegsX86_64;
91
92    fn rule_for_stub_functions() -> Self {
93        UnwindRuleX86_64::JustReturn
94    }
95    fn rule_for_function_start() -> Self {
96        UnwindRuleX86_64::JustReturn
97    }
98    fn fallback_rule() -> Self {
99        UnwindRuleX86_64::UseFramePointer
100    }
101
102    fn exec<F>(
103        self,
104        is_first_frame: bool,
105        regs: &mut UnwindRegsX86_64,
106        read_stack: &mut F,
107    ) -> Result<Option<u64>, Error>
108    where
109        F: FnMut(u64) -> Result<u64, ()>,
110    {
111        let sp = regs.sp();
112        let (new_sp, new_bp) = match self {
113            UnwindRuleX86_64::EndOfStack => return Ok(None),
114            UnwindRuleX86_64::JustReturn => {
115                let new_sp = sp.checked_add(8).ok_or(Error::IntegerOverflow)?;
116                (new_sp, regs.bp())
117            }
118            UnwindRuleX86_64::JustReturnIfFirstFrameOtherwiseFp => {
119                if is_first_frame {
120                    let new_sp = sp.checked_add(8).ok_or(Error::IntegerOverflow)?;
121                    (new_sp, regs.bp())
122                } else {
123                    let sp = regs.sp();
124                    let bp = regs.bp();
125                    let new_sp = bp.checked_add(16).ok_or(Error::IntegerOverflow)?;
126                    if new_sp <= sp {
127                        return Err(Error::FramepointerUnwindingMovedBackwards);
128                    }
129                    let new_bp = read_stack(bp).map_err(|_| Error::CouldNotReadStack(bp))?;
130                    (new_sp, new_bp)
131                }
132            }
133            UnwindRuleX86_64::OffsetSp { sp_offset_by_8 } => {
134                let sp_offset = u64::from(sp_offset_by_8) * 8;
135                let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?;
136                (new_sp, regs.bp())
137            }
138            UnwindRuleX86_64::OffsetSpAndRestoreBp {
139                sp_offset_by_8,
140                bp_storage_offset_from_sp_by_8,
141            } => {
142                let sp_offset = u64::from(sp_offset_by_8) * 8;
143                let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?;
144                let bp_storage_offset_from_sp = i64::from(bp_storage_offset_from_sp_by_8) * 8;
145                let bp_location = checked_add_signed(sp, bp_storage_offset_from_sp)
146                    .ok_or(Error::IntegerOverflow)?;
147                let new_bp = match read_stack(bp_location) {
148                    Ok(new_bp) => new_bp,
149                    Err(()) if is_first_frame && bp_location < sp => {
150                        // Ignore errors when reading beyond the stack pointer in the first frame.
151                        // These negative offsets are sometimes seen in x86_64 epilogues, where
152                        // a bunch of registers are popped one after the other, and the compiler
153                        // doesn't always set the already-popped register to "unchanged" (because
154                        // doing so would take up extra space in the dwarf information).
155                        // read_stack may legitimately refuse to read beyond the stack pointer,
156                        // for example when the stack bytes are coming from a linux perf event
157                        // sample record, where the ustack bytes are copied starting from sp.
158                        regs.bp()
159                    }
160                    Err(()) => return Err(Error::CouldNotReadStack(bp_location)),
161                };
162                (new_sp, new_bp)
163            }
164            UnwindRuleX86_64::UseFramePointer => {
165                // Do a frame pointer stack walk. Code that is compiled with frame pointers
166                // has the following function prologues and epilogues:
167                //
168                // Function prologue:
169                // pushq  %rbp
170                // movq   %rsp, %rbp
171                //
172                // Function epilogue:
173                // popq   %rbp
174                // ret
175                //
176                // Functions are called with callq; callq pushes the return address onto the stack.
177                // When a function reaches its end, ret pops the return address from the stack and jumps to it.
178                // So when a function is called, we have the following stack layout:
179                //
180                //                                                                     [... rest of the stack]
181                //                                                                     ^ rsp           ^ rbp
182                //     callq some_function
183                //                                                   [return address]  [... rest of the stack]
184                //                                                   ^ rsp                             ^ rbp
185                //     pushq %rbp
186                //                         [caller's frame pointer]  [return address]  [... rest of the stack]
187                //                         ^ rsp                                                       ^ rbp
188                //     movq %rsp, %rbp
189                //                         [caller's frame pointer]  [return address]  [... rest of the stack]
190                //                         ^ rsp, rbp
191                //     <other instructions>
192                //       [... more stack]  [caller's frame pointer]  [return address]  [... rest of the stack]
193                //       ^ rsp             ^ rbp
194                //
195                // So: *rbp is the caller's frame pointer, and *(rbp + 8) is the return address.
196                //
197                // Or, in other words, the following linked list is built up on the stack:
198                // #[repr(C)]
199                // struct CallFrameInfo {
200                //     previous: *const CallFrameInfo,
201                //     return_address: *const c_void,
202                // }
203                // and rbp is a *const CallFrameInfo.
204                let sp = regs.sp();
205                let bp = regs.bp();
206                if bp == 0 {
207                    return Ok(None);
208                }
209                let new_sp = bp.checked_add(16).ok_or(Error::IntegerOverflow)?;
210                if new_sp <= sp {
211                    return Err(Error::FramepointerUnwindingMovedBackwards);
212                }
213                let new_bp = read_stack(bp).map_err(|_| Error::CouldNotReadStack(bp))?;
214                // new_bp is the caller's bp. If the caller uses frame pointers, then bp should be
215                // a valid frame pointer and we could do a coherency check on new_bp to make sure
216                // it's moving in the right direction. But if the caller is using bp as a general
217                // purpose register, then any value (including zero) would be a valid value.
218                // At this point we don't know how the caller uses bp, so we leave new_bp unchecked.
219
220                (new_sp, new_bp)
221            }
222            UnwindRuleX86_64::OffsetSpAndPopRegisters {
223                sp_offset_by_8,
224                register_count,
225                encoded_registers_to_pop,
226            } => {
227                let sp = regs.sp();
228                let mut sp = sp
229                    .checked_add(sp_offset_by_8 as u64 * 8)
230                    .ok_or(Error::IntegerOverflow)?;
231                for reg in register_ordering::decode(register_count, encoded_registers_to_pop) {
232                    let value = read_stack(sp).map_err(|_| Error::CouldNotReadStack(sp))?;
233                    sp = sp.checked_add(8).ok_or(Error::IntegerOverflow)?;
234                    regs.set(reg, value);
235                }
236                (sp.checked_add(8).ok_or(Error::IntegerOverflow)?, regs.bp())
237            }
238        };
239        let return_address =
240            read_stack(new_sp - 8).map_err(|_| Error::CouldNotReadStack(new_sp - 8))?;
241        if return_address == 0 {
242            return Ok(None);
243        }
244        if new_sp == sp && return_address == regs.ip() {
245            return Err(Error::DidNotAdvance);
246        }
247        regs.set_ip(return_address);
248        regs.set_sp(new_sp);
249        regs.set_bp(new_bp);
250        Ok(Some(return_address))
251    }
252}
253
254#[cfg(test)]
255mod test {
256    use super::*;
257
258    #[test]
259    fn test_basic() {
260        let stack = [
261            1, 2, 0x100300, 4, 0x40, 0x100200, 5, 6, 0x70, 0x100100, 7, 8, 9, 10, 0x0, 0x0,
262        ];
263        let mut read_stack = |addr| Ok(stack[(addr / 8) as usize]);
264        let mut regs = UnwindRegsX86_64::new(0x100400, 0x10, 0x20);
265        let res =
266            UnwindRuleX86_64::OffsetSp { sp_offset_by_8: 1 }.exec(true, &mut regs, &mut read_stack);
267        assert_eq!(res, Ok(Some(0x100300)));
268        assert_eq!(regs.ip(), 0x100300);
269        assert_eq!(regs.sp(), 0x18);
270        assert_eq!(regs.bp(), 0x20);
271        let res = UnwindRuleX86_64::UseFramePointer.exec(true, &mut regs, &mut read_stack);
272        assert_eq!(res, Ok(Some(0x100200)));
273        assert_eq!(regs.ip(), 0x100200);
274        assert_eq!(regs.sp(), 0x30);
275        assert_eq!(regs.bp(), 0x40);
276        let res = UnwindRuleX86_64::UseFramePointer.exec(false, &mut regs, &mut read_stack);
277        assert_eq!(res, Ok(Some(0x100100)));
278        assert_eq!(regs.ip(), 0x100100);
279        assert_eq!(regs.sp(), 0x50);
280        assert_eq!(regs.bp(), 0x70);
281        let res = UnwindRuleX86_64::UseFramePointer.exec(false, &mut regs, &mut read_stack);
282        assert_eq!(res, Ok(None));
283    }
284
285    #[test]
286    fn test_overflow() {
287        // This test makes sure that debug builds don't panic when trying to use frame pointer
288        // unwinding on code that was using the bp register as a general-purpose register and
289        // storing -1 in it. -1 is u64::MAX, so an unchecked add panics in debug builds.
290        let stack = [
291            1, 2, 0x100300, 4, 0x40, 0x100200, 5, 6, 0x70, 0x100100, 7, 8, 9, 10, 0x0, 0x0,
292        ];
293        let mut read_stack = |addr| Ok(stack[(addr / 8) as usize]);
294        let mut regs = UnwindRegsX86_64::new(0x100400, u64::MAX / 8 * 8, u64::MAX);
295        let res = UnwindRuleX86_64::JustReturn.exec(true, &mut regs, &mut read_stack);
296        assert_eq!(res, Err(Error::IntegerOverflow));
297        let res =
298            UnwindRuleX86_64::OffsetSp { sp_offset_by_8: 1 }.exec(true, &mut regs, &mut read_stack);
299        assert_eq!(res, Err(Error::IntegerOverflow));
300        let res = UnwindRuleX86_64::OffsetSpAndRestoreBp {
301            sp_offset_by_8: 1,
302            bp_storage_offset_from_sp_by_8: 2,
303        }
304        .exec(true, &mut regs, &mut read_stack);
305        assert_eq!(res, Err(Error::IntegerOverflow));
306        let res = UnwindRuleX86_64::UseFramePointer.exec(true, &mut regs, &mut read_stack);
307        assert_eq!(res, Err(Error::IntegerOverflow));
308    }
309}