Skip to main content

imping_hook/
error.rs

1//! The error type returned by hooking operations.
2
3use core::fmt;
4
5/// The result of a hooking operation.
6pub type Result<T> = core::result::Result<T, Error>;
7
8/// Everything that can go wrong when finding, installing, enabling or disabling
9/// a hook.
10///
11/// Variants about a specific location carry its `address`, and those about a
12/// [`scan`](crate::scan) its pattern or module, which the
13/// [`Display`](fmt::Display) message names.
14#[derive(Debug, Clone, PartialEq, Eq)]
15#[non_exhaustive]
16pub enum Error {
17    /// A detour or injection is already installed at this address; hooks
18    /// aren't chained.
19    AlreadyHooked {
20        /// The address that is already hooked.
21        address: usize,
22    },
23    /// The target is too small to patch and has no usable padding or hot-patch
24    /// area.
25    NotEnoughSpace {
26        /// The address that could not be patched.
27        address: usize,
28    },
29    /// A displaced instruction can't be relocated to the trampoline, such as a
30    /// relative branch that would be out of range.
31    UnrelocatableInstruction {
32        /// The address of the offending instruction.
33        address: usize,
34    },
35    /// The target does not begin with a valid instruction.
36    InvalidInstruction {
37        /// The address that could not be decoded.
38        address: usize,
39    },
40    /// No executable memory could be allocated within branch range of the
41    /// target.
42    NoNearMemory {
43        /// The address no nearby memory could be found for.
44        address: usize,
45    },
46    /// The address is not executable memory.
47    NotExecutable {
48        /// The address that is not executable.
49        address: usize,
50    },
51    /// The target and the detour are the same address.
52    SameAddress {
53        /// The shared address.
54        address: usize,
55    },
56    /// A third party changed the code at the address since the hook was
57    /// installed, so it can't be toggled safely.
58    TargetModified {
59        /// The address whose code was modified.
60        address: usize,
61    },
62    /// A thread suspended for patching is running code that can't be
63    /// relocated.
64    ThreadNotRelocatable,
65    /// The assembly supplied to an injection could not be encoded.
66    Assembly {
67        /// The address the injection was being built for.
68        address: usize,
69        /// The assembler's message, prefixed with the instruction's position
70        /// when one instruction is invalid.
71        reason: String,
72    },
73    /// An operating system call failed, such as changing memory protection or
74    /// suspending a thread.
75    Os(OsError),
76    /// A [`scan`](crate::scan) pattern can't be parsed, or doesn't spell out the
77    /// call that [`follow_call`](crate::HookScan::follow_call) needs.
78    InvalidPattern {
79        /// The pattern as given.
80        pattern: String,
81        /// The byte position in `pattern` where it goes wrong.
82        position: usize,
83    },
84    /// A pattern matched nowhere in the module's code.
85    PatternNotFound {
86        /// The pattern as given.
87        pattern: String,
88    },
89    /// A pattern matched more than once, so it can't tell which place is meant.
90    PatternNotUnique {
91        /// The pattern as given.
92        pattern: String,
93        /// How many times it matched.
94        matches: usize,
95    },
96    /// No loaded module has the name a scan was given.
97    ModuleNotFound {
98        /// The module name as given.
99        name: String,
100    },
101}
102
103impl fmt::Display for Error {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            Error::AlreadyHooked { address } => {
107                write!(f, "address {address:#x} is already hooked")
108            }
109            Error::NotEnoughSpace { address } => {
110                write!(f, "not enough space to patch the target at {address:#x}")
111            }
112            Error::UnrelocatableInstruction { address } => {
113                write!(f, "the instruction at {address:#x} cannot be relocated")
114            }
115            Error::InvalidInstruction { address } => {
116                write!(f, "the target at {address:#x} is not a valid instruction")
117            }
118            Error::NoNearMemory { address } => {
119                write!(
120                    f,
121                    "no executable memory could be allocated near {address:#x}"
122                )
123            }
124            Error::NotExecutable { address } => {
125                write!(f, "the address {address:#x} is not executable")
126            }
127            Error::SameAddress { address } => {
128                write!(f, "the target and detour are the same address {address:#x}")
129            }
130            Error::TargetModified { address } => {
131                write!(f, "the code at {address:#x} was modified by a third party")
132            }
133            Error::ThreadNotRelocatable => {
134                f.write_str("a suspended thread is executing code that cannot be relocated")
135            }
136            Error::Assembly { address, reason } => {
137                write!(
138                    f,
139                    "the assembly for the injection at {address:#x} could not be encoded: {reason}"
140                )
141            }
142            Error::Os(_) => f.write_str("an operating system call failed"),
143            Error::InvalidPattern { pattern, position } => {
144                write!(f, "pattern `{pattern}` is invalid at position {position}")
145            }
146            Error::PatternNotFound { pattern } => {
147                write!(f, "pattern `{pattern}` matched nothing")
148            }
149            Error::PatternNotUnique { pattern, matches } => {
150                write!(f, "pattern `{pattern}` matched {matches} places, not one")
151            }
152            Error::ModuleNotFound { name } => write!(f, "module `{name}` is not loaded"),
153        }
154    }
155}
156
157impl core::error::Error for Error {
158    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
159        match self {
160            Error::Os(error) => Some(error),
161            _ => None,
162        }
163    }
164}
165
166impl Error {
167    /// Maps a [`detour::Error`] to ours, adding the `address` it lacks.
168    pub(crate) fn from_detour(error: detour::Error, address: usize) -> Self {
169        match error {
170            detour::Error::SameAddress => Error::SameAddress { address },
171            detour::Error::InvalidInstruction => Error::InvalidInstruction { address },
172            detour::Error::PatchAreaTooSmall => Error::NotEnoughSpace { address },
173            detour::Error::NotExecutable => Error::NotExecutable { address },
174            detour::Error::NoNearbyMemory => Error::NoNearMemory { address },
175            detour::Error::UnsupportedInstruction => Error::UnrelocatableInstruction { address },
176            detour::Error::TargetModified => Error::TargetModified { address },
177            detour::Error::ThreadNotRelocatable => Error::ThreadNotRelocatable,
178            detour::Error::Memory(os) => Error::Os(OsError::from_detour(os, "memory protection")),
179            detour::Error::Thread(os) => Error::Os(OsError::from_detour(os, "thread suspension")),
180            // Can't occur with the pinned detour version: hooks are initialized
181            // at construction, and Windows always has thread suspension. The
182            // wildcard is needed because `detour::Error` is non-exhaustive.
183            other => unreachable!("detour returned an error that cannot occur here: {other:?}"),
184        }
185    }
186}
187
188/// A failed operating system call, such as allocating or protecting memory or
189/// suspending a thread.
190///
191/// Carries a short description of the operation and, when available, the raw
192/// OS error code.
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct OsError {
195    /// The operating system error code (`GetLastError` on Windows), if known.
196    code: Option<i32>,
197    /// A short description of the operation that failed.
198    context: &'static str,
199}
200
201impl OsError {
202    /// Returns the operating system error code (`GetLastError` on Windows), if
203    /// one was recorded.
204    pub fn raw_os_error(&self) -> Option<i32> {
205        self.code
206    }
207
208    /// Wraps a [`detour::OsError`] with a description of the operation.
209    pub(crate) fn from_detour(error: detour::OsError, context: &'static str) -> Self {
210        OsError {
211            code: error.raw_os_error(),
212            context,
213        }
214    }
215
216    /// Wraps a [`region::Error`] with a description of the operation.
217    pub(crate) fn from_region(error: region::Error, context: &'static str) -> Self {
218        let code = match error {
219            region::Error::SystemCall(code) | region::Error::MachCall(code) => Some(code),
220            _ => None,
221        };
222        OsError { code, context }
223    }
224
225    /// Builds an error from the calling thread's `GetLastError` code.
226    #[cfg(windows)]
227    pub(crate) fn last_os_error(context: &'static str) -> Self {
228        // SAFETY: `GetLastError` has no preconditions.
229        let code = unsafe { windows_sys::Win32::Foundation::GetLastError() };
230        OsError {
231            code: Some(code as i32),
232            context,
233        }
234    }
235}
236
237impl fmt::Display for OsError {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        match self.code {
240            Some(code) => write!(
241                f,
242                "{} failed: {}",
243                self.context,
244                std::io::Error::from_raw_os_error(code)
245            ),
246            None => write!(f, "{} failed", self.context),
247        }
248    }
249}
250
251impl core::error::Error for OsError {}