move platform specific into arm file

This commit is contained in:
Lysann Tranvouez 2025-10-01 22:43:05 +02:00
parent ccaa4bfbf0
commit db603acded
3 changed files with 193 additions and 156 deletions

View file

@ -5,8 +5,12 @@
#ifndef MACH_DETOURS_ARM64_H #ifndef MACH_DETOURS_ARM64_H
#define MACH_DETOURS_ARM64_H #define MACH_DETOURS_ARM64_H
#include "detours_internal.h"
#include <stdint.h> #include <stdint.h>
#include <mach/mach.h>
typedef struct detour_trampoline typedef struct detour_trampoline
{ {
// An ARM64 instruction is 4 bytes long. // An ARM64 instruction is 4 bytes long.
@ -45,9 +49,10 @@ typedef struct detour_trampoline
uint8_t* ptr_detour; // first instruction of detour function. uint8_t* ptr_detour; // first instruction of detour function.
} detour_trampoline; } detour_trampoline;
static_assert(sizeof(detour_trampoline) == 192); static_assert(sizeof(detour_trampoline)== 192);
enum { enum
{
DETOUR_SIZE_OF_JMP = 12 DETOUR_SIZE_OF_JMP = 12
}; };
@ -65,8 +70,10 @@ static inline void write_opcode(uint8_t** int_out_code, const detour_arm64_opcod
*int_out_code += sizeof(detour_arm64_opcode_t); *int_out_code += sizeof(detour_arm64_opcode_t);
} }
struct detour_arm64_indirect_jmp { struct detour_arm64_indirect_jmp
struct { {
struct
{
uint32_t Rd : 5; uint32_t Rd : 5;
uint32_t immhi : 19; uint32_t immhi : 19;
uint32_t iop : 5; uint32_t iop : 5;
@ -74,7 +81,8 @@ struct detour_arm64_indirect_jmp {
uint32_t op : 1; uint32_t op : 1;
} ardp; } ardp;
struct { struct
{
uint32_t Rt : 5; uint32_t Rt : 5;
uint32_t Rn : 5; uint32_t Rn : 5;
uint32_t imm : 12; uint32_t imm : 12;
@ -88,8 +96,10 @@ struct detour_arm64_indirect_jmp {
uint32_t br; uint32_t br;
}; };
union detour_arm64_indirect_imm { union detour_arm64_indirect_imm
struct { {
struct
{
uint64_t pad : 12; uint64_t pad : 12;
uint64_t adrp_immlo : 2; uint64_t adrp_immlo : 2;
uint64_t adrp_immhi : 19; uint64_t adrp_immhi : 19;
@ -154,7 +164,8 @@ static inline uint8_t* internal_detour_gen_brk(uint8_t* code, const uint8_t* lim
return code; return code;
} }
static inline void internal_detour_find_jmp_bounds(uint8_t* code, detour_trampoline** out_lower, detour_trampoline** out_upper) static inline void internal_detour_find_jmp_bounds(uint8_t* code, detour_trampoline** out_lower,
detour_trampoline** out_upper)
{ {
// The encoding used by detour_gen_jmp_indirect actually enables a // The encoding used by detour_gen_jmp_indirect actually enables a
// displacement of +/- 4GiB. In the future, this could be changed to // displacement of +/- 4GiB. In the future, this could be changed to
@ -216,10 +227,10 @@ static inline bool internal_detour_does_code_end_function(const uint8_t* code)
static inline uint32_t internal_detour_is_code_filler(const uint8_t* code) static inline uint32_t internal_detour_is_code_filler(const uint8_t* code)
{ {
if (*(uint32_t *)code == 0xd503201f) { // nop. if (*(uint32_t*)code == 0xd503201f) { // nop.
return 4; return 4;
} }
if (*(uint32_t *)code == 0x00000000) { // zero-filled padding. if (*(uint32_t*)code == 0x00000000) { // zero-filled padding.
return 4; return 4;
} }
return 0; return 0;
@ -231,4 +242,66 @@ static inline uint8_t* internal_detour_skip_jmp(uint8_t* code)
return code; return code;
} }
static inline uint8_t* internal_detour_operation_get_target_ptr(detour_operation* operation)
{
return operation->target;
}
static inline uint8_t* internal_detour_operation_get_trampoline_ptr(detour_operation* operation)
{
return operation->trampoline->code;
}
static inline uint8_t* internal_detour_operation_commit_detour(detour_operation* operation)
{
uint8_t* code = internal_detour_gen_jmp_indirect(
operation->target, (uint64_t*)&(operation->trampoline->ptr_detour));
code = internal_detour_gen_brk(code, operation->trampoline->ptr_remain);
return code;
}
static inline void internal_detour_update_thread_on_commit(detour_pending_thread* thread, detour_operation* operations_head)
{
arm_thread_state64_t threadState;
mach_msg_type_number_t threadStateCnt = ARM_THREAD_STATE64_COUNT;
const kern_return_t error = thread_get_state(thread->thread, ARM_THREAD_STATE64, (thread_state_t)&threadState,
&threadStateCnt);
if (error != err_none) {
DETOUR_BREAK();
return;
}
const uintptr_t pc = arm_thread_state64_get_pc(threadState);
for (detour_operation* op = operations_head; op != nullptr; op = op->next) {
switch (op->kind) {
case detour_operation_kind_attach: {
const uintptr_t targetAddr = (uintptr_t)op->target;
if (pc >= targetAddr && pc < targetAddr + op->trampoline->restore_code_size) {
uintptr_t new_pc = (uintptr_t)op->trampoline;
new_pc += internal_detour_align_from_target(op->trampoline->align, ARRAYSIZE(op->trampoline->align), pc - targetAddr);
DETOUR_TRACE(("detours: thread %u was at 0x%" PRIXPTR ", moved to 0x%" PRIXPTR "\n",
thread->thread, pc, new_pc));
arm_thread_state64_set_pc_fptr(threadState, new_pc);
thread_set_state(thread->thread, ARM_THREAD_STATE64, (thread_state_t)&threadState,
ARM_THREAD_STATE64_COUNT);
}
break;
}
case detour_operation_kind_detach: {
const uintptr_t trampAddr = (uintptr_t)op->trampoline;
if (pc >= trampAddr && pc < trampAddr + sizeof(*op->trampoline)) {
uintptr_t new_pc = (uintptr_t)op->target;
new_pc += internal_detour_align_from_trampoline(op->trampoline->align, ARRAYSIZE(op->trampoline->align), pc - trampAddr);
DETOUR_TRACE(("detours: thread %u was at 0x%" PRIXPTR ", moved to 0x%" PRIXPTR "\n",
thread->thread, pc, new_pc));
arm_thread_state64_set_pc_fptr(threadState, new_pc);
thread_set_state(thread->thread, ARM_THREAD_STATE64, (thread_state_t)&threadState,
ARM_THREAD_STATE64_COUNT);
}
break;
}
}
}
}
#endif //MACH_DETOURS_ARM64_H #endif //MACH_DETOURS_ARM64_H

View file

@ -38,12 +38,6 @@
#endif #endif
typedef struct detour_align
{
uint8_t offset_target;
uint8_t offset_trampoline;
} detour_align;
// Region reserved for system DLLs, which cannot be used for trampolines. // Region reserved for system DLLs, which cannot be used for trampolines.
static void* s_system_region_lower_bound = (void*)(uintptr_t)0x70000000; static void* s_system_region_lower_bound = (void*)(uintptr_t)0x70000000;
static void* s_system_region_upper_bound = (void*)(uintptr_t)0x80000000; static void* s_system_region_upper_bound = (void*)(uintptr_t)0x80000000;
@ -62,4 +56,60 @@ static inline uintptr_t internal_detour_2gb_above(uintptr_t address)
#endif #endif
} }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Structs
typedef struct detour_trampoline detour_trampoline;
typedef enum detour_operation_kind
{
detour_operation_kind_attach,
detour_operation_kind_detach,
} detour_operation_kind;
typedef struct detour_operation
{
struct detour_operation* next;
detour_operation_kind kind;
uint8_t** pointer;
uint8_t* target;
detour_trampoline* trampoline;
vm_prot_t perm;
} detour_operation;
typedef struct detour_pending_thread
{
struct detour_pending_thread* next;
thread_t thread;
} detour_pending_thread;
typedef struct detour_align
{
uint8_t offset_target;
uint8_t offset_trampoline;
} detour_align;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Trampoline Helpers
static uint8_t internal_detour_align_from_trampoline(const detour_align align[], const size_t align_size, const uint8_t offset_trampoline)
{
for (size_t n = 0; n < align_size; n++) {
if (align[n].offset_trampoline == offset_trampoline) {
return align[n].offset_target;
}
}
return 0;
}
static uint8_t internal_detour_align_from_target(const detour_align align[], const size_t align_size, const uint8_t offset_target)
{
for (size_t n = 0; n < align_size; n++) {
if (align[n].offset_target == offset_target) {
return align[n].offset_trampoline;
}
}
return 0;
}
#endif //MACH_DETOURS_INTERNAL_H #endif //MACH_DETOURS_INTERNAL_H

View file

@ -305,55 +305,9 @@ static void internal_detour_free_unused_trampoline_regions()
} }
} }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Trampoline Helpers
static uint8_t internal_detour_align_from_trampoline(const detour_trampoline* trampoline,
const uint8_t offset_trampoline)
{
for (int32_t n = 0; n < ARRAYSIZE(trampoline->align); n++) {
if (trampoline->align[n].offset_trampoline == offset_trampoline) {
return trampoline->align[n].offset_target;
}
}
return 0;
}
static uint8_t internal_detour_align_from_target(const detour_trampoline* trampoline, const uint8_t offset_target)
{
for (int32_t n = 0; n < ARRAYSIZE(trampoline->align); n++) {
if (trampoline->align[n].offset_target == offset_target) {
return trampoline->align[n].offset_trampoline;
}
}
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Transactions // Transactions
typedef enum detour_operation_kind
{
detour_operation_kind_attach,
detour_operation_kind_detach,
} detour_operation_kind;
typedef struct detour_operation
{
struct detour_operation* next;
detour_operation_kind kind;
uint8_t** pointer;
uint8_t* target;
detour_trampoline* trampoline;
vm_prot_t perm;
} detour_operation;
typedef struct detour_pending_thread
{
struct detour_pending_thread* next;
thread_t thread;
} detour_pending_thread;
static bool s_ignore_too_small = false; static bool s_ignore_too_small = false;
static bool s_retain_regions = false; static bool s_retain_regions = false;
@ -450,12 +404,8 @@ mach_error_t detour_transaction_commit_ex(detour_func_t** out_failed_target)
// Insert or remove each of the detours. // Insert or remove each of the detours.
for (detour_operation* operation = s_pending_operations_head; operation != nullptr; operation = operation->next) { for (detour_operation* operation = s_pending_operations_head; operation != nullptr; operation = operation->next) {
if (operation->kind == detour_operation_kind_detach) { switch (operation->kind) {
memcpy(operation->target, operation->trampoline->restore_code, operation->trampoline->restore_code_size); case detour_operation_kind_attach: {
#ifdef DETOURS_ARM64
*operation->pointer = operation->target;
#endif
} else {
DETOUR_TRACE(("detours: trampoline=%p, ptr_remain=%p, ptr_detour=%p, restore_code_size=%u\n", DETOUR_TRACE(("detours: trampoline=%p, ptr_remain=%p, ptr_detour=%p, restore_code_size=%u\n",
operation->trampoline, operation->trampoline,
operation->trampoline->ptr_remain, operation->trampoline->ptr_remain,
@ -471,13 +421,8 @@ mach_error_t detour_transaction_commit_ex(detour_func_t** out_failed_target)
operation->target[4], operation->target[5], operation->target[6], operation->target[7], operation->target[4], operation->target[5], operation->target[6], operation->target[7],
operation->target[8], operation->target[9], operation->target[10], operation->target[11])); operation->target[8], operation->target[9], operation->target[10], operation->target[11]));
#ifdef DETOURS_ARM64 internal_detour_operation_commit_detour(operation);
uint8_t* code = internal_detour_gen_jmp_indirect(operation->target, *operation->pointer = internal_detour_operation_get_trampoline_ptr(operation)
(uint64_t*)&(operation->trampoline->ptr_detour));
code = internal_detour_gen_brk(code, operation->trampoline->ptr_remain);
UNUSED_VARIABLE(code);
*operation->pointer = operation->trampoline->code;
#endif // DETOURS_ARM64
DETOUR_TRACE(("detours: target=%p: " DETOUR_TRACE(("detours: target=%p: "
"%02x %02x %02x %02x " "%02x %02x %02x %02x "
@ -499,51 +444,20 @@ mach_error_t detour_transaction_commit_ex(detour_func_t** out_failed_target)
operation->trampoline->code[6], operation->trampoline->code[7], operation->trampoline->code[6], operation->trampoline->code[7],
operation->trampoline->code[8], operation->trampoline->code[9], operation->trampoline->code[8], operation->trampoline->code[9],
operation->trampoline->code[10], operation->trampoline->code[11])); operation->trampoline->code[10], operation->trampoline->code[11]));
break;
}
case detour_operation_kind_detach: {
memcpy(operation->target, operation->trampoline->restore_code,
operation->trampoline->restore_code_size);
*operation->pointer = internal_detour_operation_get_target_ptr(operation);
break;
}
} }
} }
// Update any suspended threads. // Update any suspended threads.
for (detour_pending_thread* thread = s_pending_threads_head; thread != nullptr; thread = thread->next) { for (detour_pending_thread* thread = s_pending_threads_head; thread != nullptr; thread = thread->next) {
arm_thread_state64_t threadState; internal_detour_update_thread_on_commit(thread, s_pending_operations_head);
mach_msg_type_number_t threadStateCnt = ARM_THREAD_STATE64_COUNT;
const kern_return_t error = thread_get_state(thread->thread, ARM_THREAD_STATE64, (thread_state_t)&threadState,
&threadStateCnt);
if (error != err_none) {
DETOUR_BREAK();
continue;
}
const uintptr_t pc = arm_thread_state64_get_pc(threadState);
for (detour_operation* op = s_pending_operations_head; op != nullptr; op = op->next) {
switch (op->kind) {
case detour_operation_kind_attach: {
const uintptr_t targetAddr = (uintptr_t)op->target;
if (pc >= targetAddr && pc < targetAddr + op->trampoline->restore_code_size) {
uintptr_t new_pc = (uintptr_t)op->trampoline;
new_pc += internal_detour_align_from_target(op->trampoline, pc - targetAddr);
DETOUR_TRACE(("detours: thread %u was at 0x%" PRIXPTR ", moved to 0x%" PRIXPTR "\n",
thread->thread, pc, new_pc));
arm_thread_state64_set_pc_fptr(threadState, new_pc);
thread_set_state(thread->thread, ARM_THREAD_STATE64, (thread_state_t)&threadState,
ARM_THREAD_STATE64_COUNT);
}
break;
}
case detour_operation_kind_detach: {
const uintptr_t trampAddr = (uintptr_t)op->trampoline;
if (pc >= trampAddr && pc < trampAddr + sizeof(*op->trampoline)) {
uintptr_t new_pc = (uintptr_t)op->target;
new_pc += internal_detour_align_from_trampoline(op->trampoline, pc - trampAddr);
DETOUR_TRACE(("detours: thread %u was at 0x%" PRIXPTR ", moved to 0x%" PRIXPTR "\n",
thread->thread, pc, new_pc));
arm_thread_state64_set_pc_fptr(threadState, new_pc);
thread_set_state(thread->thread, ARM_THREAD_STATE64, (thread_state_t)&threadState,
ARM_THREAD_STATE64_COUNT);
}
break;
}
}
}
} }
// Restore all the page permissions // Restore all the page permissions