use crate::traphandlers::{tls, CallThreadState};
use cfg_if::cfg_if;
use std::ops::ControlFlow;
cfg_if! {
if #[cfg(target_arch = "x86_64")] {
mod x86_64;
use x86_64 as arch;
} else if #[cfg(target_arch = "aarch64")] {
mod aarch64;
use aarch64 as arch;
} else if #[cfg(target_arch = "s390x")] {
mod s390x;
use s390x as arch;
} else if #[cfg(target_arch = "riscv64")] {
mod riscv64;
use riscv64 as arch;
} else {
compile_error!("unsupported architecture");
}
}
#[derive(Debug)]
pub struct Backtrace(Vec<Frame>);
#[derive(Debug)]
pub struct Frame {
pc: usize,
fp: usize,
}
impl Frame {
pub fn pc(&self) -> usize {
self.pc
}
pub fn fp(&self) -> usize {
self.fp
}
}
impl Backtrace {
pub fn empty() -> Backtrace {
Backtrace(Vec::new())
}
pub fn new() -> Backtrace {
tls::with(|state| match state {
Some(state) => unsafe { Self::new_with_trap_state(state, None) },
None => Backtrace(vec![]),
})
}
pub(crate) unsafe fn new_with_trap_state(
state: &CallThreadState,
trap_pc_and_fp: Option<(usize, usize)>,
) -> Backtrace {
let mut frames = vec![];
Self::trace_with_trap_state(state, trap_pc_and_fp, |frame| {
frames.push(frame);
ControlFlow::Continue(())
});
Backtrace(frames)
}
pub fn trace(f: impl FnMut(Frame) -> ControlFlow<()>) {
tls::with(|state| match state {
Some(state) => unsafe { Self::trace_with_trap_state(state, None, f) },
None => {}
});
}
pub(crate) unsafe fn trace_with_trap_state(
state: &CallThreadState,
trap_pc_and_fp: Option<(usize, usize)>,
mut f: impl FnMut(Frame) -> ControlFlow<()>,
) {
log::trace!("====== Capturing Backtrace ======");
let (last_wasm_exit_pc, last_wasm_exit_fp) = match trap_pc_and_fp {
Some((pc, fp)) => (pc, fp),
None => {
let pc = *(*state.limits).last_wasm_exit_pc.get();
let fp = *(*state.limits).last_wasm_exit_fp.get();
assert_ne!(pc, 0);
(pc, fp)
}
};
if let ControlFlow::Break(()) = Self::trace_through_wasm(
last_wasm_exit_pc,
last_wasm_exit_fp,
*(*state.limits).last_wasm_entry_sp.get(),
&mut f,
) {
log::trace!("====== Done Capturing Backtrace ======");
return;
}
for state in state.iter() {
if state.prev().is_null() {
debug_assert_eq!(state.old_last_wasm_exit_pc(), 0);
debug_assert_eq!(state.old_last_wasm_exit_fp(), 0);
debug_assert_eq!(state.old_last_wasm_entry_sp(), 0);
log::trace!("====== Done Capturing Backtrace ======");
return;
}
if let ControlFlow::Break(()) = Self::trace_through_wasm(
state.old_last_wasm_exit_pc(),
state.old_last_wasm_exit_fp(),
state.old_last_wasm_entry_sp(),
&mut f,
) {
log::trace!("====== Done Capturing Backtrace ======");
return;
}
}
unreachable!()
}
unsafe fn trace_through_wasm(
mut pc: usize,
mut fp: usize,
first_wasm_sp: usize,
mut f: impl FnMut(Frame) -> ControlFlow<()>,
) -> ControlFlow<()> {
log::trace!("=== Tracing through contiguous sequence of Wasm frames ===");
log::trace!("first_wasm_sp = 0x{:016x}", first_wasm_sp);
log::trace!(" initial pc = 0x{:016x}", pc);
log::trace!(" initial fp = 0x{:016x}", fp);
if first_wasm_sp == -1_isize as usize {
log::trace!("=== Done tracing (empty sequence of Wasm frames) ===");
return ControlFlow::Continue(());
}
assert_ne!(pc, 0);
assert_ne!(fp, 0);
assert_ne!(first_wasm_sp, 0);
assert!(first_wasm_sp >= fp, "{first_wasm_sp:#x} >= {fp:#x}");
arch::assert_entry_sp_is_aligned(first_wasm_sp);
loop {
arch::assert_fp_is_aligned(fp);
log::trace!("--- Tracing through one Wasm frame ---");
log::trace!("pc = {:p}", pc as *const ());
log::trace!("fp = {:p}", fp as *const ());
f(Frame { pc, fp })?;
if arch::reached_entry_sp(fp, first_wasm_sp) {
log::trace!("=== Done tracing contiguous sequence of Wasm frames ===");
return ControlFlow::Continue(());
}
pc = arch::get_next_older_pc_from_fp(fp);
assert_eq!(arch::NEXT_OLDER_FP_FROM_FP_OFFSET, 0);
let next_older_fp = *(fp as *mut usize).add(arch::NEXT_OLDER_FP_FROM_FP_OFFSET);
assert!(next_older_fp > fp, "{next_older_fp:#x} > {fp:#x}");
fp = next_older_fp;
}
}
pub fn frames<'a>(&'a self) -> impl ExactSizeIterator<Item = &'a Frame> + 'a {
self.0.iter()
}
}