Skip to content

Commit

Permalink
feat: implement proper static detours
Browse files Browse the repository at this point in the history
  • Loading branch information
darfink committed Jun 17, 2017
1 parent 6c3aa72 commit 772e785
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 148 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
53 changes: 27 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`:
Expand All @@ -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
Expand All @@ -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);
}
```

Expand Down Expand Up @@ -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
[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
12 changes: 0 additions & 12 deletions src/example.rs

This file was deleted.

25 changes: 13 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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.
//!
Expand Down Expand Up @@ -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.
Expand All @@ -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
//!
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
118 changes: 45 additions & 73 deletions src/macros.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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<T>(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<Option<Box<Fn($($argument_type),*)
-> $return_type + Send>>> {
lazy_static! {
static ref CLOSURE:
::std::sync::Mutex<Option<Box<Fn($($argument_type),*) -> $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.
Expand Down
2 changes: 1 addition & 1 deletion src/variant/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
8 changes: 8 additions & 0 deletions src/variant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
}
}
2 changes: 1 addition & 1 deletion src/variant/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ lazy_static! {
};
}

/// Implementation of an inline detour.
/// A type-less detour.
///
/// # Example
///
Expand Down
Loading

0 comments on commit 772e785

Please sign in to comment.