pub fn trace<F>(f: F)
where
F: FnMut(u64) -> bool,
{
let mut ucontext: libc::ucontext_t = unsafe { std::mem::zeroed() };
#[cfg(target_os = "macos")]
{
let mut mcontext: libc::__darwin_mcontext64 = unsafe { std::mem::zeroed() };
ucontext.uc_mcontext = &mut mcontext as *mut libc::__darwin_mcontext64;
}
let ucontext = &mut ucontext as *mut libc::ucontext_t as *mut libc::c_void;
unsafe {
if getcontext(ucontext) != 0 {
return;
}
}
trace_from_ucontext(ucontext, f)
}
pub fn trace_from_ucontext<F>(ucontext: *mut libc::c_void, mut f: F)
where
F: FnMut(u64) -> bool,
{
let Registers { mut pc, mut fp } = match Registers::from_ucontext(ucontext) {
Some(v) => v,
None => return,
};
if !f(pc) {
return;
}
while fp != 0 {
pc = match load::<u64>(fp + 8) {
Some(v) => v,
None => return,
};
pc -= 1;
if !f(pc) {
return;
}
fp = match load::<u64>(fp) {
Some(v) => v,
None => return,
};
}
}
extern "C" {
fn getcontext(_ucontext: *mut libc::c_void) -> libc::c_int;
}
#[derive(Debug, Copy, Clone)]
struct Registers {
pc: u64,
fp: u64,
}
impl Registers {
#[cfg(all(target_arch = "x86_64", target_os = "linux"))]
fn from_ucontext(ucontext: *mut libc::c_void) -> Option<Self> {
let ucontext = ucontext as *mut libc::ucontext_t;
if ucontext.is_null() {
return None;
}
let mcontext = unsafe { (*ucontext).uc_mcontext };
Some(Self {
pc: mcontext.gregs[libc::REG_RIP as usize] as u64,
fp: mcontext.gregs[libc::REG_RBP as usize] as u64,
})
}
#[cfg(all(target_arch = "x86_64", target_os = "macos"))]
fn from_ucontext(ucontext: *mut libc::c_void) -> Option<Self> {
let ucontext = ucontext as *mut libc::ucontext_t;
if ucontext.is_null() {
return None;
}
unsafe {
let mcontext = (*ucontext).uc_mcontext;
if mcontext.is_null() {
return None;
}
Some(Self {
pc: (*mcontext).__ss.__rip,
fp: (*mcontext).__ss.__rbx,
})
}
}
#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
fn from_ucontext(ucontext: *mut libc::c_void) -> Option<Self> {
let ucontext = ucontext as *mut libc::ucontext_t;
if ucontext.is_null() {
return None;
}
let mcontext = unsafe { (*ucontext).uc_mcontext };
Some(Self {
pc: mcontext.pc,
fp: mcontext.regs[29],
})
}
#[cfg(all(target_arch = "aarch64", target_os = "macos"))]
fn from_ucontext(ucontext: *mut libc::c_void) -> Option<Self> {
let ucontext = ucontext as *mut libc::ucontext_t;
if ucontext.is_null() {
return None;
}
unsafe {
let mcontext = (*ucontext).uc_mcontext;
if mcontext.is_null() {
return None;
}
Some(Self {
pc: (*mcontext).__ss.__pc,
fp: (*mcontext).__ss.__fp,
})
}
}
}
#[inline]
#[cfg(not(feature = "memory-access-check"))]
fn load<T: Copy>(address: u64) -> Option<T> {
unsafe { Some(*(address as *const T)) }
}
#[inline]
#[cfg(feature = "memory-access-check")]
fn load<T: Copy>(address: u64) -> Option<T> {
if access_check::can_access(address) {
unsafe { Some(*(address as *const T)) }
} else {
None
}
}
#[cfg(feature = "memory-access-check")]
mod access_check {
use std::mem::MaybeUninit;
thread_local! {
static CAN_ACCESS_PIPE: [libc::c_int; 2] = {
unsafe {
let mut fds = MaybeUninit::<[libc::c_int; 2]>::uninit();
let res = create_pipe(fds.as_mut_ptr() as *mut libc::c_int);
if res == 0 {
[fds.assume_init()[0], fds.assume_init()[1]]
} else {
[-1, -1]
}
}
};
}
pub fn can_access(address: u64) -> bool {
CAN_ACCESS_PIPE.with(|pipes| unsafe {
if pipes[0] == -1 || pipes[1] == -1 {
return false;
}
let mut buffer = [0u8; 8];
let can_read = loop {
let size = libc::read(pipes[0], buffer.as_mut_ptr() as _, buffer.len() as _);
if size == -1 {
match errno() {
libc::EINTR => continue,
libc::EAGAIN => break true,
_ => break false,
}
} else if size > 0 {
break true;
}
};
if !can_read {
return false;
}
loop {
let size = libc::write(pipes[1], address as _, 1);
if size == -1 {
match errno() {
libc::EINTR => continue,
libc::EAGAIN => break true,
_ => break false,
}
} else if size > 0 {
break true;
}
}
})
}
#[inline]
#[cfg(target_os = "linux")]
unsafe fn create_pipe(fds: *mut libc::c_int) -> libc::c_int {
libc::pipe2(fds, libc::O_CLOEXEC | libc::O_NONBLOCK)
}
#[cfg(target_os = "macos")]
unsafe fn create_pipe(fds: *mut libc::c_int) -> libc::c_int {
let res = libc::pipe(fds);
if res != 0 {
return res;
}
let fds = fds as *mut [libc::c_int; 2];
for n in 0..2 {
let mut flags = libc::fcntl((*fds)[n], libc::F_GETFD);
flags |= libc::O_CLOEXEC;
let res = libc::fcntl((*fds)[n], libc::F_SETFD, flags);
if res != 0 {
return res;
}
let mut flags = libc::fcntl((*fds)[n], libc::F_GETFL);
flags |= libc::O_NONBLOCK;
let res = libc::fcntl((*fds)[n], libc::F_SETFL, flags);
if res != 0 {
return res;
}
}
0
}
#[inline]
#[cfg(target_os = "linux")]
fn errno() -> libc::c_int {
unsafe { (*libc::__errno_location()) as libc::c_int }
}
#[inline]
#[cfg(target_os = "macos")]
fn errno() -> libc::c_int {
unsafe { (*libc::__error()) as libc::c_int }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_can_access() {
let v1 = 1;
let v2 = Box::new(1);
assert!(can_access(&v1 as *const i32 as u64));
assert!(can_access(v2.as_ref() as *const i32 as u64));
assert!(!can_access(0));
assert!(!can_access(u64::MAX));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load() {
let val = i8::MIN;
let loc = &val as *const i8 as u64;
assert_eq!(load::<i8>(loc), Some(val));
let val = u64::MAX;
let loc = &val as *const u64 as u64;
assert_eq!(load::<u64>(loc), Some(val));
}
}