use core::sync::atomic::{AtomicUsize, Ordering};
use crate::abi::HookFlags;
mod sealed {
pub trait Sealed {}
}
pub unsafe trait FnPtr: Copy + Send + Sync + 'static + sealed::Sealed {}
macro_rules! fn_ptr {
($($arg:ident),*) => {
impl<R: 'static, $($arg: 'static),*> sealed::Sealed for extern "system" fn($($arg),*) -> R {}
unsafe impl<R: 'static, $($arg: 'static),*> FnPtr for extern "system" fn($($arg),*) -> R {}
impl<R: 'static, $($arg: 'static),*> sealed::Sealed
for unsafe extern "system" fn($($arg),*) -> R {}
unsafe impl<R: 'static, $($arg: 'static),*> FnPtr
for unsafe extern "system" fn($($arg),*) -> R {}
};
}
fn_ptr!();
fn_ptr!(A);
fn_ptr!(A, B);
fn_ptr!(A, B, C);
fn_ptr!(A, B, C, D);
fn_ptr!(A, B, C, D, E);
fn_ptr!(A, B, C, D, E, G);
fn_ptr!(A, B, C, D, E, G, H);
fn_ptr!(A, B, C, D, E, G, H, I);
fn_ptr!(A, B, C, D, E, G, H, I, J);
fn_ptr!(A, B, C, D, E, G, H, I, J, K);
fn_ptr!(A, B, C, D, E, G, H, I, J, K, L);
fn_ptr!(A, B, C, D, E, G, H, I, J, K, L, M);
const _: () = assert!(size_of::<extern "system" fn()>() == size_of::<usize>());
#[derive(Debug)]
pub struct Hook<F: FnPtr> {
name: &'static str,
detour: F,
next: AtomicUsize,
priority: i32,
flags: HookFlags,
}
impl<F: FnPtr> Hook<F> {
#[must_use]
pub const fn new(name: &'static str, detour: F, priority: i32, flags: HookFlags) -> Self {
Self {
name,
detour,
next: AtomicUsize::new(0),
priority,
flags,
}
}
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
#[must_use]
pub const fn entry(&self) -> F {
self.detour
}
#[must_use]
pub fn next(&'static self) -> Next<F> {
Next {
slot: &self.next,
_f: core::marker::PhantomData,
}
}
pub fn after_next<R>(&self, work: impl FnOnce() -> R) -> Option<R> {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(work))
.inspect_err(|_| fault_after_next(self.name))
.ok()
}
pub(crate) fn spec(&'static self, target: u64) -> crate::abi::HookSpec {
crate::abi::HookSpec {
target,
detour: address(self.detour),
next: &raw const self.next,
priority: self.priority,
flags: self.flags,
}
}
}
#[cold]
#[inline(never)]
fn fault_after_next(hook: &str) {
if let Some(host) = crate::host() {
host.fault(&format!("panic in hook {hook} after next"));
}
}
fn address<F: FnPtr>(f: F) -> usize {
unsafe { core::mem::transmute_copy(&f) }
}
#[derive(Debug, Clone, Copy)]
pub struct Next<F: FnPtr> {
slot: &'static AtomicUsize,
_f: core::marker::PhantomData<F>,
}
impl<F: FnPtr> Next<F> {
#[inline]
#[must_use]
pub fn get(self) -> F {
let address = self.slot.load(Ordering::Acquire);
assert!(
address != 0,
"Next::get on a hook the loader has not linked"
);
unsafe { core::mem::transmute_copy(&address) }
}
}
#[macro_export]
macro_rules! detour {
(
$(#[$meta:meta])*
$vis:vis static $name:ident : unsafe extern "system" fn($($arg:ident : $ty:ty),* $(,)?) $(-> $ret:ty)?
= $body:path $(, priority = $priority:expr)? $(, flags = $flags:expr)? $(,)?;
) => {
$crate::detour!(@emit [$(#[$meta])*] $vis $name [unsafe] ($($arg : $ty),*) ($($ret)?) $body
[$($priority)?] [$($flags)?]);
};
(
$(#[$meta:meta])*
$vis:vis static $name:ident : extern "system" fn($($arg:ident : $ty:ty),* $(,)?) $(-> $ret:ty)?
= $body:path $(, priority = $priority:expr)? $(, flags = $flags:expr)? $(,)?;
) => {
$crate::detour!(@emit [$(#[$meta])*] $vis $name [] ($($arg : $ty),*) ($($ret)?) $body
[$($priority)?] [$($flags)?]);
};
(@emit [$($meta:tt)*] $vis:vis $name:ident [$($unsafe:tt)?] ($($arg:ident : $ty:ty),*) ($($ret:ty)?) $body:path
[$($priority:expr)?] [$($flags:expr)?]) => {
$($meta)*
$vis static $name: $crate::Hook<$($unsafe)? extern "system" fn($($ty),*) $(-> $ret)?> = {
const _: () = {
$($crate::__private::hook_argument::<$ty>();)*
};
const BODY: fn($crate::Next<$($unsafe)? extern "system" fn($($ty),*) $(-> $ret)?>, $($ty),*) $(-> $ret)? = $body;
$($unsafe)? extern "system" fn __enshwick_entry($($arg: $ty),*) $(-> $ret)? {
let next = $name.next();
match ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| BODY(next, $($arg),*))) {
::core::result::Result::Ok(value) => value,
::core::result::Result::Err(_) => {
$crate::__private::fault_in(::core::stringify!($name));
$crate::detour!(@call [$($unsafe)?] next ($($arg),*))
}
}
}
$crate::Hook::new(
::core::stringify!($name),
__enshwick_entry,
$crate::__private::or_default(&[$($priority)?], 0),
$crate::__private::or_default(&[$($flags)?], $crate::abi::HookFlags::NONE),
)
};
};
(@call [unsafe] $next:ident ($($arg:ident),*)) => {
unsafe { $next.get()($($arg),*) }
};
(@call [] $next:ident ($($arg:ident),*)) => {
$next.get()($($arg),*)
};
}
#[cfg(test)]
mod tests {
use super::Hook;
use crate::abi::HookFlags;
extern "system" fn original() {}
static UNLINKED: Hook<extern "system" fn()> =
Hook::new("UNLINKED", original, 0, HookFlags::NONE);
#[test]
#[should_panic(expected = "not linked")]
fn an_unlinked_next_panics_instead_of_calling_null() {
let _never = UNLINKED.next().get();
}
}