use std::ptr::null_mut;
use libc::c_void;
use crate::addr_validate::validate;
#[derive(Clone, Debug)]
pub struct Frame {
pub ip: usize,
}
extern "C" {
fn _Unwind_FindEnclosingFunction(pc: *mut c_void) -> *mut c_void;
}
impl super::Frame for Frame {
type S = backtrace::Symbol;
fn ip(&self) -> usize {
self.ip
}
fn resolve_symbol<F: FnMut(&Self::S)>(&self, cb: F) {
backtrace::resolve(self.ip as *mut c_void, cb);
}
fn symbol_address(&self) -> *mut libc::c_void {
if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
self.ip as *mut c_void
} else {
unsafe { _Unwind_FindEnclosingFunction(self.ip as *mut c_void) }
}
}
}
#[inline]
unsafe fn read_ptr<T>(ptr: *const T) -> T {
if ptr.align_offset(std::mem::align_of::<T>()) == 0 {
std::ptr::read(ptr)
} else {
std::ptr::read_unaligned(ptr)
}
}
pub struct Trace {}
impl super::Trace for Trace {
type Frame = Frame;
fn trace<F: FnMut(&Self::Frame) -> bool>(ucontext: *mut libc::c_void, mut cb: F) {
let ucontext: *mut libc::ucontext_t = ucontext as *mut libc::ucontext_t;
if ucontext.is_null() {
return;
}
#[cfg(all(target_arch = "x86_64", target_os = "linux"))]
let frame_pointer =
unsafe { (*ucontext).uc_mcontext.gregs[libc::REG_RBP as usize] as usize };
#[cfg(all(target_arch = "x86_64", target_os = "macos"))]
let frame_pointer = unsafe {
let mcontext = (*ucontext).uc_mcontext;
if mcontext.is_null() {
0
} else {
(*mcontext).__ss.__rbp as usize
}
};
#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
let frame_pointer = unsafe { (*ucontext).uc_mcontext.regs[29] as usize };
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
let frame_pointer = unsafe {
let mcontext = (*ucontext).uc_mcontext;
if mcontext.is_null() {
0
} else {
(*mcontext).__ss.__fp as usize
}
};
#[cfg(all(target_arch = "riscv64", target_os = "linux"))]
let frame_pointer = unsafe { (*ucontext).uc_mcontext.__gregs[libc::REG_S0] as usize };
#[cfg(all(target_arch = "loongarch64", target_os = "linux"))]
let frame_pointer = unsafe { (*ucontext).uc_mcontext.__gregs[22] as usize };
let mut frame_pointer = frame_pointer as *mut FramePointerLayout;
let mut last_frame_pointer: *mut FramePointerLayout = null_mut();
loop {
if !last_frame_pointer.is_null() && frame_pointer < last_frame_pointer {
break;
}
if !validate(frame_pointer as *const libc::c_void) {
break;
}
last_frame_pointer = frame_pointer;
let frame = Frame {
ip: unsafe { read_ptr(frame_pointer).ret },
};
if !cb(&frame) {
break;
}
frame_pointer = unsafe { read_ptr(frame_pointer).frame_pointer };
}
}
}
#[repr(C)]
struct FramePointerLayout {
frame_pointer: *mut FramePointerLayout,
ret: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[repr(C, align(64))]
struct AlignToSixtyFour([u8; 64]);
impl Default for AlignToSixtyFour {
fn default() -> Self {
AlignToSixtyFour([0; 64])
}
}
#[test]
fn test_read_ptr_aligned() {
let x = AlignToSixtyFour::default();
let ptr = &x.0[0] as *const u8;
assert_eq!(unsafe { read_ptr(ptr) }, x.0[0]);
}
#[test]
fn test_read_ptr_unaligned() {
let mut x = AlignToSixtyFour::default();
let expected = usize::MAX / 2;
x.0[1..9].copy_from_slice(&expected.to_ne_bytes()[..]);
let ptr: *const usize = unsafe { std::mem::transmute(&x.0[1] as *const u8) };
assert_eq!(unsafe { read_ptr(ptr) }, expected);
}
}