use super::impl_prelude::*; use minidump::format::ContextFlagsCpu; use minidump::{ CpuContext, Endian, MinidumpContext, MinidumpContextValidity, MinidumpModuleList, MinidumpRawContext, }; use scroll::ctx::{SizeWith, TryFromCtx}; use std::collections::HashSet; use std::convert::TryFrom; use tracing::trace; type MipsContext = minidump::format::CONTEXT_MIPS; type Pointer = ::Register; const STACK_POINTER: &str = "sp"; const PROGRAM_COUNTER: &str = "pc"; const CALLEE_SAVED_REGS: &[&str] = &[ "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "gp", "sp", "fp", ]; async fn get_caller_by_cfi<'a, C, P>( ctx: &'a C, args: &'a GetCallerFrameArgs<'a, P>, ) -> Option where P: SymbolProvider + Sync, // all these bounds are essentially duplicated from `CfiStackWalker` :-( C: CpuContext + IntoRawContext + Clone + Send + Sync, C::Register: TryFrom, u64: TryFrom, C::Register: TryFromCtx<'a, Endian, [u8], Error = scroll::Error> + SizeWith, { trace!("trying cfi"); let _last_sp = ctx.get_register(STACK_POINTER, args.valid())?; let mut stack_walker = CfiStackWalker::from_ctx_and_args(ctx, args, callee_forwarded_regs)?; args.symbol_provider .walk_frame(stack_walker.module, &mut stack_walker) .await?; let caller_pc = stack_walker.caller_ctx.get_register_always(PROGRAM_COUNTER); let caller_sp = stack_walker.caller_ctx.get_register_always(STACK_POINTER); trace!( "cfi evaluation was successful -- caller_pc: 0x{caller_pc:016x}, caller_sp: 0x{caller_sp:016x}" ); // Do absolutely NO validation! Yep! As long as CFI evaluation succeeds // (which does include pc and sp resolving), just blindly assume the // values are correct. I Don't Like This, but it's what breakpad does and // we should start with a baseline of parity. let context = MinidumpContext { raw: stack_walker.caller_ctx.into_ctx(), valid: MinidumpContextValidity::Some(stack_walker.caller_validity), }; Some(StackFrame::from_context(context, FrameTrust::CallFrameInfo)) } fn callee_forwarded_regs(valid: &MinidumpContextValidity) -> HashSet<&'static str> { match valid { MinidumpContextValidity::All => CALLEE_SAVED_REGS.iter().copied().collect(), MinidumpContextValidity::Some(ref which) => CALLEE_SAVED_REGS .iter() .filter(|®| which.contains(reg)) .copied() .collect(), } } async fn get_caller_by_scan32

( ctx: &Mips32Context, args: &GetCallerFrameArgs<'_, P>, ) -> Option where P: SymbolProvider + Sync, { const MAX_STACK_SIZE: u32 = 1024; const MIN_ARGS: u32 = 4; const POINTER_WIDTH: u32 = 4; trace!("trying scan"); // Stack scanning is just walking from the end of the frame until we encounter // a value on the stack that looks like a pointer into some code (it's an address // in a range covered by one of our modules). If we find such an instruction, // we assume it's a `ra` value that was saved on the stack by the callee in // its function prologue, following a `jal` (call) instruction of the caller. // The next frame is then assumed to end just before that `ra` value. let mut last_sp = ctx.get_register(STACK_POINTER, args.valid())?; let mut count = MAX_STACK_SIZE / POINTER_WIDTH; // In case of mips32 ABI the stack frame of a non-leaf function // must have a minimum stack frame size for 4 arguments (4 words). // Move stack pointer for 4 words to avoid reporting non-existing frames // for all frames except the topmost one. // There is no way of knowing if topmost frame belongs to a leaf or // a non-leaf function. if args.callee_frame.trust != FrameTrust::Context { last_sp = last_sp.checked_add(MIN_ARGS * POINTER_WIDTH)?; count -= MIN_ARGS; } for i in 0..count { let address_of_pc = last_sp.checked_add(i * POINTER_WIDTH)?; let caller_pc: u32 = args .stack_memory .get_memory_at_address(address_of_pc as u64)?; //trace!("unwind: trying addr 0x{address_of_pc:08x}: 0x{caller_pc:08x}"); if instruction_seems_valid(caller_pc as u64, args.modules, args.symbol_provider).await { // `ra` is usually saved directly at the bottom of the frame, // so sp is just address_of_pc + ptr let caller_sp = address_of_pc.checked_add(POINTER_WIDTH)?; // Don't do any more validation, and don't try to restore fp // (that's what breakpad does!) trace!( "scan seems valid -- caller_pc: 0x{caller_pc:016x}, caller_sp: 0x{caller_sp:016x}" ); let mut caller_ctx = MipsContext::default(); caller_ctx.set_register(PROGRAM_COUNTER, caller_pc as u64); caller_ctx.set_register(STACK_POINTER, caller_sp as u64); let mut valid = HashSet::new(); valid.insert(PROGRAM_COUNTER); valid.insert(STACK_POINTER); let context = MinidumpContext { raw: MinidumpRawContext::Mips(caller_ctx), valid: MinidumpContextValidity::Some(valid), }; return Some(StackFrame::from_context(context, FrameTrust::Scan)); } } None } async fn get_caller_by_scan64

( ctx: &MipsContext, args: &GetCallerFrameArgs<'_, P>, ) -> Option where P: SymbolProvider + Sync, { const MAX_STACK_SIZE: u64 = 1024; const POINTER_WIDTH: u64 = 8; trace!("trying scan"); // Stack scanning is just walking from the end of the frame until we encounter // a value on the stack that looks like a pointer into some code (it's an address // in a range covered by one of our modules). If we find such an instruction, // we assume it's a `ra` value that was saved on the stack by the callee in // its function prologue, following a `jal` (call) instruction of the caller. // The next frame is then assumed to end just before that `ra` value. let last_sp = ctx.get_register(STACK_POINTER, args.valid())?; let count = MAX_STACK_SIZE / POINTER_WIDTH; for i in 0..count { let address_of_pc = last_sp.checked_add(i * POINTER_WIDTH)?; let caller_pc = args.stack_memory.get_memory_at_address(address_of_pc)?; if instruction_seems_valid(caller_pc, args.modules, args.symbol_provider).await { // `ra` is usually saved directly at the bottom of the frame, // so sp is just address_of_pc + ptr let caller_sp = address_of_pc.checked_add(POINTER_WIDTH)?; // Don't do any more validation, and don't try to restore fp // (that's what breakpad does!) trace!( "scan seems valid -- caller_pc: 0x{caller_pc:016x}, caller_sp: 0x{caller_sp:016x}" ); let mut caller_ctx = MipsContext::default(); caller_ctx.set_register(PROGRAM_COUNTER, caller_pc); caller_ctx.set_register(STACK_POINTER, caller_sp); let mut valid = HashSet::new(); valid.insert(PROGRAM_COUNTER); valid.insert(STACK_POINTER); let context = MinidumpContext { raw: MinidumpRawContext::Mips(caller_ctx), valid: MinidumpContextValidity::Some(valid), }; return Some(StackFrame::from_context(context, FrameTrust::Scan)); } } None } async fn instruction_seems_valid

( instruction: Pointer, modules: &MinidumpModuleList, symbol_provider: &P, ) -> bool where P: SymbolProvider + Sync, { if instruction < 0x1000 { return false; } super::instruction_seems_valid_by_symbols(instruction, modules, symbol_provider).await } pub async fn get_caller_frame

( ctx: &MipsContext, args: &GetCallerFrameArgs<'_, P>, ) -> Option where P: SymbolProvider + Sync, { let ctx32 = Mips32Context::try_from(ctx.clone()); // .await doesn't like closures, so don't use Option chaining let mut frame = None; if frame.is_none() { match &ctx32 { Ok(mips32) => frame = get_caller_by_cfi(mips32, args).await, Err(mips64) => frame = get_caller_by_cfi(mips64, args).await, } } if frame.is_none() { match &ctx32 { Ok(mips32) => frame = get_caller_by_scan32(mips32, args).await, Err(mips64) => frame = get_caller_by_scan64(mips64, args).await, } } let mut frame = frame?; // We now check the frame to see if it looks like unwinding is complete, // based on the frame we computed having a nonsense value. Returning // None signals to the unwinder to stop unwinding. // if the instruction is within the first ~page of memory, it's basically // null, and we can assume unwinding is complete. if frame.context.get_instruction_pointer() < 4096 { trace!("instruction pointer was nullish, assuming unwind complete"); return None; } // If the new stack pointer is at a lower address than the old, // then that's clearly incorrect. Treat this as end-of-stack to // enforce progress and avoid infinite loops. let sp = frame.context.get_stack_pointer(); let last_sp = ctx.get_register_always(STACK_POINTER); if sp <= last_sp { // Mips leaf functions may not actually touch the stack (thanks // to the return address register allowing you to "push" the return address // to a register), so we need to permit the stack pointer to not // change for the first frame of the unwind. After that we need // more strict validation to avoid infinite loops. let is_leaf = args.callee_frame.trust == FrameTrust::Context && sp == last_sp; if !is_leaf { trace!("stack pointer went backwards, assuming unwind complete"); return None; } } // Ok, the frame now seems well and truly valid, do final cleanup. // The Mips `jal` instruction always sets $ra to PC + 8 let ip = frame.context.get_instruction_pointer(); frame.instruction = ip - 8; Some(frame) } /// This is a hack to have a different [`CpuContext`] type/impl depending on the /// context flags of the inner [`MipsContext`] #[derive(Clone)] struct Mips32Context(MipsContext); impl CpuContext for Mips32Context { type Register = u32; const REGISTERS: &'static [&'static str] = ::REGISTERS; fn get_register_always(&self, reg: &str) -> Self::Register { self.0.get_register_always(reg) as u32 } fn set_register(&mut self, reg: &str, val: Self::Register) -> Option<()> { self.0.set_register(reg, val.into()) } fn stack_pointer_register_name(&self) -> &'static str { self.0.stack_pointer_register_name() } fn instruction_pointer_register_name(&self) -> &'static str { self.0.instruction_pointer_register_name() } } impl IntoRawContext for Mips32Context { fn into_ctx(self) -> MinidumpRawContext { MinidumpRawContext::Mips(self.0) } } trait IntoRawContext { fn into_ctx(self) -> MinidumpRawContext; } impl IntoRawContext for MipsContext { fn into_ctx(self) -> MinidumpRawContext { MinidumpRawContext::Mips(self) } } impl TryFrom for Mips32Context { type Error = MipsContext; fn try_from(ctx: MipsContext) -> Result { if ContextFlagsCpu::from_flags(ctx.context_flags).contains(ContextFlagsCpu::CONTEXT_MIPS64) { Err(ctx) } else { Ok(Self(ctx)) } } }