use alloc::{fmt, vec::Vec};
use core::{fmt::Display, marker::PhantomData};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Backtrace {
frames: Vec<*const ()>,
}
impl Backtrace {
#[allow(clippy::inline_always)]
#[inline(always)] #[allow(clippy::missing_const_for_fn)]
#[must_use = "this creates a backtrace but doesn't save it or log it anywhere"]
pub fn capture() -> Self {
BacktraceIter::capture(|bt| {
let mut frames = Vec::new();
for frame in bt {
frames.push(frame);
}
Self { frames }
})
}
#[must_use]
pub const fn frames(&self) -> &[*const ()] {
self.frames.as_slice()
}
}
impl Display for Backtrace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
format_backtrace(f, self.frames.iter().copied())
}
}
pub struct BacktraceIter<'a> {
lifetime: PhantomData<&'a ()>,
#[cfg(all(target_arch = "arm", target_os = "vexos"))]
frame: Option<&'a arm::FrameRecord>,
}
impl Iterator for BacktraceIter<'_> {
type Item = *const ();
fn next(&mut self) -> Option<Self::Item> {
#[cfg(target_os = "vexos")]
if let Some(frame) = self.frame {
let addr = frame.caller_address();
self.frame = frame.next();
return Some(addr as *const ());
}
None
}
}
impl BacktraceIter<'_> {
pub fn capture<R>(handler: impl FnOnce(BacktraceIter<'_>) -> R) -> R {
cfg_select! {
all(
target_arch = "arm",
target_os = "vexos",
) => {
arm::capture_frame(|frame| {
handler(BacktraceIter {
lifetime: PhantomData,
frame: Some(frame),
})
})
}
_ => handler(BacktraceIter {
lifetime: PhantomData,
})
}
}
#[must_use = "the backtrace isn't captured unless you step the iterator"]
#[allow(
clippy::missing_const_for_fn,
reason = "performs non-const operations on some targets"
)]
pub unsafe fn from_frame_ptr(ptr: *const ()) -> Self {
_ = ptr; Self {
lifetime: PhantomData,
#[cfg(all(target_arch = "arm", target_os = "vexos"))]
frame: unsafe { arm::FrameRecord::from_ptr(ptr.cast()) },
}
}
pub fn write_to(self, dest: &mut impl fmt::Write) -> fmt::Result {
format_backtrace(dest, self)
}
}
fn format_backtrace(
dest: &mut impl fmt::Write,
frames: impl Iterator<Item = *const ()>,
) -> fmt::Result {
writeln!(dest, "stack backtrace:")?;
for (i, frame) in frames.enumerate() {
writeln!(dest, "{i:>3}: 0x{:x}", frame as usize)?;
}
write!(
dest,
"note: Use a symbolizer to convert stack frames to human-readable function names."
)
}
#[cfg(all(target_arch = "arm", target_os = "vexos"))]
mod arm {
use core::{arch::asm, ptr};
unsafe extern "C" {
unsafe static __stack_top: u32;
unsafe static __stack_bottom: u32;
}
#[repr(C)]
pub struct FrameRecord {
caller: *const FrameRecord,
lr: u32,
}
impl FrameRecord {
#[must_use]
pub unsafe fn from_ptr<'a>(ptr: *const Self) -> Option<&'a Self> {
let stack = (&raw const __stack_bottom)..(&raw const __stack_top);
if !stack.contains(&ptr.cast()) {
return None;
}
unsafe { ptr.as_ref() }
}
#[must_use]
pub fn next(&self) -> Option<&Self> {
if self.caller <= ptr::from_ref(self) {
return None;
}
unsafe { Self::from_ptr(self.caller) }
}
#[must_use]
pub const fn caller_address(&self) -> u32 {
const THUMB_BIT: u32 = 0b1;
const INSTR_SIZE: u32 = 2;
(self.lr & !THUMB_BIT) - INSTR_SIZE
}
}
pub fn capture_frame<R>(handler: impl FnOnce(&FrameRecord) -> R) -> R {
let caller: *const FrameRecord;
let lr: u32;
unsafe {
cfg_select! {
target_feature = "thumb-mode" => asm!(
"mov {caller}, r7",
"mov {lr}, pc",
caller = out(reg) caller,
lr = out(reg) lr,
),
_ => asm!(
"mov {caller}, fp",
"mov {lr}, pc",
caller = out(reg) caller,
lr = out(reg) lr,
),
};
}
handler(&FrameRecord { caller, lr })
}
}