use super::unwindregs::UnwindRegsAarch64; use crate::add_signed::checked_add_signed; use crate::error::Error; use crate::unwind_rule::UnwindRule; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum UnwindRuleAarch64 { /// (sp, fp, lr) = (sp, fp, lr) /// Only possible for the first frame. Subsequent frames must get the /// return address from somewhere other than the lr register to avoid /// infinite loops. NoOp, /// (sp, fp, lr) = if is_first_frame (sp, fp, lr) else (fp + 16, *fp, *(fp + 8)) /// Used as a fallback rule. NoOpIfFirstFrameOtherwiseFp, /// (sp, fp, lr) = (sp + 16x, fp, lr) /// Only possible for the first frame. Subsequent frames must get the /// return address from somewhere other than the lr register to avoid /// infinite loops. OffsetSp { sp_offset_by_16: u16 }, /// (sp, fp, lr) = (sp + 16x, fp, lr) if is_first_frame /// This rule reflects an ambiguity in DWARF CFI information. When the /// return address is "undefined" because it was omitted, it could mean /// "same value", but this is only allowed for the first frame. OffsetSpIfFirstFrameOtherwiseStackEndsHere { sp_offset_by_16: u16 }, /// (sp, fp, lr) = (sp + 16x, fp, *(sp + 8y)) OffsetSpAndRestoreLr { sp_offset_by_16: u16, lr_storage_offset_from_sp_by_8: i16, }, /// (sp, fp, lr) = (sp + 16x, *(sp + 8y), *(sp + 8z)) OffsetSpAndRestoreFpAndLr { sp_offset_by_16: u16, fp_storage_offset_from_sp_by_8: i16, lr_storage_offset_from_sp_by_8: i16, }, /// (sp, fp, lr) = (fp + 16, *fp, *(fp + 8)) UseFramePointer, /// (sp, fp, lr) = (fp + 8x, *(fp + 8y), *(fp + 8z)) UseFramepointerWithOffsets { sp_offset_from_fp_by_8: u16, fp_storage_offset_from_fp_by_8: i16, lr_storage_offset_from_fp_by_8: i16, }, } impl UnwindRule for UnwindRuleAarch64 { type UnwindRegs = UnwindRegsAarch64; fn rule_for_stub_functions() -> Self { UnwindRuleAarch64::NoOp } fn rule_for_function_start() -> Self { UnwindRuleAarch64::NoOp } fn fallback_rule() -> Self { UnwindRuleAarch64::UseFramePointer } fn exec( self, is_first_frame: bool, regs: &mut UnwindRegsAarch64, read_stack: &mut F, ) -> Result, Error> where F: FnMut(u64) -> Result, { let lr = regs.lr(); let sp = regs.sp(); let fp = regs.fp(); let (new_lr, new_sp, new_fp) = match self { UnwindRuleAarch64::NoOp => { if !is_first_frame { return Err(Error::DidNotAdvance); } (lr, sp, fp) } UnwindRuleAarch64::NoOpIfFirstFrameOtherwiseFp => { if is_first_frame { (lr, sp, fp) } else { let fp = regs.fp(); let new_sp = fp.checked_add(16).ok_or(Error::IntegerOverflow)?; let new_lr = read_stack(fp + 8).map_err(|_| Error::CouldNotReadStack(fp + 8))?; let new_fp = read_stack(fp).map_err(|_| Error::CouldNotReadStack(fp))?; if new_sp <= sp { return Err(Error::FramepointerUnwindingMovedBackwards); } (new_lr, new_sp, new_fp) } } UnwindRuleAarch64::OffsetSpIfFirstFrameOtherwiseStackEndsHere { sp_offset_by_16 } => { if !is_first_frame { return Ok(None); } let sp_offset = u64::from(sp_offset_by_16) * 16; let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?; (lr, new_sp, fp) } UnwindRuleAarch64::OffsetSp { sp_offset_by_16 } => { if !is_first_frame { return Err(Error::DidNotAdvance); } let sp_offset = u64::from(sp_offset_by_16) * 16; let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?; (lr, new_sp, fp) } UnwindRuleAarch64::OffsetSpAndRestoreLr { sp_offset_by_16, lr_storage_offset_from_sp_by_8, } => { let sp_offset = u64::from(sp_offset_by_16) * 16; let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?; let lr_storage_offset = i64::from(lr_storage_offset_from_sp_by_8) * 8; let lr_location = checked_add_signed(sp, lr_storage_offset).ok_or(Error::IntegerOverflow)?; let new_lr = read_stack(lr_location).map_err(|_| Error::CouldNotReadStack(lr_location))?; (new_lr, new_sp, fp) } UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr { sp_offset_by_16, fp_storage_offset_from_sp_by_8, lr_storage_offset_from_sp_by_8, } => { let sp_offset = u64::from(sp_offset_by_16) * 16; let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?; let lr_storage_offset = i64::from(lr_storage_offset_from_sp_by_8) * 8; let lr_location = checked_add_signed(sp, lr_storage_offset).ok_or(Error::IntegerOverflow)?; let new_lr = read_stack(lr_location).map_err(|_| Error::CouldNotReadStack(lr_location))?; let fp_storage_offset = i64::from(fp_storage_offset_from_sp_by_8) * 8; let fp_location = checked_add_signed(sp, fp_storage_offset).ok_or(Error::IntegerOverflow)?; let new_fp = read_stack(fp_location).map_err(|_| Error::CouldNotReadStack(fp_location))?; (new_lr, new_sp, new_fp) } UnwindRuleAarch64::UseFramePointer => { // Do a frame pointer stack walk. Frame-based aarch64 functions store the caller's fp and lr // on the stack and then set fp to the address where the caller's fp is stored. // // Function prologue example (this one also stores x19, x20, x21 and x22): // stp x22, x21, [sp, #-0x30]! ; subtracts 0x30 from sp, and then stores (x22, x21) at sp // stp x20, x19, [sp, #0x10] ; stores (x20, x19) at sp + 0x10 (== original sp - 0x20) // stp fp, lr, [sp, #0x20] ; stores (fp, lr) at sp + 0x20 (== original sp - 0x10) // add fp, sp, #0x20 ; sets fp to the address where the old fp is stored on the stack // // Function epilogue: // ldp fp, lr, [sp, #0x20] ; restores fp and lr from the stack // ldp x20, x19, [sp, #0x10] ; restores x20 and x19 // ldp x22, x21, [sp], #0x30 ; restores x22 and x21, and then adds 0x30 to sp // ret ; follows lr to jump back to the caller // // Functions are called with bl ("branch with link"); bl puts the return address into the lr register. // When a function reaches its end, ret reads the return address from lr and jumps to it. // On aarch64, the stack pointer is always aligned to 16 bytes, and registers are usually written // to and read from the stack in pairs. // In frame-based functions, fp and lr are placed next to each other on the stack. // So when a function is called, we have the following stack layout: // // [... rest of the stack] // ^ sp ^ fp // bl some_function ; jumps to the function and sets lr = return address // [... rest of the stack] // ^ sp ^ fp // adjust stack ptr, write some registers, and write fp and lr // [more saved regs] [caller's frame pointer] [return address] [... rest of the stack] // ^ sp ^ fp // add fp, sp, #0x20 ; sets fp to where the caller's fp is now stored // [more saved regs] [caller's frame pointer] [return address] [... rest of the stack] // ^ sp ^ fp // ; can execute bl and overwrite lr with a new value // ... [more saved regs] [caller's frame pointer] [return address] [... rest of the stack] // ^ sp ^ fp // // So: *fp is the caller's frame pointer, and *(fp + 8) is the return address. let fp = regs.fp(); let new_sp = fp.checked_add(16).ok_or(Error::IntegerOverflow)?; let new_lr = read_stack(fp + 8).map_err(|_| Error::CouldNotReadStack(fp + 8))?; let new_fp = read_stack(fp).map_err(|_| Error::CouldNotReadStack(fp))?; if new_fp == 0 { return Ok(None); } if new_fp <= fp || new_sp <= sp { return Err(Error::FramepointerUnwindingMovedBackwards); } (new_lr, new_sp, new_fp) } UnwindRuleAarch64::UseFramepointerWithOffsets { sp_offset_from_fp_by_8, fp_storage_offset_from_fp_by_8, lr_storage_offset_from_fp_by_8, } => { let sp_offset_from_fp = u64::from(sp_offset_from_fp_by_8) * 8; let new_sp = fp .checked_add(sp_offset_from_fp) .ok_or(Error::IntegerOverflow)?; let lr_storage_offset = i64::from(lr_storage_offset_from_fp_by_8) * 8; let lr_location = checked_add_signed(fp, lr_storage_offset).ok_or(Error::IntegerOverflow)?; let new_lr = read_stack(lr_location).map_err(|_| Error::CouldNotReadStack(lr_location))?; let fp_storage_offset = i64::from(fp_storage_offset_from_fp_by_8) * 8; let fp_location = checked_add_signed(fp, fp_storage_offset).ok_or(Error::IntegerOverflow)?; let new_fp = read_stack(fp_location).map_err(|_| Error::CouldNotReadStack(fp_location))?; if new_fp == 0 { return Ok(None); } if new_fp <= fp || new_sp <= sp { return Err(Error::FramepointerUnwindingMovedBackwards); } (new_lr, new_sp, new_fp) } }; let return_address = regs.lr_mask().strip_ptr_auth(new_lr); if return_address == 0 { return Ok(None); } if !is_first_frame && new_sp == sp { return Err(Error::DidNotAdvance); } regs.set_lr(new_lr); regs.set_sp(new_sp); regs.set_fp(new_fp); Ok(Some(return_address)) } } #[cfg(test)] mod test { use super::*; #[test] fn test_basic() { let stack = [ 1, 2, 3, 4, 0x40, 0x100200, 5, 6, 0x70, 0x100100, 7, 8, 9, 10, 0x0, 0x0, ]; let mut read_stack = |addr| Ok(stack[(addr / 8) as usize]); let mut regs = UnwindRegsAarch64::new(0x100300, 0x10, 0x20); let res = UnwindRuleAarch64::NoOp.exec(true, &mut regs, &mut read_stack); assert_eq!(res, Ok(Some(0x100300))); assert_eq!(regs.sp(), 0x10); let res = UnwindRuleAarch64::UseFramePointer.exec(false, &mut regs, &mut read_stack); assert_eq!(res, Ok(Some(0x100200))); assert_eq!(regs.sp(), 0x30); assert_eq!(regs.fp(), 0x40); let res = UnwindRuleAarch64::UseFramePointer.exec(false, &mut regs, &mut read_stack); assert_eq!(res, Ok(Some(0x100100))); assert_eq!(regs.sp(), 0x50); assert_eq!(regs.fp(), 0x70); let res = UnwindRuleAarch64::UseFramePointer.exec(false, &mut regs, &mut read_stack); assert_eq!(res, Ok(None)); } }