use crate::process_state::{FrameTrust, StackFrame};
use crate::stackwalker::unwind::Unwind;
use crate::stackwalker::CfiStackWalker;
use crate::SymbolProvider;
use log::trace;
use minidump::format::CONTEXT_AMD64;
use minidump::{
MinidumpContext, MinidumpContextValidity, MinidumpMemory, MinidumpModuleList,
MinidumpRawContext,
};
use std::collections::HashSet;
type Pointer = u64;
const POINTER_WIDTH: Pointer = 8;
const INSTRUCTION_REGISTER: &str = "rip";
const STACK_POINTER_REGISTER: &str = "rsp";
const FRAME_POINTER_REGISTER: &str = "rbp";
const CALLEE_SAVED_REGS: &[&str] = &["rbx", "rbp", "r12", "r13", "r14", "r15"];
fn get_caller_by_cfi<P>(
ctx: &CONTEXT_AMD64,
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;
if let MinidumpContextValidity::Some(ref which) = valid {
if !which.contains(STACK_POINTER_REGISTER) {
return None;
}
}
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_ip = stack_walker.caller_ctx.rip;
let caller_sp = stack_walker.caller_ctx.rsp;
trace!(
"unwind: cfi evaluation was successful -- caller_ip: 0x{:016x}, caller_sp: 0x{:016x}",
caller_ip,
caller_sp,
);
trace!("unwind: cfi result seems valid");
let context = MinidumpContext {
raw: MinidumpRawContext::Amd64(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: &CONTEXT_AMD64,
callee: &StackFrame,
stack_memory: &MinidumpMemory,
_modules: &MinidumpModuleList,
_symbol_provider: &P,
) -> Option<StackFrame>
where
P: SymbolProvider,
{
trace!("unwind: trying frame pointer");
if let MinidumpContextValidity::Some(ref which) = callee.context.valid {
if !which.contains(FRAME_POINTER_REGISTER) {
return None;
}
if !which.contains(STACK_POINTER_REGISTER) {
return None;
}
}
let last_bp = ctx.rbp;
let last_sp = ctx.rsp;
if last_bp as u64 >= u64::MAX - POINTER_WIDTH as u64 * 2 {
return None;
}
let caller_ip = stack_memory.get_memory_at_address(last_bp as u64 + POINTER_WIDTH as u64)?;
let caller_bp = stack_memory.get_memory_at_address(last_bp as u64)?;
let caller_sp = last_bp + POINTER_WIDTH * 2;
if caller_sp <= last_bp || caller_bp < caller_sp {
trace!("unwind: rejecting frame pointer result for unreasonable frame pointer");
return None;
}
let _unused: Pointer = stack_memory.get_memory_at_address(caller_bp as u64)?;
if is_non_canonical(caller_ip) {
trace!("unwind: rejecting frame pointer result for unreasonable instruction pointer");
return None;
}
if !stack_seems_valid(caller_sp, last_sp, stack_memory) {
trace!("unwind: rejecting frame pointer result for unreasonable stack pointer");
return None;
}
trace!(
"unwind: frame pointer seems valid -- caller_ip: 0x{:016x}, caller_sp: 0x{:016x}",
caller_ip,
caller_sp,
);
let caller_ctx = CONTEXT_AMD64 {
rip: caller_ip,
rsp: caller_sp,
rbp: caller_bp,
..CONTEXT_AMD64::default()
};
let mut valid = HashSet::new();
valid.insert(INSTRUCTION_REGISTER);
valid.insert(STACK_POINTER_REGISTER);
valid.insert(FRAME_POINTER_REGISTER);
let context = MinidumpContext {
raw: MinidumpRawContext::Amd64(caller_ctx),
valid: MinidumpContextValidity::Some(valid),
};
Some(StackFrame::from_context(context, FrameTrust::FramePointer))
}
fn get_caller_by_scan<P>(
ctx: &CONTEXT_AMD64,
callee: &StackFrame,
stack_memory: &MinidumpMemory,
modules: &MinidumpModuleList,
symbol_provider: &P,
) -> Option<StackFrame>
where
P: SymbolProvider,
{
trace!("unwind: trying scan");
let last_bp = match callee.context.valid {
MinidumpContextValidity::All => Some(ctx.rbp),
MinidumpContextValidity::Some(ref which) => {
if !which.contains(STACK_POINTER_REGISTER) {
trace!("unwind: cannot scan without stack pointer");
return None;
}
if which.contains(FRAME_POINTER_REGISTER) {
Some(ctx.rbp)
} else {
None
}
}
};
let last_sp = ctx.rsp;
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_ip = last_sp.checked_add(i * POINTER_WIDTH)?;
let caller_ip = stack_memory.get_memory_at_address(address_of_ip as u64)?;
if instruction_seems_valid(caller_ip, modules, symbol_provider) {
let caller_sp = address_of_ip.checked_add(POINTER_WIDTH)?;
let mut caller_bp = None;
const MAX_REASONABLE_GAP_BETWEEN_FRAMES: Pointer = 128 * 1024;
if let Some(last_bp) = last_bp {
let address_of_bp = address_of_ip - POINTER_WIDTH;
let bp = stack_memory.get_memory_at_address(address_of_bp as u64)?;
if last_bp == address_of_bp
&& bp > address_of_ip
&& bp - address_of_bp <= MAX_REASONABLE_GAP_BETWEEN_FRAMES
{
if stack_memory
.get_memory_at_address::<Pointer>(bp as u64)
.is_some()
{
caller_bp = Some(bp);
}
} else if last_bp >= caller_sp {
caller_bp = Some(last_bp);
}
}
trace!(
"unwind: scan seems valid -- caller_ip: 0x{:08x}, caller_sp: 0x{:08x}",
caller_ip,
caller_sp,
);
let caller_ctx = CONTEXT_AMD64 {
rip: caller_ip,
rsp: caller_sp,
rbp: caller_bp.unwrap_or(0),
..CONTEXT_AMD64::default()
};
let mut valid = HashSet::new();
valid.insert(INSTRUCTION_REGISTER);
valid.insert(STACK_POINTER_REGISTER);
if caller_bp.is_some() {
valid.insert(FRAME_POINTER_REGISTER);
}
let context = MinidumpContext {
raw: MinidumpRawContext::Amd64(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 stack_seems_valid(
caller_sp: Pointer,
callee_sp: Pointer,
stack_memory: &MinidumpMemory,
) -> bool {
if caller_sp <= callee_sp {
return false;
}
stack_memory
.get_memory_at_address::<Pointer>(caller_sp as u64)
.is_some()
}
fn is_non_canonical(ptr: Pointer) -> bool {
ptr > 0x7FFFFFFFFFFF && ptr < 0xFFFF800000000000
}
impl Unwind for CONTEXT_AMD64 {
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.rsp {
trace!("unwind: stack pointer went backwards, assuming unwind complete");
return None;
}
let ip = frame.context.get_instruction_pointer() as u64;
frame.instruction = ip - 1;
Some(frame)
})
}
}