use crate::process_state::{FrameTrust, StackFrame};
use crate::stackwalker::unwind::Unwind;
use crate::stackwalker::CfiStackWalker;
use crate::SymbolProvider;
use log::trace;
use minidump::{
CpuContext, MinidumpContext, MinidumpContextValidity, MinidumpMemory, MinidumpModuleList,
MinidumpRawContext,
};
use std::collections::HashSet;
type ArmContext = minidump::format::CONTEXT_ARM;
type Pointer = <ArmContext as CpuContext>::Register;
type Registers = minidump::format::ArmRegisterNumbers;
const POINTER_WIDTH: Pointer = std::mem::size_of::<Pointer>() as Pointer;
const FRAME_POINTER: &str = Registers::FramePointer.name();
const STACK_POINTER: &str = Registers::StackPointer.name();
const PROGRAM_COUNTER: &str = Registers::ProgramCounter.name();
const LINK_REGISTER: &str = Registers::LinkRegister.name();
const CALLEE_SAVED_REGS: &[&str] = &["r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11"];
fn get_caller_by_cfi<P>(
ctx: &ArmContext,
callee: &StackFrame,
grand_callee: Option<&StackFrame>,
stack_memory: &MinidumpMemory,
modules: &MinidumpModuleList,
symbol_provider: &P,
) -> Option<StackFrame>
where
P: SymbolProvider,
{
trace!("unwind: trying cfi");
let valid = &callee.context.valid;
let _last_sp = ctx.get_register(STACK_POINTER, valid)?;
let module = modules.module_at_address(callee.instruction)?;
let grand_callee_parameter_size = grand_callee.and_then(|f| f.parameter_size).unwrap_or(0);
let mut stack_walker = CfiStackWalker {
instruction: callee.instruction,
grand_callee_parameter_size,
callee_ctx: ctx,
callee_validity: valid,
caller_ctx: ctx.clone(),
caller_validity: callee_forwarded_regs(valid),
stack_memory,
};
symbol_provider.walk_frame(module, &mut stack_walker)?;
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!(
"unwind: cfi evaluation was successful -- caller_pc: 0x{:016x}, caller_sp: 0x{:016x}",
caller_pc,
caller_sp,
);
let context = MinidumpContext {
raw: MinidumpRawContext::Arm(stack_walker.caller_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(),
}
}
fn get_caller_by_frame_pointer<P>(
ctx: &ArmContext,
callee: &StackFrame,
stack_memory: &MinidumpMemory,
_modules: &MinidumpModuleList,
_symbol_provider: &P,
) -> Option<StackFrame>
where
P: SymbolProvider,
{
trace!("unwind: trying frame pointer");
let valid = &callee.context.valid;
let last_fp = ctx.get_register(FRAME_POINTER, valid)?;
let last_sp = ctx.get_register(STACK_POINTER, valid)?;
let last_lr = ctx.get_register(LINK_REGISTER, valid)?;
if last_fp as u64 >= u64::MAX - POINTER_WIDTH as u64 * 2 {
return None;
}
let caller_fp = stack_memory.get_memory_at_address(last_fp as u64)?;
let caller_lr = stack_memory.get_memory_at_address(last_fp as u64 + POINTER_WIDTH as u64)?;
let caller_pc = last_lr;
let caller_sp = if last_fp == 0 {
last_sp
} else {
last_fp + POINTER_WIDTH * 2
};
trace!(
"unwind: frame pointer seems valid -- caller_pc: 0x{:016x}, caller_sp: 0x{:016x}",
caller_pc,
caller_sp,
);
let mut caller_ctx = ArmContext::default();
caller_ctx.set_register(PROGRAM_COUNTER, caller_pc);
caller_ctx.set_register(LINK_REGISTER, caller_lr);
caller_ctx.set_register(FRAME_POINTER, caller_fp);
caller_ctx.set_register(STACK_POINTER, caller_sp);
let mut valid = HashSet::new();
valid.insert(PROGRAM_COUNTER);
valid.insert(LINK_REGISTER);
valid.insert(FRAME_POINTER);
valid.insert(STACK_POINTER);
let context = MinidumpContext {
raw: MinidumpRawContext::Arm(caller_ctx),
valid: MinidumpContextValidity::Some(valid),
};
Some(StackFrame::from_context(context, FrameTrust::FramePointer))
}
fn get_caller_by_scan<P>(
ctx: &ArmContext,
callee: &StackFrame,
stack_memory: &MinidumpMemory,
modules: &MinidumpModuleList,
symbol_provider: &P,
) -> Option<StackFrame>
where
P: SymbolProvider,
{
trace!("unwind: trying scan");
let valid = &callee.context.valid;
let last_sp = ctx.get_register(STACK_POINTER, valid)?;
let default_scan_range = 40;
let extended_scan_range = default_scan_range * 4;
let scan_range = if let FrameTrust::Context = callee.trust {
extended_scan_range
} else {
default_scan_range
};
for i in 0..scan_range {
let address_of_pc = last_sp.checked_add(i * POINTER_WIDTH)?;
let caller_pc = stack_memory.get_memory_at_address(address_of_pc as u64)?;
if instruction_seems_valid(caller_pc, modules, symbol_provider) {
let caller_sp = address_of_pc.checked_add(POINTER_WIDTH)?;
trace!(
"unwind: scan seems valid -- caller_pc: 0x{:08x}, caller_sp: 0x{:08x}",
caller_pc,
caller_sp,
);
let mut caller_ctx = ArmContext::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::Arm(caller_ctx),
valid: MinidumpContextValidity::Some(valid),
};
return Some(StackFrame::from_context(context, FrameTrust::Scan));
}
}
None
}
#[allow(clippy::match_like_matches_macro)]
fn instruction_seems_valid<P>(
instruction: Pointer,
modules: &MinidumpModuleList,
symbol_provider: &P,
) -> bool
where
P: SymbolProvider,
{
super::instruction_seems_valid_by_symbols(instruction as u64, modules, symbol_provider)
}
impl Unwind for ArmContext {
fn get_caller_frame<P>(
&self,
callee: &StackFrame,
grand_callee: Option<&StackFrame>,
stack_memory: Option<&MinidumpMemory>,
modules: &MinidumpModuleList,
syms: &P,
) -> Option<StackFrame>
where
P: SymbolProvider,
{
stack_memory
.as_ref()
.and_then(|stack| {
get_caller_by_cfi(self, callee, grand_callee, stack, modules, syms)
.or_else(|| get_caller_by_frame_pointer(self, callee, stack, modules, syms))
.or_else(|| get_caller_by_scan(self, callee, stack, modules, syms))
})
.and_then(|mut frame| {
if frame.context.get_instruction_pointer() < 4096 {
trace!("unwind: instruction pointer was nullish, assuming unwind complete");
return None;
}
if frame.context.get_stack_pointer() < self.get_register_always("sp") as u64 {
trace!("unwind: stack pointer went backwards, assuming unwind complete");
return None;
}
let ip = frame.context.get_instruction_pointer() as u64;
frame.instruction = ip - 2;
Some(frame)
})
}
}