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, Module,
};
use std::collections::HashSet;
type ArmContext = minidump::format::CONTEXT_ARM64_OLD;
type Pointer = <ArmContext as CpuContext>::Register;
type Registers = minidump::format::Arm64RegisterNumbers;
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 LINK_REGISTER: &str = Registers::LinkRegister.name();
const PROGRAM_COUNTER: &str = Registers::ProgramCounter.name();
const CALLEE_SAVED_REGS: &[&str] = &[
"x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29",
];
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,
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::OldArm64(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,
grand_callee: Option<&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 = match ctx.get_register(LINK_REGISTER, valid) {
Some(lr) => ptr_auth_strip(modules, lr),
None => {
get_link_register_by_frame_pointer(ctx, valid, stack_memory, grand_callee, modules)?
}
};
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 + POINTER_WIDTH as u64)?;
let caller_lr = ptr_auth_strip(modules, caller_lr);
let caller_pc = last_lr;
let caller_sp = if last_fp == 0 {
last_sp
} else {
last_fp + POINTER_WIDTH * 2
};
if is_non_canonical(caller_pc) {
trace!("unwind: rejecting frame pointer result for unreasonable instruction pointer");
return None;
}
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::OldArm64(caller_ctx),
valid: MinidumpContextValidity::Some(valid),
};
Some(StackFrame::from_context(context, FrameTrust::FramePointer))
}
fn get_link_register_by_frame_pointer(
ctx: &ArmContext,
valid: &MinidumpContextValidity,
stack_memory: &MinidumpMemory,
grand_callee: Option<&StackFrame>,
modules: &MinidumpModuleList,
) -> Option<Pointer> {
let grand_callee = grand_callee?;
let last_last_fp = if let MinidumpRawContext::OldArm64(ref ctx) = grand_callee.context.raw {
ctx.get_register(FRAME_POINTER, &grand_callee.context.valid)?
} else {
return None;
};
let presumed_last_fp: Pointer = stack_memory.get_memory_at_address(last_last_fp as u64)?;
let last_fp = ctx.get_register(FRAME_POINTER, valid)?;
let last_sp = ctx.get_register(STACK_POINTER, valid)?;
if last_fp <= last_sp {
return None;
}
if presumed_last_fp != last_fp {
return None;
}
let last_lr = stack_memory.get_memory_at_address(last_last_fp + POINTER_WIDTH)?;
Some(ptr_auth_strip(modules, last_lr))
}
fn ptr_auth_strip(modules: &MinidumpModuleList, ptr: Pointer) -> Pointer {
if let Some(last_module) = modules.by_addr().next_back() {
let mut mask = last_module.base_address() + last_module.size();
mask |= mask >> 1;
mask |= mask >> 1;
mask |= mask >> 2;
mask |= mask >> 4;
mask |= mask >> 8;
mask |= mask >> 16;
mask |= mask >> 32;
let stripped = ptr & mask;
if modules.module_at_address(stripped).is_some() {
return stripped;
}
}
ptr
}
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::OldArm64(caller_ctx),
valid: MinidumpContextValidity::Some(valid),
};
return Some(StackFrame::from_context(context, FrameTrust::Scan));
}
}
None
}
fn instruction_seems_valid<P>(
instruction: Pointer,
modules: &MinidumpModuleList,
symbol_provider: &P,
) -> bool
where
P: SymbolProvider,
{
if is_non_canonical(instruction) || instruction == 0 {
return false;
}
super::instruction_seems_valid_by_symbols(instruction as u64, modules, symbol_provider)
}
fn is_non_canonical(instruction: Pointer) -> bool {
!(0x1000..=0x000fffffffffffff).contains(&instruction)
}
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,
grand_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") {
trace!("unwind: stack pointer went backwards, assuming unwind complete");
return None;
}
let ip = frame.context.get_instruction_pointer() as u64;
frame.instruction = ip - 4;
Some(frame)
})
}
}