framehop/aarch64/
unwind_rule.rs

1use super::unwindregs::UnwindRegsAarch64;
2use crate::add_signed::checked_add_signed;
3use crate::error::Error;
4
5use crate::unwind_rule::UnwindRule;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum UnwindRuleAarch64 {
9    /// (sp, fp, lr) = (sp, fp, lr)
10    /// Only possible for the first frame. Subsequent frames must get the
11    /// return address from somewhere other than the lr register to avoid
12    /// infinite loops.
13    NoOp,
14    /// (sp, fp, lr) = if is_first_frame (sp, fp, lr) else (fp + 16, *fp, *(fp + 8))
15    /// Used as a fallback rule.
16    NoOpIfFirstFrameOtherwiseFp,
17    /// (sp, fp, lr) = (sp + 16x, fp, lr)
18    /// Only possible for the first frame. Subsequent frames must get the
19    /// return address from somewhere other than the lr register to avoid
20    /// infinite loops.
21    OffsetSp { sp_offset_by_16: u16 },
22    /// (sp, fp, lr) = (sp + 16x, fp, lr) if is_first_frame
23    /// This rule reflects an ambiguity in DWARF CFI information. When the
24    /// return address is "undefined" because it was omitted, it could mean
25    /// "same value", but this is only allowed for the first frame.
26    OffsetSpIfFirstFrameOtherwiseStackEndsHere { sp_offset_by_16: u16 },
27    /// (sp, fp, lr) = (sp + 16x, fp, *(sp + 8y))
28    OffsetSpAndRestoreLr {
29        sp_offset_by_16: u16,
30        lr_storage_offset_from_sp_by_8: i16,
31    },
32    /// (sp, fp, lr) = (sp + 16x, *(sp + 8y), *(sp + 8z))
33    OffsetSpAndRestoreFpAndLr {
34        sp_offset_by_16: u16,
35        fp_storage_offset_from_sp_by_8: i16,
36        lr_storage_offset_from_sp_by_8: i16,
37    },
38    /// (sp, fp, lr) = (fp + 16, *fp, *(fp + 8))
39    UseFramePointer,
40    /// (sp, fp, lr) = (fp + 8x, *(fp + 8y), *(fp + 8z))
41    UseFramepointerWithOffsets {
42        sp_offset_from_fp_by_8: u16,
43        fp_storage_offset_from_fp_by_8: i16,
44        lr_storage_offset_from_fp_by_8: i16,
45    },
46}
47
48impl UnwindRule for UnwindRuleAarch64 {
49    type UnwindRegs = UnwindRegsAarch64;
50
51    fn rule_for_stub_functions() -> Self {
52        UnwindRuleAarch64::NoOp
53    }
54    fn rule_for_function_start() -> Self {
55        UnwindRuleAarch64::NoOp
56    }
57    fn fallback_rule() -> Self {
58        UnwindRuleAarch64::UseFramePointer
59    }
60
61    fn exec<F>(
62        self,
63        is_first_frame: bool,
64        regs: &mut UnwindRegsAarch64,
65        read_stack: &mut F,
66    ) -> Result<Option<u64>, Error>
67    where
68        F: FnMut(u64) -> Result<u64, ()>,
69    {
70        let lr = regs.lr();
71        let sp = regs.sp();
72        let fp = regs.fp();
73
74        let (new_lr, new_sp, new_fp) = match self {
75            UnwindRuleAarch64::NoOp => {
76                if !is_first_frame {
77                    return Err(Error::DidNotAdvance);
78                }
79                (lr, sp, fp)
80            }
81            UnwindRuleAarch64::NoOpIfFirstFrameOtherwiseFp => {
82                if is_first_frame {
83                    (lr, sp, fp)
84                } else {
85                    let fp = regs.fp();
86                    let new_sp = fp.checked_add(16).ok_or(Error::IntegerOverflow)?;
87                    let new_lr =
88                        read_stack(fp + 8).map_err(|_| Error::CouldNotReadStack(fp + 8))?;
89                    let new_fp = read_stack(fp).map_err(|_| Error::CouldNotReadStack(fp))?;
90                    if new_sp <= sp {
91                        return Err(Error::FramepointerUnwindingMovedBackwards);
92                    }
93                    (new_lr, new_sp, new_fp)
94                }
95            }
96            UnwindRuleAarch64::OffsetSpIfFirstFrameOtherwiseStackEndsHere { sp_offset_by_16 } => {
97                if !is_first_frame {
98                    return Ok(None);
99                }
100                let sp_offset = u64::from(sp_offset_by_16) * 16;
101                let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?;
102                (lr, new_sp, fp)
103            }
104            UnwindRuleAarch64::OffsetSp { sp_offset_by_16 } => {
105                if !is_first_frame {
106                    return Err(Error::DidNotAdvance);
107                }
108                let sp_offset = u64::from(sp_offset_by_16) * 16;
109                let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?;
110                (lr, new_sp, fp)
111            }
112            UnwindRuleAarch64::OffsetSpAndRestoreLr {
113                sp_offset_by_16,
114                lr_storage_offset_from_sp_by_8,
115            } => {
116                let sp_offset = u64::from(sp_offset_by_16) * 16;
117                let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?;
118                let lr_storage_offset = i64::from(lr_storage_offset_from_sp_by_8) * 8;
119                let lr_location =
120                    checked_add_signed(sp, lr_storage_offset).ok_or(Error::IntegerOverflow)?;
121                let new_lr =
122                    read_stack(lr_location).map_err(|_| Error::CouldNotReadStack(lr_location))?;
123                (new_lr, new_sp, fp)
124            }
125            UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr {
126                sp_offset_by_16,
127                fp_storage_offset_from_sp_by_8,
128                lr_storage_offset_from_sp_by_8,
129            } => {
130                let sp_offset = u64::from(sp_offset_by_16) * 16;
131                let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?;
132                let lr_storage_offset = i64::from(lr_storage_offset_from_sp_by_8) * 8;
133                let lr_location =
134                    checked_add_signed(sp, lr_storage_offset).ok_or(Error::IntegerOverflow)?;
135                let new_lr =
136                    read_stack(lr_location).map_err(|_| Error::CouldNotReadStack(lr_location))?;
137                let fp_storage_offset = i64::from(fp_storage_offset_from_sp_by_8) * 8;
138                let fp_location =
139                    checked_add_signed(sp, fp_storage_offset).ok_or(Error::IntegerOverflow)?;
140                let new_fp =
141                    read_stack(fp_location).map_err(|_| Error::CouldNotReadStack(fp_location))?;
142                (new_lr, new_sp, new_fp)
143            }
144            UnwindRuleAarch64::UseFramePointer => {
145                // Do a frame pointer stack walk. Frame-based aarch64 functions store the caller's fp and lr
146                // on the stack and then set fp to the address where the caller's fp is stored.
147                //
148                // Function prologue example (this one also stores x19, x20, x21 and x22):
149                // stp  x22, x21, [sp, #-0x30]! ; subtracts 0x30 from sp, and then stores (x22, x21) at sp
150                // stp  x20, x19, [sp, #0x10]   ; stores (x20, x19) at sp + 0x10 (== original sp - 0x20)
151                // stp  fp, lr, [sp, #0x20]     ; stores (fp, lr) at sp + 0x20 (== original sp - 0x10)
152                // add  fp, sp, #0x20           ; sets fp to the address where the old fp is stored on the stack
153                //
154                // Function epilogue:
155                // ldp  fp, lr, [sp, #0x20]     ; restores fp and lr from the stack
156                // ldp  x20, x19, [sp, #0x10]   ; restores x20 and x19
157                // ldp  x22, x21, [sp], #0x30   ; restores x22 and x21, and then adds 0x30 to sp
158                // ret                          ; follows lr to jump back to the caller
159                //
160                // Functions are called with bl ("branch with link"); bl puts the return address into the lr register.
161                // When a function reaches its end, ret reads the return address from lr and jumps to it.
162                // On aarch64, the stack pointer is always aligned to 16 bytes, and registers are usually written
163                // to and read from the stack in pairs.
164                // In frame-based functions, fp and lr are placed next to each other on the stack.
165                // So when a function is called, we have the following stack layout:
166                //
167                //                                                                      [... rest of the stack]
168                //                                                                      ^ sp           ^ fp
169                //     bl some_function          ; jumps to the function and sets lr = return address
170                //                                                                      [... rest of the stack]
171                //                                                                      ^ sp           ^ fp
172                //     adjust stack ptr, write some registers, and write fp and lr
173                //       [more saved regs]  [caller's frame pointer]  [return address]  [... rest of the stack]
174                //       ^ sp                                                                          ^ fp
175                //     add    fp, sp, #0x20      ; sets fp to where the caller's fp is now stored
176                //       [more saved regs]  [caller's frame pointer]  [return address]  [... rest of the stack]
177                //       ^ sp               ^ fp
178                //     <function contents>       ; can execute bl and overwrite lr with a new value
179                //  ...  [more saved regs]  [caller's frame pointer]  [return address]  [... rest of the stack]
180                //  ^ sp                    ^ fp
181                //
182                // So: *fp is the caller's frame pointer, and *(fp + 8) is the return address.
183                let fp = regs.fp();
184                let new_sp = fp.checked_add(16).ok_or(Error::IntegerOverflow)?;
185                let new_lr = read_stack(fp + 8).map_err(|_| Error::CouldNotReadStack(fp + 8))?;
186                let new_fp = read_stack(fp).map_err(|_| Error::CouldNotReadStack(fp))?;
187                if new_fp == 0 {
188                    return Ok(None);
189                }
190                if new_fp <= fp || new_sp <= sp {
191                    return Err(Error::FramepointerUnwindingMovedBackwards);
192                }
193                (new_lr, new_sp, new_fp)
194            }
195            UnwindRuleAarch64::UseFramepointerWithOffsets {
196                sp_offset_from_fp_by_8,
197                fp_storage_offset_from_fp_by_8,
198                lr_storage_offset_from_fp_by_8,
199            } => {
200                let sp_offset_from_fp = u64::from(sp_offset_from_fp_by_8) * 8;
201                let new_sp = fp
202                    .checked_add(sp_offset_from_fp)
203                    .ok_or(Error::IntegerOverflow)?;
204                let lr_storage_offset = i64::from(lr_storage_offset_from_fp_by_8) * 8;
205                let lr_location =
206                    checked_add_signed(fp, lr_storage_offset).ok_or(Error::IntegerOverflow)?;
207                let new_lr =
208                    read_stack(lr_location).map_err(|_| Error::CouldNotReadStack(lr_location))?;
209                let fp_storage_offset = i64::from(fp_storage_offset_from_fp_by_8) * 8;
210                let fp_location =
211                    checked_add_signed(fp, fp_storage_offset).ok_or(Error::IntegerOverflow)?;
212                let new_fp =
213                    read_stack(fp_location).map_err(|_| Error::CouldNotReadStack(fp_location))?;
214
215                if new_fp == 0 {
216                    return Ok(None);
217                }
218                if new_fp <= fp || new_sp <= sp {
219                    return Err(Error::FramepointerUnwindingMovedBackwards);
220                }
221                (new_lr, new_sp, new_fp)
222            }
223        };
224        let return_address = regs.lr_mask().strip_ptr_auth(new_lr);
225        if return_address == 0 {
226            return Ok(None);
227        }
228        if !is_first_frame && new_sp == sp {
229            return Err(Error::DidNotAdvance);
230        }
231        regs.set_lr(new_lr);
232        regs.set_sp(new_sp);
233        regs.set_fp(new_fp);
234
235        Ok(Some(return_address))
236    }
237}
238
239#[cfg(test)]
240mod test {
241    use super::*;
242
243    #[test]
244    fn test_basic() {
245        let stack = [
246            1, 2, 3, 4, 0x40, 0x100200, 5, 6, 0x70, 0x100100, 7, 8, 9, 10, 0x0, 0x0,
247        ];
248        let mut read_stack = |addr| Ok(stack[(addr / 8) as usize]);
249        let mut regs = UnwindRegsAarch64::new(0x100300, 0x10, 0x20);
250        let res = UnwindRuleAarch64::NoOp.exec(true, &mut regs, &mut read_stack);
251        assert_eq!(res, Ok(Some(0x100300)));
252        assert_eq!(regs.sp(), 0x10);
253        let res = UnwindRuleAarch64::UseFramePointer.exec(false, &mut regs, &mut read_stack);
254        assert_eq!(res, Ok(Some(0x100200)));
255        assert_eq!(regs.sp(), 0x30);
256        assert_eq!(regs.fp(), 0x40);
257        let res = UnwindRuleAarch64::UseFramePointer.exec(false, &mut regs, &mut read_stack);
258        assert_eq!(res, Ok(Some(0x100100)));
259        assert_eq!(regs.sp(), 0x50);
260        assert_eq!(regs.fp(), 0x70);
261        let res = UnwindRuleAarch64::UseFramePointer.exec(false, &mut regs, &mut read_stack);
262        assert_eq!(res, Ok(None));
263    }
264}