1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
//! Stack trace (backtrace) functionality using frame pointers.
//!
//! There are two main ways of obtaining stack traces:
//! 1. Using the frame pointer register to find the previous stack frame.
//! 2. Using DWARF debugging information to understand the layout of each stack frame.
//!
//! We support both ways, but prefer #2 because it doesn't suffer from the
//! compatibility and performance drawbacks of #1.
//! See the `stack_trace` crate for the #2 functionality.
//!
//! This crate offers support for #1.
//! The advantage of using this is that it doesn't require any significant dependencies.
//! However, this crate's frame pointer-based stack traces are only available
//! when the compiler has been configured to emit frame pointers,
//! which in Rust is achieved via the `-C force-frame-pointers=yes` rust flags option.
#![no_std]
// This entire crate depends upon the `frame_pointers` config option.
#[macro_use] extern crate cfg_if;
cfg_if! { if #[cfg(frame_pointers)] {
extern crate alloc;
extern crate memory;
use memory::{PageTable, VirtualAddress};
/// Get a stack trace using the frame pointer registers (RBP on x86_64).
/// This function is only available if the compiler was configured to use frame pointers.
/// Using frame pointers to navigate up the call stack can only provide very basic information,
/// i.e., the frame pointer register value and the instruction pointer of the call site.
///
/// For additional, fuller information about each stack frame, use the stack frame iterator
/// based on DWARF debug info.
///
/// # Arguments
/// * `current_page_table`: a reference to the active `PageTable`,
/// * `on_each_stack_frame`: the function that will be called for each stack frame in the call stack.
/// The function is called with two arguments:
/// (1) the frame pointer register value and (2) the instruction pointer
/// at that point in the call stack; the latter is useful for symbol resolution.
/// The function should return `true` if it wants to continue iterating up the call stack,
/// or `false` if it wants the iteration to stop.
/// * `max_recursion`: an optional maximum number of stack frames to recurse up the call stack.
/// If not provided, the default maximum will be `64` call stack frames.
///
#[inline(never)]
pub fn stack_trace_using_frame_pointers(
current_page_table: &PageTable,
on_each_stack_frame: &mut dyn FnMut(usize, VirtualAddress) -> bool,
max_recursion: Option<usize>,
) -> Result<(), &'static str> {
let mut rbp: usize;
// SAFE: just reading current register value
unsafe {
core::arch::asm!("mov {}, rbp", out(reg) rbp);
}
for _i in 0 .. max_recursion.unwrap_or(64) {
// the stack contains the return address (of the caller) right before the current frame pointer
if let Some(rip_ptr) = rbp.checked_add(core::mem::size_of::<usize>()) {
if let (Some(rbp_vaddr), Some(rip_ptr)) = (VirtualAddress::new(rbp), VirtualAddress::new(rip_ptr)) {
if current_page_table.translate(rbp_vaddr).is_some() && current_page_table.translate(rip_ptr).is_some() {
// SAFE: the address was checked above using page table walks
let rip = unsafe { *(rip_ptr.value() as *const usize) };
if rip == 0 {
return Ok(());
}
let rip = VirtualAddress::new(rip).ok_or("instruction pointer value was an invalid virtual address")?;
let keep_going = on_each_stack_frame(rbp, rip);
if !keep_going {
return Ok(());
}
// move up the call stack to the previous frame
// SAFE: the address was checked above using page tables
rbp = unsafe { *(rbp as *const usize) };
} else {
return Err("guard page");
}
} else {
return Err("frame pointer value in RBP was an invalid virtual address");
}
} else {
return Err("frame pointer value in RBP was too large and overflowed.");
}
}
Err("reached maximum recursion depth of call stack frames")
}
// // snippet to get the current instruction pointer RIP, stack pointer RSP, and RBP
// let mut rbp: usize;
// let mut rsp: usize;
// let mut rip: usize;
// unsafe {
// // On x86 you cannot directly read the value of the instruction pointer (RIP),
// // so we use a trick that exploits RIP-relateive addressing to read the current value of RIP (also gets RBP and RSP)
// llvm_asm!("lea $0, [rip]" : "=r"(rip), "={rbp}"(rbp), "={rsp}"(rsp) : : "memory" : "intel", "volatile");
// }
// debug!("register values: RIP: {:#X}, RSP: {:#X}, RBP: {:#X}", rip, rsp, rbp);
// let _curr_instruction_pointer = VirtualAddress::new_canonical(rip);
}} // end of cfg_if block