use super::impl_prelude::*;
use minidump::format::CONTEXT_X86;
use minidump::{MinidumpContext, MinidumpContextValidity, MinidumpModuleList, MinidumpRawContext};
use std::collections::HashSet;
use tracing::trace;
type Pointer = u32;
const POINTER_WIDTH: Pointer = 4;
const INSTRUCTION_REGISTER: &str = "eip";
const STACK_POINTER_REGISTER: &str = "esp";
const FRAME_POINTER_REGISTER: &str = "ebp";
const CALLEE_SAVED_REGS: &[&str] = &["ebp", "ebx", "edi", "esi"];
async fn get_caller_by_cfi<P>(
ctx: &CONTEXT_X86,
args: &GetCallerFrameArgs<'_, P>,
) -> Option<StackFrame>
where
P: SymbolProvider + Sync,
{
trace!("trying cfi");
if let MinidumpContextValidity::Some(ref which) = args.valid() {
if !which.contains(STACK_POINTER_REGISTER) {
return None;
}
}
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_ip = stack_walker.caller_ctx.eip;
let caller_sp = stack_walker.caller_ctx.esp;
trace!(
"cfi evaluation was successful -- caller_ip: 0x{:08x}, caller_sp: 0x{:08x}",
caller_ip,
caller_sp,
);
trace!("cfi result seems valid");
let context = MinidumpContext {
raw: MinidumpRawContext::X86(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_X86,
args: &GetCallerFrameArgs<'_, P>,
) -> Option<StackFrame>
where
P: SymbolProvider + Sync,
{
trace!("trying frame pointer");
if let MinidumpContextValidity::Some(ref which) = args.valid() {
if !which.contains(FRAME_POINTER_REGISTER) {
return None;
}
}
let last_bp = ctx.ebp;
if last_bp >= u32::MAX - POINTER_WIDTH * 2 {
return None;
}
let caller_ip = args
.stack_memory
.get_memory_at_address(last_bp as u64 + POINTER_WIDTH as u64)?;
let caller_bp = args.stack_memory.get_memory_at_address(last_bp as u64)?;
let caller_sp = last_bp + POINTER_WIDTH * 2;
trace!(
"frame pointer seems valid -- caller_ip: 0x{:08x}, caller_sp: 0x{:08x}",
caller_ip,
caller_sp,
);
let caller_ctx = CONTEXT_X86 {
eip: caller_ip,
esp: caller_sp,
ebp: caller_bp,
..CONTEXT_X86::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::X86(caller_ctx),
valid: MinidumpContextValidity::Some(valid),
};
Some(StackFrame::from_context(context, FrameTrust::FramePointer))
}
async fn get_caller_by_scan<P>(
ctx: &CONTEXT_X86,
args: &GetCallerFrameArgs<'_, P>,
) -> Option<StackFrame>
where
P: SymbolProvider + Sync,
{
trace!("trying scan");
let last_bp = match args.valid() {
MinidumpContextValidity::All => Some(ctx.ebp),
MinidumpContextValidity::Some(ref which) => {
if !which.contains(STACK_POINTER_REGISTER) {
trace!("cannot scan without stack pointer");
return None;
}
if which.contains(FRAME_POINTER_REGISTER) {
Some(ctx.ebp)
} else {
None
}
}
};
let last_sp = ctx.esp;
let default_scan_range = 40;
let extended_scan_range = default_scan_range * 4;
let scan_range = if let FrameTrust::Context = args.callee_frame.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 = args
.stack_memory
.get_memory_at_address(address_of_ip as u64)?;
if instruction_seems_valid(caller_ip, args.modules, args.symbol_provider).await {
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 i > 0 {
let address_of_bp = address_of_ip - POINTER_WIDTH;
let bp = args
.stack_memory
.get_memory_at_address(address_of_bp as u64)?;
if bp > address_of_ip && bp - address_of_bp <= MAX_REASONABLE_GAP_BETWEEN_FRAMES {
if args
.stack_memory
.get_memory_at_address::<Pointer>(bp as u64)
.is_some()
{
caller_bp = Some(bp);
}
} else if let Some(last_bp) = last_bp {
if last_bp >= caller_sp {
if args
.stack_memory
.get_memory_at_address::<Pointer>(last_bp as u64)
.is_some()
{
caller_bp = Some(last_bp);
}
}
}
}
trace!(
"scan seems valid -- caller_ip: 0x{:08x}, caller_sp: 0x{:08x}",
caller_ip,
caller_sp,
);
let caller_ctx = CONTEXT_X86 {
eip: caller_ip,
esp: caller_sp,
ebp: caller_bp.unwrap_or(0),
..CONTEXT_X86::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::X86(caller_ctx),
valid: MinidumpContextValidity::Some(valid),
};
return Some(StackFrame::from_context(context, FrameTrust::Scan));
}
}
None
}
async fn instruction_seems_valid<P>(
instruction: Pointer,
modules: &MinidumpModuleList,
symbol_provider: &P,
) -> bool
where
P: SymbolProvider + Sync,
{
if instruction == 0 {
return false;
}
super::instruction_seems_valid_by_symbols(instruction as u64, modules, symbol_provider).await
}
pub async fn get_caller_frame<P>(
ctx: &CONTEXT_X86,
args: &GetCallerFrameArgs<'_, P>,
) -> Option<StackFrame>
where
P: SymbolProvider + Sync,
{
let mut frame = None;
if frame.is_none() {
frame = get_caller_by_cfi(ctx, args).await;
}
if frame.is_none() {
frame = get_caller_by_frame_pointer(ctx, args);
}
if frame.is_none() {
frame = get_caller_by_scan(ctx, args).await;
}
let mut frame = frame?;
if frame.context.get_instruction_pointer() < 4096 {
trace!("instruction pointer was nullish, assuming unwind complete");
return None;
}
if frame.context.get_stack_pointer() <= ctx.esp as u64 {
trace!("stack pointer went backwards, assuming unwind complete");
return None;
}
let ip = frame.context.get_instruction_pointer();
frame.instruction = ip - 1;
Some(frame)
}