From 772e7851f337624d674a1e35f0c905ad9177593c Mon Sep 17 00:00:00 2001 From: Elliott Linder Date: Sat, 17 Jun 2017 14:04:13 +0200 Subject: [PATCH] feat: implement proper static detours --- Cargo.toml | 4 +- README.md | 53 +++++++++--------- src/example.rs | 12 ----- src/lib.rs | 25 ++++----- src/macros.rs | 118 ++++++++++++++++------------------------- src/variant/generic.rs | 2 +- src/variant/mod.rs | 8 +++ src/variant/raw.rs | 2 +- src/variant/statik.rs | 113 +++++++++++++++++++++++++++++++++++++++ tests/lib.rs | 47 ++++++++-------- 10 files changed, 236 insertions(+), 148 deletions(-) delete mode 100644 src/example.rs create mode 100644 src/variant/statik.rs diff --git a/Cargo.toml b/Cargo.toml index 7d226441..083f8873 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,8 @@ repository = "https://github.com/darfink/detour-rs" version = "0.1.0" [features] -default = ["example"] -example = [] +default = ["static"] +static = [] [dependencies] boolinator = "2.4.0" diff --git a/README.md b/README.md index eb032340..0e420a4b 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ detour-rs [![Travis build status][travis-shield]][travis] [![Appveyor build status][appveyor-shield]][appveyor] [![crates.io version][crate-shield]][crate] +[![Documentation][docs-shield]][docs] [![Language (Rust)][rust-shield]][rust] This is a cross-platform detour library developed in Rust. Beyond the basic @@ -15,16 +16,14 @@ maintain this feature, not all desired functionality can be supported due to lack of cross-platform APIs. Therefore [EIP relocation](#appendix) is not supported. +**NOTE**: Nightly is currently required, mostly due to *untagged_union*. + ## Platforms - `x86`: Windows, Linux, macOS - `x64`: Windows, Linux, macOS - `ARM`: Not implemented, but foundation exists. -## Documentation - -https://docs.rs/detour - ## Installation Add this to your `Cargo.toml`: @@ -45,7 +44,6 @@ extern crate detour; ```rust #[macro_use] extern crate detour; -#[macro_use] extern crate lazy_static; extern "C" fn add(x: i32, y: i32) -> i32 { x + y @@ -55,26 +53,27 @@ static_detours! { struct DetourAdd: extern "C" fn(i32, i32) -> i32; } -#[test] -fn static_hook() { - unsafe { - let mut hook = DetourAdd::initialize(add, |x, y| x - y).unwrap(); - - assert_eq!(add(10, 5), 15); - assert_eq!(hook.is_enabled(), false); - - hook.enable().unwrap(); - { - assert!(hook.is_enabled()); - assert_eq!(hook.call(10, 5), 15); - assert_eq!(add(10, 5), 5); - } - hook.disable().unwrap(); - - assert_eq!(hook.is_enabled(), false); - assert_eq!(hook.call(10, 5), 15); - assert_eq!(add(10, 5), 15); - } +fn main() { + // Replace the add function with a closure that subtracts + let mut hook = unsafe { DetourAdd.initialize(add, |x, y| x - y).unwrap() }; + + assert_eq!(add(1, 5), 6); + assert_eq!(hook.is_enabled(), false); + + unsafe { hook.enable().unwrap(); } + + assert_eq!(add(1, 5), -4); + assert_eq!(hook.call(1, 5), 6); + + // Change the detour whilst hooked + hook.set_detour(|x, y| x * y); + assert_eq!(add(5, 5), 25); + + unsafe { hook.disable().unwrap(); } + + assert_eq!(hook.is_enabled(), false); + assert_eq!(hook.call(1, 5), 6); + assert_eq!(add(1, 5), 6); } ``` @@ -110,4 +109,6 @@ fn static_hook() { [crate-shield]: https://img.shields.io/crates/v/detour.svg?style=flat-square [crate]: https://crates.io/crates/detour [rust-shield]: https://img.shields.io/badge/powered%20by-rust-blue.svg?style=flat-square -[rust]: https://www.rust-lang.org \ No newline at end of file +[rust]: https://www.rust-lang.org +[docs-shield]: https://img.shields.io/badge/docs-github-green.svg?style=flat-square +[docs]: https://darfink.github.io/detour-rs/detour/index.html \ No newline at end of file diff --git a/src/example.rs b/src/example.rs deleted file mode 100644 index 458572d9..00000000 --- a/src/example.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Example of code generated by a static detour. **IT MUST NOT BE USED OUTSIDE -//! THIS CRATE**. -//! -//! ```c -//! static_detours! { -//! pub struct Detour: extern "system" fn(i32, String, bool) -> f32; -//! } -//! ``` - -static_detours! { - pub struct Static: extern "system" fn(i32, String, bool) -> f32; -} diff --git a/src/lib.rs b/src/lib.rs index a5626b0d..383bc3c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,7 @@ #![recursion_limit = "1024"] -#![feature(range_contains)] -#![feature(offset_to)] -#![cfg_attr(test, feature(naked_functions))] -#![cfg_attr(test, feature(core_intrinsics))] -#![cfg_attr(test, feature(asm))] +#![feature(range_contains, offset_to)] +#![cfg_attr(feature = "static", feature(const_fn, unboxed_closures))] +#![cfg_attr(test, feature(naked_functions, core_intrinsics, asm))] //! A cross-platform detour library written in Rust. //! @@ -31,7 +29,7 @@ //! prototype is enforced for both the target and the detour. //! It is also enforced when invoking the original target. //! -//! - [Static](./macro.static_detours.html): A static & type-safe interface. +//! - [Static](./struct.StaticDetour.html): A static & type-safe interface. //! Thanks to its static nature it can accept a closure as its second //! argument, but on the other hand, it can only have one detour active at a //! time. @@ -42,7 +40,13 @@ //! It should be avoided unless the types used aren't known until runtime. //! //! All detours implement the [Detour](./trait.Detour.html) trait, which exposes -//! several methods, and enforces `Sync + Send`. +//! several methods, and enforces `Send + Sync`. Therefore you must also include +//! it into your scope whenever you are using a detour. +//! +//! ## Features +//! +//! - **static**: Enabled by default. Includes the static detour functionality, +//! but requires the nightly features *const_fn* & *unboxed_closures*. //! //! ## Procedure //! @@ -97,8 +101,8 @@ extern crate region; extern crate slice_pool; // Re-exports -pub use variant::{RawDetour, GenericDetour}; -pub use traits::{Detour, Function, HookableWith}; +pub use variant::*; +pub use traits::*; #[macro_use] mod macros; @@ -111,9 +115,6 @@ mod pic; mod util; mod variant; -#[cfg(feature = "example")] -pub mod example; - cfg_if! { if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { mod x86; diff --git a/src/macros.rs b/src/macros.rs index 17218448..3548bd52 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,51 +1,55 @@ -/// Macro for defining type-safe detours. +/// A macro for defining type-safe detours. /// -/// This macro uses `RawDetour` for its implementation, but it exposes its -/// functionality in a type-safe wrapper. -/// The macro can contain one or more definitions — each definition will become a -/// distinct type. +/// This macro creates a `StaticDetourController`, which returns a `StaticDetour` +/// upon initialization. It can accept both functions and closures as its second +/// argument. Due to the requirements of the implementation, *const_fn* is needed +/// if the macro is to be used. /// -/// A type may only have one active detour at a time. Therefore another -/// `initialize` call can only be done once the previous detour has been dropped. -/// This is due to the closures being stored as static variables. +/// A static detour may only have one active detour at a time. Therefore another +/// `initialize` call can only be done once the previous instance has been +/// dropped. This is because the closures are being stored as static variables. /// /// # Example /// /// ```rust -/// # #[macro_use] extern crate lazy_static; -/// # #[macro_use] extern crate detour; -/// # use detour::Detour; -/// # fn main() { unsafe { example() } } +/// #![feature(const_fn)] +/// #[macro_use] extern crate detour; +/// +/// use detour::Detour; +/// /// static_detours! { -/// struct Test: /* [extern "X"] */ fn(i32) -> i32; +/// struct Test: /* extern "X" */ fn(i32) -> i32; /// } /// /// fn add5(val: i32) -> i32 { val + 5 } /// fn add10(val: i32) -> i32 { val + 10 } /// -/// unsafe fn example() { -/// // The detour can also be a closure -/// let mut hook = Test::initialize(add5, add10).unwrap(); +/// fn main() { +/// let mut hook = unsafe { Test.initialize(add5, add10).unwrap() }; /// -/// assert_eq!(add5(5), 10); -/// assert_eq!(hook.call(5), 10); +/// assert_eq!(add5(1), 6); +/// assert_eq!(hook.call(1), 6); /// -/// hook.enable().unwrap(); +/// unsafe { hook.enable().unwrap(); } /// -/// assert_eq!(add5(5), 15); -/// assert_eq!(hook.call(5), 10); +/// assert_eq!(add5(1), 11); +/// assert_eq!(hook.call(1), 6); /// -/// hook.disable().unwrap(); +/// // You can also call using the static object +/// assert_eq!(unsafe { Test.get().unwrap().call(1) }, 6); /// -/// assert_eq!(add5(5), 10); -/// } -/// ``` +/// // ... and change the detour whilst hooked +/// hook.set_detour(|val| val - 5); +/// assert_eq!(add5(5), 0); /// -/// Any type of function is supported, and `extern` is optional. +/// unsafe { hook.disable().unwrap() }; /// -/// There is also an example module [available](example/index.html). +/// assert_eq!(add5(1), 6); +/// } +/// ``` /// -/// **NOTE: Requires [lazy_static](https://crates.io/crates/lazy_static).** +/// Any type of function is supported, and *extern* is optional. +#[cfg(feature = "static")] #[macro_export] // Inspired by: https://github.com/Jascha-N/minhook-rs macro_rules! static_detours { @@ -125,56 +129,24 @@ macro_rules! static_detours { ($name:ident) ($($modifier:tt)*) ($($argument_type:ty)*) ($return_type:ty) ($fn_type:ty)) => { static_detours!(@generate + #[allow(non_upper_case_globals)] $(#[$attribute])* - $($visibility)* struct $name(()); - ); - static_detours!(@generate - impl $name { - /// Constructs a new detour for the target, and initializes the - /// static mutex with the supplied closure. - pub unsafe fn initialize(target: $fn_type, closure: T) -> - $crate::error::Result<$crate::GenericDetour<$fn_type>> - where T: Fn($($argument_type),*) -> $return_type + Send + 'static { - let mut static_closure = Self::closure().lock().unwrap(); - if static_closure.is_some() { - Err($crate::error::ErrorKind::AlreadyExisting.into()) - } else { - let detour = $crate::GenericDetour::<$fn_type>::new(target, Self::callback)?; - *static_closure = Some(Box::new(closure)); - Ok(detour) - } - } - - #[doc(hidden)] - #[allow(dead_code)] - $($modifier) * fn callback($($argument_name: $argument_type),*) -> $return_type { - Self::closure().lock().unwrap() - .as_ref().expect("calling detour closure; is null")($($argument_name),*) - } + $($visibility)* static $name: $crate::StaticDetourController<$fn_type> = { + use std::sync::atomic::{AtomicPtr, Ordering}; + use std::ptr; - fn closure() -> &'static ::std::sync::Mutex $return_type + Send>>> { - lazy_static! { - static ref CLOSURE: - ::std::sync::Mutex $return_type + Send>>> = - ::std::sync::Mutex::new(None); - } + static DATA: AtomicPtr<$crate::__StaticDetourInner<$fn_type>> = + AtomicPtr::new(ptr::null_mut()); - &*CLOSURE + #[inline(never)] + $($modifier) * fn __ffi_detour($($argument_name: $argument_type),*) -> $return_type { + let data = unsafe { DATA.load(Ordering::SeqCst).as_ref().unwrap() }; + (data.closure)($($argument_name),*) } - } + + $crate::StaticDetourController::__new(&DATA, __ffi_detour) + }; ); - //static_detours!(@generate - // impl Drop for $name { - // /// Disables the detour and frees the associated closure. - // fn drop(&mut self) { - // unsafe { - // self.0.disable().expect("disabling detour on drop"); - // *Self::closure().lock().unwrap() = None; - // } - // } - // } - //); }; // Associates each argument type with a dummy name. diff --git a/src/variant/generic.rs b/src/variant/generic.rs index 5a14ce24..b20d59b4 100644 --- a/src/variant/generic.rs +++ b/src/variant/generic.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use error::*; use {Detour, Function, HookableWith, RawDetour}; -/// This is a type-safe wrapper around [RawDetour](./struct.RawDetour.html). +/// A type-safe wrapper around [RawDetour](./struct.RawDetour.html). /// /// Therefore documentation related to `RawDetour` affects this interface as well. /// diff --git a/src/variant/mod.rs b/src/variant/mod.rs index 713242e8..09b5c836 100644 --- a/src/variant/mod.rs +++ b/src/variant/mod.rs @@ -3,3 +3,11 @@ mod raw; pub use self::generic::*; pub use self::raw::*; + +cfg_if! { + if #[cfg(feature = "static")] { + mod statik; + pub use self::statik::*; + } else { + } +} diff --git a/src/variant/raw.rs b/src/variant/raw.rs index a3193ab0..5dcb704a 100644 --- a/src/variant/raw.rs +++ b/src/variant/raw.rs @@ -12,7 +12,7 @@ lazy_static! { }; } -/// Implementation of an inline detour. +/// A type-less detour. /// /// # Example /// diff --git a/src/variant/statik.rs b/src/variant/statik.rs new file mode 100644 index 00000000..2e2713e4 --- /dev/null +++ b/src/variant/statik.rs @@ -0,0 +1,113 @@ +use std::sync::atomic::{AtomicPtr, Ordering}; +use std::ops::{Deref, DerefMut}; +use std::{mem, ptr}; + +use error::*; +use {Function, GenericDetour}; + +#[doc(hidden)] +pub struct __StaticDetourInner { + pub closure: Box>, + pub detour: GenericDetour, +} + +/// An instantiator for [StaticDetour](./struct.StaticDetour.html). +/// +/// This is the type used by the [static_detours!](./macro.static_detours.html) +/// macro, it cannot be created without it. +pub struct StaticDetourController { + inner: &'static AtomicPtr<__StaticDetourInner>, + ffi: T, +} + +impl StaticDetourController { + #[doc(hidden)] + pub const fn __new(inner: &'static AtomicPtr<__StaticDetourInner>, ffi: T) -> Self { + StaticDetourController { inner, ffi } + } + + /// Creates a [StaticDetour](./struct.StaticDetour.html) initialized with a + /// target and a closure. + /// + /// If the detour has already been initialized, but it has not gone out of + /// scope, an `AlreadyExisting` error will be thrown. + pub unsafe fn initialize(&self, target: T, closure: C) -> Result> + where C: Fn + Send + 'static { + let mut boxed = Box::new(__StaticDetourInner { + detour: GenericDetour::new(target, self.ffi)?, + closure: Box::new(closure), + }); + + if !self.inner.compare_and_swap(ptr::null_mut(), &mut *boxed, Ordering::SeqCst).is_null() { + bail!(ErrorKind::AlreadyExisting) + } + + mem::forget(boxed); + Ok(StaticDetour(self.inner)) + } + + /// Returns a reference to the underlying detour. + /// + /// It is mostly provided so the original function can easily be called + /// within a detour. + /// + /// This is unsafe because the lifetime of the detour has no relation to the + /// actual detour returned by `initialize`. Therefore it can be dropped at + /// any time. Prefer to use the handle returned from `initialize` whenever + /// possible. + pub unsafe fn get(&self) -> Option<&GenericDetour> { + self.inner.load(Ordering::SeqCst).as_ref().map(|i| &i.detour) + } +} + +/// A type-safe static detour. +/// +/// It can only be created using +/// [StaticDetourController::initialize](struct.StaticDetourController.html#method.initialize). +/// +/// When this object has been dropped, the detour is freed and the controller can +/// be initialized once again. +/// It dereferences to `GenericDetour` so it provides the same functions that it +/// (and `Detour`) provides. +/// +/// Beyond this it also provides a `set_detour` method, enabling the detour to be +/// changed whilst hooked. +/// +/// To see an example view the [macro's page](macro.static_detours.html). +pub struct StaticDetour(&'static AtomicPtr<__StaticDetourInner>); + +impl StaticDetour { + /// Changes the detour, regardless of whether the target is hooked or not. + pub fn set_detour(&mut self, closure: C) + where C: Fn + Send + 'static { + let data = unsafe { self.0.load(Ordering::SeqCst).as_mut().unwrap() }; + data.closure = Box::new(closure); + } +} + +impl Drop for StaticDetour { + /// Removes the detour and frees the controller for new initializations. + fn drop(&mut self) { + let data = self.0.swap(ptr::null_mut(), Ordering::SeqCst); + assert_eq!(data.is_null(), false); + unsafe { Box::from_raw(data) }; + } +} + +impl Deref for StaticDetour { + type Target = GenericDetour; + + fn deref(&self) -> &GenericDetour { + unsafe { + &self.0.load(Ordering::SeqCst).as_ref().unwrap().detour + } + } +} + +impl DerefMut for StaticDetour { + fn deref_mut(&mut self) -> &mut GenericDetour { + unsafe { + &mut self.0.load(Ordering::SeqCst).as_mut().unwrap().detour + } + } +} diff --git a/tests/lib.rs b/tests/lib.rs index 4b90303a..550ceea6 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,5 +1,6 @@ -#[macro_use] extern crate lazy_static; -#[macro_use] extern crate detour; +#![cfg_attr(feature = "static", feature(const_fn))] +#[cfg_attr(feature = "static", macro_use)] +extern crate detour; extern crate volatile_cell; use std::mem; @@ -67,29 +68,33 @@ fn generic() { } } -static_detours! { - pub struct DetourAdd: extern "C" fn(i32, i32) -> i32; -} +#[cfg(feature = "static")] +mod static_test { + use super::*; -#[test] -fn static_hook() { - unsafe { - let mut hook = DetourAdd::initialize(add, |x, y| x - y).unwrap(); + static_detours! { + pub struct DetourAdd: extern "C" fn(i32, i32) -> i32; + } - assert_eq!(add(10, 5), 15); - assert_eq!(hook.is_enabled(), false); + #[test] + fn static_hook() { + unsafe { + let mut hook = DetourAdd.initialize(add, |x, y| x - y).unwrap(); - hook.enable().unwrap(); - { - assert!(hook.is_enabled()); + assert_eq!(add(10, 5), 15); + assert_eq!(hook.is_enabled(), false); + + hook.enable().unwrap(); + { + assert!(hook.is_enabled()); + assert_eq!(hook.call(10, 5), 15); + assert_eq!(add(10, 5), 5); + } + hook.disable().unwrap(); + + assert_eq!(hook.is_enabled(), false); assert_eq!(hook.call(10, 5), 15); - assert_eq!(add(10, 5), 5); + assert_eq!(add(10, 5), 15); } - hook.disable().unwrap(); - - assert_eq!(hook.is_enabled(), false); - assert_eq!(hook.call(10, 5), 15); - assert_eq!(add(10, 5), 15); } } -