diff --git a/docs/dev/design/assembly-hooks/overview.md b/docs/dev/design/assembly-hooks/overview.md index bbef021..b6a2812 100644 --- a/docs/dev/design/assembly-hooks/overview.md +++ b/docs/dev/design/assembly-hooks/overview.md @@ -138,6 +138,8 @@ The following table below shows common hook lengths, for: !!! info "Extra: [Thread Safety on x86](../common.md#thread-safety-on-x86)" +### Stub Memory Layout + In order to support thread safety, while retaining maximum runtime performance, the buffers where the original and hook code are contained have a very specific memory layout (shown below) @@ -150,7 +152,7 @@ original and hook code are contained have a very specific memory layout (shown b Emplacing the jump to the hook function itself, and patching within the hook function should be atomic whenever it is possible on the platform. -### Example +#### Example If the *'Original Code'* was: @@ -185,6 +187,28 @@ hook: ; Backup (Hook) b back_to_code ``` +### Heap Layout + +Each Assembly Hook contains a pointer to the heap stub (seen above) and a pointer to the heap. + +The heap contains all information required to perform operations on the stub. + +```text +- AssemblyHookPackedProps + - Enabled Flag + - Offset of Hook Function (Also length of HookFunction/OriginalCode block) + - Offset of Original Code +- [Hook Function / Original Code] +``` + +The data in the heap contains a short 'AssemblyHookPackedProps' struct, detailing the data that is required +to make a temporary branch to the stub/hook function. After that is the either the `hook function` bytes or +the `original code` bytes, depending on the state of the hook. + +The hook uses a 'swapping' system, where the `[Hook Function / Original Code]` block in the stub is swapped +with the `[Hook Function / Original Code]` block in the heap. When one contains the code for `Hook Function`, +the other contains the code for `Original Code`. This is memory efficient. + ### Switching State !!! info "When transitioning between Enabled/Disabled state, we place a temporary branch at `entry`, this allows us to manipulate the remaining code safely." @@ -212,8 +236,9 @@ This means a few functionalities must be supported here: Assembly hook info is packed by default to save on memory space. By default, the following limits apply: -| Property | 4 Byte Instruction (e.g. ARM) | x86 | Unknown | -| -------------------- | ----------------------------- | ------ | ------- | -| Max Branch Length | 4 | 5 | 8 | -| Max Orig Code Length | 16KiB | 4KiB | 128MiB | -| Max Hook Code Length | 2MiB | 128KiB | 1GiB | +| Property | 4 Byte Instruction (e.g. ARM64) | Other (e.g. x86) | +| -------------------- | ------------------------------- | ---------------- | +| Max Orig Code Length | 128KiB | 32KiB | +| Max Hook Code Length | 128KiB | 32KiB | + +!!! note "These limits may increase in the future if additional functionality warrants extending metadata length." diff --git a/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook.rs b/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook.rs index 430ea18..640708c 100644 --- a/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook.rs +++ b/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook.rs @@ -9,21 +9,34 @@ use crate::{ settings::assembly_hook_settings::AssemblyHookSettings, traits::register_info::RegisterInfo, }, - helpers::atomic_write_masked::atomic_write_masked, + helpers::{ + atomic_write_masked::atomic_write_masked, jit_jump_operation::create_jump_operation, + }, internal::assembly_hook::create_assembly_hook, }; - -use core::marker::PhantomData; +use alloc::vec::Vec; use core::ptr::NonNull; - -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -use super::assembly_hook_props_x86::*; - -#[cfg(target_arch = "aarch64")] +use core::{marker::PhantomData, slice::from_raw_parts_mut}; + +#[cfg(any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "mips", + target_arch = "powerpc", + target_arch = "riscv32", + target_arch = "riscv64" +))] use super::assembly_hook_props_4byteins::*; -#[cfg(not(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")))] -use super::assembly_hook_props_unknown::*; +#[cfg(not(any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "mips", + target_arch = "powerpc", + target_arch = "riscv32", + target_arch = "riscv64" +)))] +use super::assembly_hook_props_other::*; /// Represents an assembly hook. #[repr(C)] // Not 'packed' because this is not in array and malloc in practice will align this. @@ -142,19 +155,45 @@ where } /// Writes the hook to memory, either enabling or disabling it based on the provided parameters. - fn write_hook(&self, branch_opcode: &[u8], code: &[u8], num_bytes: usize) { - // Write the branch first, as per docs + unsafe fn swap_hook(&self, temp_branch_offset: usize) { + let props = self.props.as_ref(); + + // Backup current code from swap buffer. + let swap_buffer_real = props.get_swap_buffer(); + let swap_buffer_copy = swap_buffer_real.to_vec(); + + // Copy current code into swap buffer + let buf_buffer_real = + from_raw_parts_mut(self.stub_address as *mut u8, props.get_swap_size()); + swap_buffer_real.copy_from_slice(buf_buffer_real); + + // JIT temp branch to hook/orig code. + let mut vec = Vec::::with_capacity(8); + _ = create_jump_operation::( + self.stub_address, + true, + self.stub_address + temp_branch_offset, + None, + &mut vec, + ); + let branch_opcode = &vec; + let branch_bytes = branch_opcode.len(); + + // Write the temp branch first, as per docs // This also overwrites some extra code afterwards, but that's a-ok for now. unsafe { - atomic_write_masked::(self.stub_address, branch_opcode, num_bytes); + atomic_write_masked::(self.stub_address, branch_opcode, branch_bytes); } // Now write the remaining code - TBuffer::overwrite(self.stub_address + num_bytes, &code[num_bytes..]); + TBuffer::overwrite( + self.stub_address + branch_bytes, + &swap_buffer_copy[branch_bytes..], + ); // And now re-insert the code we temp overwrote with the branch unsafe { - atomic_write_masked::(self.stub_address, code, num_bytes); + atomic_write_masked::(self.stub_address, &swap_buffer_copy, branch_bytes); } } @@ -164,13 +203,13 @@ where /// If the hook is disabled, this function will write the hook to memory. pub fn enable(&self) { unsafe { - let props = self.props.as_ref(); - let num_bytes = props.get_branch_to_hook_len(); - self.write_hook( - props.get_branch_to_hook_slice(), - props.get_enabled_code(), - num_bytes, - ); + let props = &mut (*self.props.as_ptr()); + if props.is_enabled() { + return; + }; + + self.swap_hook(props.get_swap_size()); + props.set_is_enabled(true); } } @@ -180,13 +219,13 @@ where /// If the hook is enabled, this function will no-op the hook. pub fn disable(&self) { unsafe { - let props = self.props.as_ref(); - let num_bytes = props.get_branch_to_orig_len(); - self.write_hook( - props.get_branch_to_orig_slice(), - props.get_disabled_code(), - num_bytes, - ); + let props = &mut (*self.props.as_ptr()); + if !props.is_enabled() { + return; + }; + + self.swap_hook(props.get_swap_size() + props.get_hook_fn_size()); + props.set_is_enabled(false); } } diff --git a/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_4byteins.rs b/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_4byteins.rs index 4345c5f..0d2d5be 100644 --- a/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_4byteins.rs +++ b/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_4byteins.rs @@ -1,68 +1,48 @@ use bitfield::bitfield; bitfield! { - /// Defines the data layout of the Assembly Hook data. - /// For architectures which use 4 byte instructions. + /// Defines the data layout of the Assembly Hook data for architectures + /// with fixed instruction sizes of 4 bytes. pub struct AssemblyHookPackedProps(u32); impl Debug; /// True if the hook is enabled, else false. pub is_enabled, set_is_enabled: 0; - /// Length of the 'disabled code' array. - u16, disabled_code_len, set_disabled_code_len_impl: 12, 1; // Max 16KiB. + reserved, _: 1; - /// Length of the 'enabled code' array. - u32, enabled_code_len, set_enabled_code_len_impl: 31, 13; // Max 2MiB. + /// Size of the 'swap' space where hook function and original code are swapped out. + /// Represented in number of 4-byte instructions. + u16, swap_size, set_swap_size_impl: 16, 2; // Max 32Ki instructions, 128KiB. + + /// Size of the 'swap' space where hook function and original code are swapped out. + /// Represented in number of 4-byte instructions. + u16, hook_fn_size, set_hook_fn_size_impl: 31, 17; // Max 32Ki instructions, 128KiB. } impl AssemblyHookPackedProps { - /// Gets the length of the 'enabled code' array. - pub fn get_enabled_code_len(&self) -> usize { - self.enabled_code_len() as usize * 4 - } - - /// Gets the length of the 'disabled code' array. - pub fn get_disabled_code_len(&self) -> usize { - self.disabled_code_len() as usize * 4 - } - - /// Sets the length of the 'enabled code' array with a minimum value of 4. - pub fn set_enabled_code_len(&mut self, len: usize) { - debug_assert!( - len >= 4 && len % 4 == 0, - "Length must be a multiple of 4 and at least 4" - ); - self.set_enabled_code_len_impl((len / 4) as u32); + pub fn get_swap_size(&self) -> usize { + (self.swap_size() as usize) * 4 // Convert from instructions to bytes } - /// Sets the length of the 'disabled code' array with a minimum value of 4. - pub fn set_disabled_code_len(&mut self, len: usize) { + pub fn set_swap_size(&mut self, size: usize) { debug_assert!( - len >= 4 && len % 4 == 0, - "Length must be a multiple of 4 and at least 4" + size % 4 == 0 && size <= 128 * 1024, + "Swap size must be a multiple of 4 and at most 128KiB" ); - self.set_disabled_code_len_impl((len / 4) as u16); - } - - /// Sets the 'branch to orig' length field based on the provided length. - pub fn set_branch_to_hook_len(&mut self, _len: usize) { - // no-op, for API compatibility - } - - /// Gets the length of the 'branch to hook' array. Always 4 for AArch64. - pub fn get_branch_to_hook_len(&self) -> usize { - 4 + self.set_swap_size_impl((size / 4) as u16); // Convert from bytes to instructions } - /// Sets the 'branch to orig' length field based on the provided length. - pub fn set_branch_to_orig_len(&mut self, _len: usize) { - // no-op, for API compatibility + pub fn get_hook_fn_size(&self) -> usize { + (self.hook_fn_size() as usize) * 4 // Convert from instructions to bytes } - /// Gets the length of the 'branch to orig' array. Always 4 for AArch64. - pub fn get_branch_to_orig_len(&self) -> usize { - 4 + pub fn set_hook_fn_size(&mut self, size: usize) { + debug_assert!( + size % 4 == 0 && size <= 128 * 1024, + "Hook function size must be a multiple of 4 and at most 128KiB" + ); + self.set_hook_fn_size_impl((size / 4) as u16); // Convert from bytes to instructions } } @@ -71,34 +51,35 @@ mod tests { use super::*; #[test] - fn test_enabled_and_disabled_code_lengths() { + fn test_swap_and_hook_fn_sizes() { let mut props = AssemblyHookPackedProps(0); - // Test setting and getting enabled code length - props.set_enabled_code_len(123 * 4); // Multiples of 4 - assert_eq!(props.get_enabled_code_len(), 123 * 4); + // Test setting and getting swap size + props.set_swap_size(1024); // 256 instructions + assert_eq!(props.get_swap_size(), 1024); + + // Test setting and getting hook function size + props.set_hook_fn_size(2048); // 512 instructions + assert_eq!(props.get_hook_fn_size(), 2048); - // Test setting and getting disabled code length - props.set_disabled_code_len(456 * 4); // Multiples of 4 - assert_eq!(props.get_disabled_code_len(), 456 * 4); + // Test upper limits + props.set_swap_size(127 * 1024); // 32Ki instructions + props.set_hook_fn_size(127 * 1024); // 32Ki instructions + assert_eq!(props.get_swap_size(), 127 * 1024); + assert_eq!(props.get_hook_fn_size(), 127 * 1024); } #[test] - #[should_panic(expected = "Length must be a multiple of 4 and at least 4")] - fn test_invalid_code_length() { + #[should_panic(expected = "Swap size must be a multiple of 4 and at most 128KiB")] + fn test_swap_size_limit() { let mut props = AssemblyHookPackedProps(0); - props.set_enabled_code_len(5); // Should panic, not a multiple of 4 + props.set_swap_size(129 * 1024); // Should panic } #[test] - fn test_branch_lengths() { + #[should_panic(expected = "Hook function size must be a multiple of 4 and at most 128KiB")] + fn test_hook_fn_size_limit() { let mut props = AssemblyHookPackedProps(0); - - // Setting and getting branch lengths, always 4 for AArch64 - props.set_branch_to_hook_len(4); - assert_eq!(props.get_branch_to_hook_len(), 4); - - props.set_branch_to_orig_len(4); - assert_eq!(props.get_branch_to_orig_len(), 4); + props.set_hook_fn_size(129 * 1024); // Should panic } } diff --git a/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_common.rs b/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_common.rs index 089daea..753fd99 100644 --- a/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_common.rs +++ b/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_common.rs @@ -3,54 +3,44 @@ use alloc::vec::Vec; use core::{ mem::size_of, ptr::{copy_nonoverlapping, NonNull}, - slice::from_raw_parts, + slice::from_raw_parts_mut, }; -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -use super::assembly_hook_props_x86::AssemblyHookPackedProps; - -#[cfg(target_arch = "aarch64")] +#[cfg(any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "mips", + target_arch = "powerpc", + target_arch = "riscv32", + target_arch = "riscv64" +))] use super::assembly_hook_props_4byteins::AssemblyHookPackedProps; -#[cfg(not(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")))] -use super::assembly_hook_props_unknown::AssemblyHookPackedProps; +#[cfg(not(any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "mips", + target_arch = "powerpc", + target_arch = "riscv32", + target_arch = "riscv64" +)))] +use super::assembly_hook_props_other::AssemblyHookPackedProps; /* Memory Layout: - AssemblyHookPackedProps - - disabled_code_instructions - - enabled_code_instructions - - branch_to_orig_instructions - - branch_to_hook_instructions + - Enabled Flag + - Offset of Hook Function (Also length of HookFunction/OriginalCode block) + - Offset of Original Code + - enabled_code_instructions / disabled_code_instructions */ // Implement common methods on AssemblyHookPackedProps impl AssemblyHookPackedProps { - pub fn get_disabled_code<'a>(&self) -> &'a [u8] { + pub fn get_swap_buffer<'a>(&self) -> &'a mut [u8] { let start_addr = self as *const Self as *const u8; let offset = size_of::(); - unsafe { from_raw_parts(start_addr.add(offset), self.get_disabled_code_len()) } - } - - pub fn get_enabled_code<'a>(&self) -> &'a [u8] { - let start_addr = self as *const Self as *const u8; - let offset = size_of::() + self.get_disabled_code_len(); - unsafe { from_raw_parts(start_addr.add(offset), self.get_enabled_code_len()) } - } - - pub fn get_branch_to_orig_slice<'a>(&self) -> &'a [u8] { - let start_addr = self as *const Self as *const u8; - let offset = size_of::() + self.get_disabled_code_len() + self.get_enabled_code_len(); - unsafe { from_raw_parts(start_addr.add(offset), self.get_branch_to_hook_len()) } - } - - pub fn get_branch_to_hook_slice<'a>(&self) -> &'a [u8] { - let start_addr = self as *const Self as *const u8; - let offset = size_of::() - + self.get_disabled_code_len() - + self.get_enabled_code_len() - + self.get_branch_to_orig_len(); - unsafe { from_raw_parts(start_addr.add(offset), self.get_branch_to_orig_len()) } + unsafe { from_raw_parts_mut(start_addr.add(offset) as *mut u8, self.get_swap_size()) } } /// Frees the memory allocated for this instance using libc's free. diff --git a/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_other.rs b/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_other.rs new file mode 100644 index 0000000..fc8ed67 --- /dev/null +++ b/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_other.rs @@ -0,0 +1,80 @@ +use bitfield::bitfield; + +bitfield! { + /// Defines the data layout of the Assembly Hook data for architectures + /// with variable length instructions. + pub struct AssemblyHookPackedProps(u32); + impl Debug; + + /// True if the hook is enabled, else false. + pub is_enabled, set_is_enabled: 0; + + reserved, _: 1; + + /// Size of the 'swap' space where hook function and original code are swapped out. + u16, swap_size, set_swap_size_impl: 16, 2; // Max 32KiB. + + /// Size of the 'swap' space where hook function and original code are swapped out. + u16, hook_fn_size, set_hook_fn_size_impl: 31, 17; // Max 32KiB. +} + +impl AssemblyHookPackedProps { + pub fn get_swap_size(&self) -> usize { + self.swap_size() as usize + } + + pub fn set_swap_size(&mut self, size: usize) { + debug_assert!(size <= 32 * 1024, "Swap size must be at most 32KiB"); + self.set_swap_size_impl(size as u16); + } + + pub fn get_hook_fn_size(&self) -> usize { + self.hook_fn_size() as usize + } + + pub fn set_hook_fn_size(&mut self, size: usize) { + debug_assert!( + size <= 32 * 1024, + "Hook function size must be at most 32KiB" + ); + self.set_hook_fn_size_impl(size as u16); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_swap_and_hook_fn_sizes() { + let mut props = AssemblyHookPackedProps(0); + + // Test setting and getting swap size + props.set_swap_size(1024); + assert_eq!(props.get_swap_size(), 1024); + + // Test setting and getting hook function size + props.set_hook_fn_size(2048); + assert_eq!(props.get_hook_fn_size(), 2048); + + // Test upper limits + props.set_swap_size(32 * 1024 - 1); + props.set_hook_fn_size(32 * 1024 - 1); + assert_eq!(props.get_swap_size(), 32 * 1024 - 1); + assert_eq!(props.get_hook_fn_size(), 32 * 1024 - 1); + } + + #[test] + #[should_panic(expected = "Swap size must be at most 32KiB")] + fn test_swap_size_limit() { + let mut props = AssemblyHookPackedProps(0); + props.set_swap_size(33 * 1024); // Should panic + } + + #[test] + #[should_panic(expected = "Hook function size must be at most 32KiB")] + fn test_hook_fn_size_limit() { + let mut props = AssemblyHookPackedProps(0); + props.set_hook_fn_size(33 * 1024); // Should panic + } +} diff --git a/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_unknown.rs b/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_unknown.rs deleted file mode 100644 index 0f0055e..0000000 --- a/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_unknown.rs +++ /dev/null @@ -1,111 +0,0 @@ -use bitfield::bitfield; - -bitfield! { - /// Defines the data layout of the Assembly Hook data for unknown architectures. - pub struct AssemblyHookPackedProps(u64); - impl Debug; - - /// True if the hook is enabled, else false. - pub is_enabled, set_is_enabled: 0; - - /// Length of 'branch to hook' array. - u8, branch_to_hook_len, set_branch_to_hook_len_impl: 3, 1; - - /// Length of 'branch to hook' array. - u8, branch_to_orig_len, set_branch_to_orig_len_impl: 6, 4; - - /// Length of the 'disabled code' array. - u32, disabled_code_len, set_disabled_code_len_impl: 33, 7; // Max 128MiB. - - /// Length of the 'enabled code' array. - u32, enabled_code_len, set_enabled_code_len_impl: 63, 34; // Max 1GiB. -} - -impl AssemblyHookPackedProps { - /// Gets the length of the 'enabled code' array. - pub fn get_enabled_code_len(&self) -> usize { - self.enabled_code_len() as usize - } - - /// Gets the length of the 'disabled code' array. - pub fn get_disabled_code_len(&self) -> usize { - self.disabled_code_len() as usize - } - - /// Sets the length of the 'enabled code' array with a minimum value of 1. - pub fn set_enabled_code_len(&mut self, len: usize) { - debug_assert!(len >= 1, "Length must be at least 1"); - self.set_enabled_code_len_impl(len as u32); // Adjust for 'unknown' architecture - } - - /// Sets the length of the 'disabled code' array with a minimum value of 1. - pub fn set_disabled_code_len(&mut self, len: usize) { - debug_assert!(len >= 1, "Length must be at least 1"); - self.set_disabled_code_len_impl(len as u32); // Adjust for 'unknown' architecture - } - - /// Sets the length of the 'branch to hook' array. - pub fn set_branch_to_hook_len(&mut self, len: usize) { - self.set_branch_to_hook_len_impl(len as u8); - } - - /// Gets the length of the 'branch to hook' array. - pub fn get_branch_to_hook_len(&self) -> usize { - self.branch_to_hook_len() as usize - } - - /// Sets the length of the 'branch to orig' array. - pub fn set_branch_to_orig_len(&mut self, len: usize) { - self.set_branch_to_orig_len_impl(len as u8); - } - - /// Gets the length of the 'branch to orig' array. - pub fn get_branch_to_orig_len(&self) -> usize { - self.branch_to_orig_len() as usize - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_enabled_and_disabled_code_lengths() { - let mut props = AssemblyHookPackedProps(0); - - // Test setting and getting enabled code length - props.set_enabled_code_len(123); - assert_eq!(props.get_enabled_code_len(), 123); - - // Test setting and getting disabled code length - props.set_disabled_code_len(456); - assert_eq!(props.get_disabled_code_len(), 456); - } - - #[test] - #[should_panic(expected = "Length must be at least 1")] - fn test_enabled_code_length_minimum() { - let mut props = AssemblyHookPackedProps(0); - props.set_enabled_code_len(0); // Should panic - } - - #[test] - #[should_panic(expected = "Length must be at least 1")] - fn test_disabled_code_length_minimum() { - let mut props = AssemblyHookPackedProps(0); - props.set_disabled_code_len(0); // Should panic - } - - #[test] - fn test_branch_lengths() { - let mut props = AssemblyHookPackedProps(0); - - // Test setting and getting branch to hook length - props.set_branch_to_hook_len(3); - assert_eq!(props.get_branch_to_hook_len(), 3); - - // Test setting and getting branch to orig length - props.set_branch_to_orig_len(4); - assert_eq!(props.get_branch_to_orig_len(), 4); - } -} diff --git a/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_x86.rs b/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_x86.rs deleted file mode 100644 index 35ccf20..0000000 --- a/projects/reloaded-hooks-portable/src/api/hooks/assembly/assembly_hook_props_x86.rs +++ /dev/null @@ -1,145 +0,0 @@ -use bitfield::bitfield; - -bitfield! { - /// Defines the data layout of the Assembly Hook data for x86. - pub struct AssemblyHookPackedProps(u32); - impl Debug; - - /// True if the hook is enabled, else false. - pub is_enabled, set_is_enabled: 0; - - /// If true, 'branch to hook' array is 5 bytes instead of 2 bytes on x86. - is_long_branch_to_hook_len, set_is_long_branch_to_hook_len: 1; - - /// If true, 'branch to orig' array is 5 bytes instead of 2 bytes on x86. - is_long_branch_to_orig_len, set_is_long_branch_to_orig_len: 2; - - /// Length of the 'disabled code' array. - u16, disabled_code_len, set_disabled_code_len_impl: 14, 3; // Max 4KiB. - - /// Length of the 'enabled code' array. - u32, enabled_code_len, set_enabled_code_len_impl: 31, 15; // Max 128KiB. -} - -impl AssemblyHookPackedProps { - /// Gets the length of the 'enabled code' array. - pub fn get_enabled_code_len(&self) -> usize { - self.enabled_code_len() as usize - } - - /// Gets the length of the 'disabled code' array. - pub fn get_disabled_code_len(&self) -> usize { - self.disabled_code_len() as usize - } - - /// Sets the length of the 'enabled code' array with a minimum value of 1. - pub fn set_enabled_code_len(&mut self, len: usize) { - debug_assert!(len >= 1, "Length must be at least 1"); - self.set_enabled_code_len_impl(len as u32); - } - - /// Sets the length of the 'disabled code' array with a minimum value of 1. - pub fn set_disabled_code_len(&mut self, len: usize) { - debug_assert!(len >= 1, "Length must be at least 1"); - self.set_disabled_code_len_impl(len as u16); - } - - /// Sets the 'branch to orig' length field based on the provided length. - pub fn set_branch_to_hook_len(&mut self, len: usize) { - debug_assert!(len == 2 || len == 5, "Length must be either 2 or 5"); - self.set_is_long_branch_to_hook_len(len == 5); - } - - /// Gets the length of the 'branch to hook' array. - pub fn get_branch_to_hook_len(&self) -> usize { - if self.is_long_branch_to_hook_len() { - 5 - } else { - 2 - } - } - - /// Sets the 'branch to orig' length field based on the provided length. - pub fn set_branch_to_orig_len(&mut self, len: usize) { - debug_assert!(len == 2 || len == 5, "Length must be either 2 or 5"); - self.set_is_long_branch_to_orig_len(len == 5); - } - - /// Gets the length of the 'branch to orig' array. - pub fn get_branch_to_orig_len(&self) -> usize { - if self.is_long_branch_to_orig_len() { - 5 - } else { - 2 - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_enabled_and_disabled_code_lengths() { - let mut props = AssemblyHookPackedProps(0); - - // Test setting and getting enabled code length - props.set_enabled_code_len(123); - assert_eq!(props.get_enabled_code_len(), 123); - - // Test setting and getting disabled code length - props.set_disabled_code_len(456); - assert_eq!(props.get_disabled_code_len(), 456); - - // Test minimum length enforcement - props.set_enabled_code_len(1); - props.set_disabled_code_len(1); - assert_eq!(props.enabled_code_len(), 1); - assert_eq!(props.disabled_code_len(), 1); - } - - #[test] - #[should_panic(expected = "Length must be at least 1")] - fn test_enabled_code_length_minimum() { - let mut props = AssemblyHookPackedProps(0); - props.set_enabled_code_len(0); // Should panic - } - - #[test] - #[should_panic(expected = "Length must be at least 1")] - fn test_disabled_code_length_minimum() { - let mut props = AssemblyHookPackedProps(0); - props.set_disabled_code_len(0); // Should panic - } - - #[test] - fn test_branch_lengths() { - let mut props = AssemblyHookPackedProps(0); - - // Test setting and getting branch to hook length - props.set_branch_to_hook_len(2); - assert_eq!(props.get_branch_to_hook_len(), 2); - props.set_branch_to_hook_len(5); - assert_eq!(props.get_branch_to_hook_len(), 5); - - // Test setting and getting branch to orig length - props.set_branch_to_orig_len(2); - assert_eq!(props.get_branch_to_orig_len(), 2); - props.set_branch_to_orig_len(5); - assert_eq!(props.get_branch_to_orig_len(), 5); - } - - #[test] - #[should_panic(expected = "Length must be either 2 or 5")] - fn test_invalid_branch_to_hook_length() { - let mut props = AssemblyHookPackedProps(0); - props.set_branch_to_hook_len(3); // Should panic - } - - #[test] - #[should_panic(expected = "Length must be either 2 or 5")] - fn test_invalid_branch_to_orig_length() { - let mut props = AssemblyHookPackedProps(0); - props.set_branch_to_orig_len(4); // Should panic - } -} diff --git a/projects/reloaded-hooks-portable/src/internal/assembly_hook.rs b/projects/reloaded-hooks-portable/src/internal/assembly_hook.rs index feb5009..88e84b2 100644 --- a/projects/reloaded-hooks-portable/src/internal/assembly_hook.rs +++ b/projects/reloaded-hooks-portable/src/internal/assembly_hook.rs @@ -18,8 +18,7 @@ use crate::{ }, helpers::{ allocate_with_proximity::allocate_with_proximity, - jit_jump_operation::create_jump_operation, make_inline_rel_branch::INLINE_BRANCH_LEN, - overwrite_code::overwrite_code, + jit_jump_operation::create_jump_operation, overwrite_code::overwrite_code, }, }; use alloc::vec::Vec; @@ -31,14 +30,25 @@ use core::{ slice::from_raw_parts, }; -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -use crate::api::hooks::assembly::assembly_hook_props_x86::*; - -#[cfg(target_arch = "aarch64")] +#[cfg(any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "mips", + target_arch = "powerpc", + target_arch = "riscv32", + target_arch = "riscv64" +))] use crate::api::hooks::assembly::assembly_hook_props_4byteins::*; -#[cfg(not(any(target_arch = "aarch64", target_arch = "x86", target_arch = "x86_64")))] -use crate::api::hooks::assembly::assembly_hook_props_unknown::*; +#[cfg(not(any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "mips", + target_arch = "powerpc", + target_arch = "riscv32", + target_arch = "riscv64" +)))] +use crate::api::hooks::assembly::assembly_hook_props_other::*; /// Creates an assembly hook at a specified location in memory. /// @@ -153,13 +163,8 @@ where // Reusable code buffers. let max_vec_len = max(hook_code_max_length, hook_orig_max_length); - let mut props_buf = Vec::::with_capacity( - max_vec_len - + size_of::() - + hook_code_max_length - + hook_orig_max_length - + INLINE_BRANCH_LEN * 2, - ); + let mut props_buf = + Vec::::with_capacity(max_vec_len + size_of::()); // Reserve space for AssemblyHookPackedProps, and get a pointer to it. #[allow(clippy::uninit_vec)] @@ -167,7 +172,8 @@ where let props: &mut AssemblyHookPackedProps = unsafe { &mut *(props_buf.as_mut_ptr() as *mut AssemblyHookPackedProps) }; - let mut code_buf = Vec::::with_capacity(max_vec_len); + let mut code_buf_1 = Vec::::with_capacity(max_vec_len); + let mut code_buf_2 = Vec::::with_capacity(max_vec_len); let hook_params = HookFunctionCommonParams { behaviour: settings.behaviour, asm_code: settings.asm_code, @@ -184,72 +190,69 @@ where // - orig: Original Code // 'Original Code' @ entry - let orig_at_entry_start = props_buf.as_ptr().add(props_buf.len()) as usize; TRewriter::rewrite_code_with_buffer( settings.hook_address as *const u8, orig_code_length, settings.hook_address, buf_addr, settings.scratch_register.clone(), - &mut props_buf, + &mut code_buf_1, ) .map_err(|e| new_rewrite_error(OriginalCode, settings.hook_address, buf_addr, e))?; - let rewritten_len = props_buf - .as_ptr() - .add(props_buf.len()) - .sub(orig_at_entry_start) as usize; create_jump_operation::( - buf_addr.wrapping_add(rewritten_len as usize), + buf_addr.wrapping_add(code_buf_1.len() as usize), alloc_result.0, jump_back_address, settings.scratch_register.clone(), - &mut props_buf, + &mut code_buf_1, ) .map_err(|e| AssemblyHookError::JitError(e))?; - let orig_at_entry_end = props_buf.as_ptr().add(props_buf.len()) as usize; // 'Hook Function' @ entry construct_hook_function::( &hook_params, buf_addr, - &mut props_buf, + &mut code_buf_2, )?; - let hook_at_entry_end = props_buf.as_ptr().add(props_buf.len()) as usize; - // Write the default code. - let enabled_len = hook_at_entry_end - orig_at_entry_end; - let enabled_code = from_raw_parts(orig_at_entry_end as *const u8, enabled_len); - let disabled_len = orig_at_entry_end - orig_at_entry_start; - let disabled_code = from_raw_parts(orig_at_entry_start as *const u8, disabled_len); + let enabled_len = code_buf_2.len(); + let disabled_len = code_buf_1.len(); + let swap_space_len = max(enabled_len, disabled_len); - TBuffer::overwrite( - buf_addr, - if settings.auto_activate { - enabled_code - } else { - disabled_code - }, - ); + let enabled_code = from_raw_parts(code_buf_2.as_ptr(), enabled_len); + let disabled_code = from_raw_parts(code_buf_1.as_ptr(), disabled_len); + let old_len = props_buf.len(); + if settings.auto_activate { + TBuffer::overwrite(buf_addr, enabled_code); + props_buf.extend_from_slice(disabled_code); + } else { + TBuffer::overwrite(buf_addr, disabled_code); + props_buf.extend_from_slice(enabled_code); + } + + props_buf.set_len(old_len + swap_space_len); + code_buf_1.clear(); + code_buf_2.clear(); props.set_is_enabled(settings.auto_activate); - props.set_enabled_code_len(enabled_len); - props.set_disabled_code_len(disabled_len); + props.set_swap_size(swap_space_len); + props.set_hook_fn_size(enabled_len); // Write the other 2 stubs. - let entry_end_ptr = buf_addr + max(enabled_code.len(), disabled_code.len()); + let entry_end_ptr = buf_addr + swap_space_len; // 'Hook Function' @ hook construct_hook_function::( &hook_params, entry_end_ptr, - &mut code_buf, + &mut code_buf_1, )?; - TBuffer::overwrite(entry_end_ptr, &code_buf); - let hook_at_hook_end = entry_end_ptr + code_buf.len(); - code_buf.clear(); + TBuffer::overwrite(entry_end_ptr, &code_buf_1); + let hook_at_hook_end = entry_end_ptr + code_buf_1.len(); + code_buf_1.clear(); // 'Original Code' @ orig TRewriter::rewrite_code_with_buffer( @@ -258,44 +261,19 @@ where settings.hook_address, hook_at_hook_end, settings.scratch_register.clone(), - &mut code_buf, + &mut code_buf_1, ) .map_err(|e| new_rewrite_error(OrigCodeAtOrig, settings.hook_address, hook_at_hook_end, e))?; create_jump_operation::( - hook_at_hook_end.wrapping_add(code_buf.len()), + hook_at_hook_end.wrapping_add(code_buf_1.len()), alloc_result.0, jump_back_address, settings.scratch_register.clone(), - &mut code_buf, - ) - .map_err(|e| AssemblyHookError::JitError(e))?; - TBuffer::overwrite(hook_at_hook_end, &code_buf); - let orig_at_orig_len = code_buf.len(); - - // Branch `entry -> orig` - // Branch `entry -> hook` - let branch_to_orig_start = props_buf.as_ptr().add(props_buf.len()); - create_jump_operation::( - buf_addr, - true, - entry_end_ptr, - settings.scratch_register.clone(), - &mut props_buf, - ) - .map_err(|e| AssemblyHookError::JitError(e))?; - - let branch_to_hook_start = props_buf.as_ptr().add(props_buf.len()); - props.set_branch_to_orig_len(branch_to_hook_start.sub(branch_to_orig_start as usize) as usize); - create_jump_operation::( - buf_addr, - true, - hook_at_hook_end, - settings.scratch_register.clone(), - &mut props_buf, + &mut code_buf_1, ) .map_err(|e| AssemblyHookError::JitError(e))?; - let branch_to_hook_end = props_buf.as_ptr().add(props_buf.len()); - props.set_branch_to_hook_len(branch_to_hook_end.sub(branch_to_hook_start as usize) as usize); + TBuffer::overwrite(hook_at_hook_end, &code_buf_1); + let orig_at_orig_len = code_buf_1.len(); // Now JIT a jump to the original code. overwrite_code(settings.hook_address, &code); diff --git a/projects/reloaded-hooks-portable/src/lib.rs b/projects/reloaded-hooks-portable/src/lib.rs index 89528cd..b441a56 100644 --- a/projects/reloaded-hooks-portable/src/lib.rs +++ b/projects/reloaded-hooks-portable/src/lib.rs @@ -31,8 +31,7 @@ pub mod api { pub mod assembly_hook; pub mod assembly_hook_props_4byteins; pub mod assembly_hook_props_common; - pub mod assembly_hook_props_unknown; - pub mod assembly_hook_props_x86; + pub mod assembly_hook_props_other; } }