dinvk/
breakpoint.rs

1use core::ffi::c_void;
2use core::ptr::addr_of_mut;
3use core::sync::atomic::{Ordering, AtomicBool};
4
5use crate::{NtGetThreadContext, NtSetThreadContext};
6use crate::data::{
7    CONTEXT, CONTEXT_DEBUG_REGISTERS_AMD64, EXCEPTION_SINGLE_STEP,
8    EXCEPTION_CONTINUE_EXECUTION, EXCEPTION_CONTINUE_SEARCH, 
9    EXCEPTION_POINTERS, HANDLE, OBJECT_ATTRIBUTES,
10    CONTEXT_DEBUG_REGISTERS_X86
11};
12
13// Atomic variable to control the use of VEH.
14static USE_BREAKPOINT: AtomicBool = AtomicBool::new(false);
15
16/// Enables or disables the use of hardware breakpoints globally.
17///
18/// # Arguments
19/// 
20/// * `enabled` - Enables / Disables the use of hardware breakpoints
21///
22/// # Example
23/// 
24/// ```rust,ignore
25/// set_use_breakpoint(true);  // Enable breakpoints.
26/// set_use_breakpoint(false); // Disable breakpoints.
27/// ```
28#[inline(always)]
29pub fn set_use_breakpoint(enabled: bool) {
30    USE_BREAKPOINT.store(enabled, Ordering::SeqCst);
31}
32
33/// Checks if hardware breakpoints are currently enabled.
34///
35/// # Returns
36/// 
37/// * `true` - If breakpoints are enabled.
38/// * `false` - If breakpoints are disabled.
39///
40/// # Example
41/// 
42/// ```rust,ignore
43/// if is_breakpoint_enabled() {
44///     println!("Breakpoints are enabled!");
45/// } else {
46///     println!("Breakpoints are disabled.");
47/// }
48/// ```
49#[inline(always)]
50pub fn is_breakpoint_enabled() -> bool {
51    USE_BREAKPOINT.load(Ordering::SeqCst)
52}
53
54/// Configures a hardware breakpoint on the specified address.
55///
56/// This function sets a hardware breakpoint by manipulating the debug registers
57/// (specifically, `DR0`) for the current thread. The breakpoint will trigger an exception
58/// whenever the CPU executes the instruction at the specified address.
59///
60/// # Arguments
61/// 
62/// * `address` - The memory address where the hardware breakpoint should be set.
63///
64/// # Example
65/// ```rust,ignore
66/// set_breakpoint(0x7FFF_AA12_3ABC);  // Sets a breakpoint at the specified address.
67/// ```
68pub(crate) fn set_breakpoint<T: Into<u64>>(address: T) {
69    let mut ctx = CONTEXT {
70        ContextFlags: if cfg!(target_arch = "x86_64") { CONTEXT_DEBUG_REGISTERS_AMD64 } else { CONTEXT_DEBUG_REGISTERS_X86 },
71        ..Default::default()
72    };
73
74    NtGetThreadContext(-2isize as HANDLE, &mut ctx);
75
76    cfg_if::cfg_if! {
77        if #[cfg(target_arch = "x86_64")] {
78            ctx.Dr0 = address.into();
79            ctx.Dr6 = 0x00;
80            ctx.Dr7 = set_dr7_bits(ctx.Dr7, 0, 1, 1);
81        } else {
82            ctx.Dr0 = address.into() as u32;
83            ctx.Dr6 = 0x00;
84            ctx.Dr7 = set_dr7_bits(ctx.Dr7 as u64, 0, 1, 1) as u32;
85        }
86    }
87
88    NtSetThreadContext(-2isize as HANDLE, &ctx);
89}
90
91/// Modifies specific bits in the `DR7` register.
92///
93/// This helper function updates the `DR7` debug control register. It allows enabling,
94/// disabling, or modifying specific debug conditions for the hardware breakpoint.
95///
96/// # Arguments
97/// 
98/// * `current` - The current value of the `DR7` register.
99/// * `start_bit` - The starting bit index to modify.
100/// * `nmbr_bits` - The number of bits to modify.
101/// * `new_bit` - The new value to set for the specified bits.
102///
103/// # Returns
104/// 
105/// * The updated value of the `DR7` register.
106///
107/// # Example
108/// ```rust,ignore
109/// let updated_dr7 = set_dr7_bits(dr7, 0, 1, 1); // Enables the first debug condition.
110/// ``` 
111fn set_dr7_bits<T: Into<u64>>(current: T, start_bit: i32, nmbr_bits: i32, new_bit: u64) -> u64 {
112    let current = current.into();
113    let mask = (1u64 << nmbr_bits) - 1;
114    (current & !(mask << start_bit)) | (new_bit << start_bit)
115}
116
117/// Global mutable static holding the current Windows API call.
118pub static mut CURRENT_API: Option<WINAPI> = None;
119
120/// Enum representing different Windows API calls that can be used.
121#[derive(Debug)]
122pub enum WINAPI {
123    /// Represents the `NtAllocateVirtualMemory` call.
124    NtAllocateVirtualMemory {
125        ProcessHandle: HANDLE,
126        Protect: u32,
127    },
128
129    /// Represents the `NtProtectVirtualMemory` call.
130    NtProtectVirtualMemory {
131        ProcessHandle: HANDLE,
132        NewProtect: u32,
133    },
134
135    /// Represents the `NtCreateThreadEx` call.
136    NtCreateThreadEx {
137        ProcessHandle: HANDLE,
138        ThreadHandle: *mut HANDLE,
139        DesiredAccess: u32,
140        ObjectAttributes: *mut OBJECT_ATTRIBUTES
141    },
142
143    /// Represents the `NtWriteVirtualMemory` call.
144    NtWriteVirtualMemory {
145        ProcessHandle: HANDLE,
146        Buffer: *mut c_void,
147        NumberOfBytesToWrite: *mut usize,
148    },
149}
150
151/// Handles exceptions triggered by hardware breakpoints (x64).
152///
153/// # Arguments
154/// 
155/// * `exceptioninfo` - A pointer to the [`EXCEPTION_POINTERS`] structure containing information
156///   about the current exception, including the CPU context and exception code.
157///
158/// # Returns
159/// 
160/// * `EXCEPTION_CONTINUE_EXECUTION` - If the exception was handled.
161/// * `EXCEPTION_CONTINUE_SEARCH` - If the exception was not handled. 
162#[cfg(target_arch = "x86_64")]
163#[allow(unsafe_op_in_unsafe_fn)]
164pub unsafe extern "system" fn veh_handler(exceptioninfo: *mut EXCEPTION_POINTERS) -> i32 {
165    if !is_breakpoint_enabled() || (*(*exceptioninfo).ExceptionRecord).ExceptionCode != EXCEPTION_SINGLE_STEP {
166        return EXCEPTION_CONTINUE_SEARCH;
167    }
168
169    let context = (*exceptioninfo).ContextRecord;
170    if (*context).Rip == (*context).Dr0 && (*context).Dr7 & 1 == 1 {
171        if let Some(current) = (*addr_of_mut!(CURRENT_API)).take() {
172            match current {
173                WINAPI::NtAllocateVirtualMemory { 
174                    ProcessHandle, 
175                    Protect 
176                } => {
177                    (*context).R10 = ProcessHandle as u64;
178                    *(((*context).Rsp + 0x30) as *mut u32) = Protect;
179                },
180
181                WINAPI::NtProtectVirtualMemory { 
182                    ProcessHandle, 
183                    NewProtect, 
184                } => {
185                    (*context).R10 = ProcessHandle as u64;
186                    (*context).R9  = NewProtect as u64;
187                },
188
189                WINAPI::NtCreateThreadEx { 
190                    ProcessHandle,
191                    ThreadHandle,
192                    DesiredAccess,
193                    ObjectAttributes
194                } => {
195                    (*context).R10 = ThreadHandle as u64;
196                    (*context).Rdx = DesiredAccess as u64;
197                    (*context).R8  = ObjectAttributes as u64;
198                    (*context).R9  = ProcessHandle as u64;
199                },
200
201                WINAPI::NtWriteVirtualMemory { 
202                    ProcessHandle,
203                    Buffer,
204                    NumberOfBytesToWrite,
205                } => {
206                    (*context).R10 = ProcessHandle as u64;
207                    (*context).R8  = Buffer as u64;
208                    (*context).R9  = NumberOfBytesToWrite as u64;
209                }
210            }
211
212            (*context).Dr0 = 0x00;
213            (*context).Dr6 = 0x00;
214            (*context).Dr7 = set_dr7_bits((*context).Dr7, 0, 1, 0);
215        }
216
217        return EXCEPTION_CONTINUE_EXECUTION;
218    }
219
220    EXCEPTION_CONTINUE_SEARCH
221}
222
223/// Handles exceptions triggered by hardware breakpoints (x86).
224///
225/// # Arguments
226/// 
227/// * `exceptioninfo` - A pointer to the [`EXCEPTION_POINTERS`] structure containing information
228/// about the current exception, including the CPU context and exception code.
229///
230/// # Returns
231/// 
232/// * `EXCEPTION_CONTINUE_EXECUTION` - If the exception was handled.
233/// * `EXCEPTION_CONTINUE_SEARCH` - If the exception was not handled. 
234#[cfg(target_arch = "x86")]
235#[allow(unsafe_op_in_unsafe_fn)]
236pub unsafe extern "system" fn veh_handler(exceptioninfo: *mut EXCEPTION_POINTERS) -> i32 {
237    if !is_breakpoint_enabled() || (*(*exceptioninfo).ExceptionRecord).ExceptionCode != EXCEPTION_SINGLE_STEP {
238        return EXCEPTION_CONTINUE_SEARCH;
239    }
240
241    let context = (*exceptioninfo).ContextRecord;
242    if (*context).Eip == (*context).Dr0 && (*context).Dr7 & 1 == 1 {
243        if let Some(current) = (*addr_of_mut!(CURRENT_API)).take() {
244            match current {
245                WINAPI::NtAllocateVirtualMemory { 
246                    ProcessHandle, 
247                    Protect 
248                } => {
249                    *(((*context).Esp + 0x4) as *mut u32) = ProcessHandle as u32;
250                    *(((*context).Esp + 0x18) as *mut u32) = Protect;
251                },
252
253                WINAPI::NtProtectVirtualMemory { 
254                    ProcessHandle, 
255                    NewProtect, 
256                } => {
257                    *(((*context).Esp + 0x4) as *mut u32) = ProcessHandle as u32;
258                    *(((*context).Esp + 0x10) as *mut u32) = NewProtect as u32;
259                },
260
261                WINAPI::NtCreateThreadEx { 
262                    ProcessHandle,
263                    ThreadHandle,
264                    DesiredAccess,
265                    ObjectAttributes
266                } => {
267                    *(((*context).Esp + 0x4) as *mut u32) = ThreadHandle as u32;
268                    *(((*context).Esp + 0x8) as *mut u32) = DesiredAccess as u32;
269                    *(((*context).Esp + 0xC) as *mut u32) = ObjectAttributes as u32;
270                    *(((*context).Esp + 0x10) as *mut u32) = ProcessHandle as u32;
271                },
272
273                WINAPI::NtWriteVirtualMemory { 
274                    ProcessHandle,
275                    Buffer,
276                    NumberOfBytesToWrite,
277                } => {
278                    *(((*context).Esp + 0x4) as *mut u32) = ProcessHandle as u32;
279                    *(((*context).Esp + 0xC) as *mut u32) = Buffer as u32;
280                    *(((*context).Esp + 0x10) as *mut u32) = NumberOfBytesToWrite as u32;
281                }
282            }
283
284            (*context).Dr0 = 0x00;
285            (*context).Dr6 = 0x00;
286            (*context).Dr7 = set_dr7_bits((*context).Dr7, 0, 1, 0) as u32;
287        }
288
289        return EXCEPTION_CONTINUE_EXECUTION;
290    }
291
292    EXCEPTION_CONTINUE_SEARCH
293}