// Copyright 2015 Ted Mielczarek. See the COPYRIGHT // file at the top-level directory of this distribution.
// NOTE: arm64_old.rs and arm64.rs should be identical except for the names of // their context types.
usesuper::impl_prelude::*; use minidump::system_info::Os; use minidump::{
CpuContext, MinidumpContext, MinidumpContextValidity, MinidumpModuleList, MinidumpRawContext,
}; use std::collections::HashSet; use tracing::trace;
type ArmContext = minidump::format::CONTEXT_ARM; type Pointer = <ArmContext as CpuContext>::Register; type Registers = minidump::format::ArmRegisterNumbers;
// 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.
fn get_caller_by_frame_pointer<P>(
ctx: &ArmContext,
args: &GetCallerFrameArgs<'_, P>,
) -> Option<StackFrame> where
P: SymbolProvider + Sync,
{ // The ARM manual states that: // > LR can be used for other purposes when it is not required to support // > a return from a subroutine. // In other words, we need to be conservative and treat it as a general // purpose register. Except on iOS, which has stricter conventions around // register use, and does guarantee that LR contains a valid return addr. if args.system_info.os != Os::Ios { return None;
}
trace!("trying frame pointer"); // Assume that the standard %fp-using ARM calling convention is in use. // The main quirk of this ABI is that the return address doesn't need to // be restored from the stack -- it's already in the link register (lr). // But that means we need to save/restore lr itself so that the *caller's* // return address can be recovered. // // In the standard calling convention, the following happens: // // lr := return_address (done implicitly by a call) // PUSH fp, lr (save fp and lr to the stack -- ARM pushes in pairs) // fp := sp (update the frame pointer to the current stack pointer) // // So to restore the caller's registers, we have: // // sp := fp + ptr*2 // pc := *(fp + ptr) // fp := *fp let last_fp = ctx.get_register(FRAME_POINTER, args.valid())?; let last_sp = ctx.get_register(STACK_POINTER, args.valid())?;
if last_fp >= u32::MAX - POINTER_WIDTH * 2 { // Although this code generally works fine if the pointer math overflows, // debug builds will still panic, and this guard protects against it without // drowning the rest of the code in checked_add. return None;
} let (caller_fp, caller_pc, caller_sp) = if last_fp == 0 { // In this case we want unwinding to stop. One of the termination conditions in get_caller_frame // is that caller_sp <= last_sp. Therefore we can force termination by setting caller_sp = last_sp.
(0, 0, last_sp)
} else {
(
args.stack_memory.get_memory_at_address(last_fp as u64)?,
args.stack_memory
.get_memory_at_address(last_fp as u64 + POINTER_WIDTH as u64)?,
last_fp + POINTER_WIDTH * 2,
)
};
// Don't do any more validation, just assume it worked.
asyncfn get_caller_by_scan<P>(
ctx: &ArmContext,
args: &GetCallerFrameArgs<'_, P>,
) -> Option<StackFrame> where
P: SymbolProvider + Sync,
{
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 an pc value that was pushed by the CALL instruction that created // the current frame. The next frame is then assumed to end just before that // pc value. let last_sp = ctx.get_register(STACK_POINTER, args.valid())?;
// Number of pointer-sized values to scan through in our search. let default_scan_range = 40; let extended_scan_range = default_scan_range * 4;
// Breakpad devs found that the first frame of an unwind can be really messed up, // and therefore benefits from a longer scan. Let's do it too. let scan_range = iflet FrameTrust::Context = args.callee_frame.trust {
extended_scan_range
} else {
default_scan_range
};
for i in0..scan_range { let address_of_pc = last_sp.checked_add(i * POINTER_WIDTH)?; let caller_pc = args
.stack_memory
.get_memory_at_address(address_of_pc as u64)?; if instruction_seems_valid(caller_pc, args.modules, args.symbol_provider).await { // pc is pushed by CALL, 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!)
/// The most strict validation we have for instruction pointers. /// /// This is only used for stack-scanning, because it's explicitly /// trying to distinguish between total garbage and correct values. /// cfi and frame_pointer approaches do not use this validation /// because by default they're working with plausible/trustworthy /// data. /// /// Specifically, not using this validation allows cfi/fp methods /// to unwind through frames we don't have mapped modules for (such as /// OS APIs). This may seem confusing since we obviously don't have cfi /// for unmapped modules! /// /// The way this works is that we will use cfi to unwind some frame we /// know about and *end up* in a function we know nothing about, but with /// all the right register values. At this point, frame pointers will /// often do the correct thing even though we don't know what code we're /// in -- until we get back into code we do know about and cfi kicks back in. /// At worst, this sets scanning up in a better position for success! /// /// If we applied this more rigorous validation to cfi/fp methods, we /// would just discard the correct register values from the known frame /// and immediately start doing unreliable scans. asyncfn instruction_seems_valid<P>(
instruction: Pointer,
modules: &MinidumpModuleList,
symbol_provider: &P,
) -> bool where
P: SymbolProvider + Sync,
{ super::instruction_seems_valid_by_symbols(instruction as u64, modules, symbol_provider).await
}
/* // ARM is currently hyper-permissive, so we don't use this, // but here it is in case we change our minds! fnstack_seems_valid( caller_sp:Pointer, callee_sp:Pointer, stack_memory:UnifiedMemory<'_,'_>, )->bool{ // The stack shouldn't *grow* when we unwind ifcaller_sp<callee_sp{ returnfalse; }
// The stack pointer should be in the stack stack_memory .get_memory_at_address::<Pointer>(caller_spasu64) .is_some() }
*/
pubasyncfn get_caller_frame<P>(
ctx: &ArmContext,
args: &GetCallerFrameArgs<'_, P>,
) -> Option<StackFrame> where
P: SymbolProvider + Sync,
{ // .await doesn't like closures, so don't use Option chaining letmut 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;
} letmut 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("sp") as u64; if sp <= last_sp { // Arm leaf functions may not actually touch the stack (thanks // to the link 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.
// A caller's ip is the return address, which is the instruction // *after* the CALL that caused us to arrive at the callee. Set // the value to 2 less than that, so it points to the CALL instruction // (arm instructions are all 2 bytes wide). This is important because // we use this value to lookup the CFI we need to unwind the next frame. let ip = frame.context.get_instruction_pointer();
frame.instruction = ip - 2;
Some(frame)
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.19 Sekunden
(vorverarbeitet am 2026-06-19)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.