retour\arch/
detour.rs

1use super::memory;
2use crate::error::{Error, Result};
3use crate::{alloc, arch, util};
4use std::cell::UnsafeCell;
5use std::fmt;
6use std::sync::atomic::{AtomicBool, Ordering};
7
8/// An architecture-independent implementation of a base detour.
9///
10/// This class is never instantiated by itself, it merely exposes an API
11/// available through it's descendants.
12pub struct Detour {
13  #[allow(dead_code)]
14  relay: Option<alloc::ExecutableMemory>,
15  trampoline: alloc::ExecutableMemory,
16  patcher: UnsafeCell<arch::Patcher>,
17  enabled: AtomicBool,
18}
19
20impl Detour {
21  pub unsafe fn new(target: *const (), detour: *const ()) -> Result<Self> {
22    if target == detour {
23      Err(Error::SameAddress)?;
24    }
25
26    // Lock this so OS operations are not performed in parallell
27    let mut pool = memory::POOL.lock().unwrap();
28
29    if !util::is_executable_address(target)? || !util::is_executable_address(detour)? {
30      Err(Error::NotExecutable)?;
31    }
32
33    // Create a trampoline generator for the target function
34    let margin = arch::meta::prolog_margin(target);
35    let trampoline = arch::Trampoline::new(target, margin)?;
36
37    // A relay is used in case a normal branch cannot reach the destination
38    let relay = if let Some(emitter) = arch::meta::relay_builder(target, detour)? {
39      Some(memory::allocate_pic(&mut pool, &emitter, target)?)
40    } else {
41      None
42    };
43
44    // If a relay is supplied, use it instead of the detour address
45    let detour = relay
46      .as_ref()
47      .map(|code| code.as_ptr() as *const ())
48      .unwrap_or(detour);
49
50    Ok(Detour {
51      patcher: UnsafeCell::new(arch::Patcher::new(
52        target,
53        detour,
54        trampoline.prolog_size(),
55      )?),
56      trampoline: memory::allocate_pic(&mut pool, trampoline.emitter(), target)?,
57      enabled: AtomicBool::default(),
58      relay,
59    })
60  }
61
62  /// Enables the detour.
63  pub unsafe fn enable(&self) -> Result<()> {
64    self.toggle(true)
65  }
66
67  /// Disables the detour.
68  pub unsafe fn disable(&self) -> Result<()> {
69    self.toggle(false)
70  }
71
72  /// Returns whether the detour is enabled or not.
73  pub fn is_enabled(&self) -> bool {
74    self.enabled.load(Ordering::SeqCst)
75  }
76
77  /// Returns a reference to the generated trampoline.
78  pub fn trampoline(&self) -> &() {
79    unsafe {
80      (self.trampoline.as_ptr() as *const ())
81        .as_ref()
82        .expect("trampoline should not be null")
83    }
84  }
85
86  /// Enables or disables the detour.
87  unsafe fn toggle(&self, enabled: bool) -> Result<()> {
88    let _guard = memory::POOL.lock().unwrap();
89
90    if self.enabled.load(Ordering::SeqCst) == enabled {
91      return Ok(());
92    }
93
94    // Runtime code is by default only read-execute
95    let _handle = {
96      let area = (*self.patcher.get()).area();
97      region::protect_with_handle(
98        area.as_ptr(),
99        area.len(),
100        region::Protection::READ_WRITE_EXECUTE,
101      )
102    }?;
103
104    // Copy either the detour or the original bytes of the function
105    (*self.patcher.get()).toggle(enabled);
106    self.enabled.store(enabled, Ordering::SeqCst);
107    Ok(())
108  }
109}
110
111impl Drop for Detour {
112  /// Disables the detour, if enabled.
113  fn drop(&mut self) {
114    debug_assert!(unsafe { self.disable().is_ok() });
115  }
116}
117
118impl fmt::Debug for Detour {
119  /// Output whether the detour is enabled or not.
120  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
121    write!(
122      f,
123      "Detour {{ enabled: {}, trampoline: {:?} }}",
124      self.is_enabled(),
125      self.trampoline()
126    )
127  }
128}
129
130unsafe impl Send for Detour {}
131unsafe impl Sync for Detour {}