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
#define MACH_DETOURS_ARM64_H
#include "detours_internal.h"
#include <stdint.h>
#include <mach/mach.h>
typedef struct detour_trampoline
{
// An ARM64 instruction is 4 bytes long.
@ -45,9 +49,10 @@ typedef struct detour_trampoline
uint8_t* ptr_detour; // first instruction of detour function.
} detour_trampoline;
static_assert(sizeof(detour_trampoline) == 192);
static_assert(sizeof(detour_trampoline)== 192);
enum {
enum
{
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);
}
struct detour_arm64_indirect_jmp {
struct {
struct detour_arm64_indirect_jmp
{
struct
{
uint32_t Rd : 5;
uint32_t immhi : 19;
uint32_t iop : 5;
@ -74,7 +81,8 @@ struct detour_arm64_indirect_jmp {
uint32_t op : 1;
} ardp;
struct {
struct
{
uint32_t Rt : 5;
uint32_t Rn : 5;
uint32_t imm : 12;
@ -88,8 +96,10 @@ struct detour_arm64_indirect_jmp {
uint32_t br;
};
union detour_arm64_indirect_imm {
struct {
union detour_arm64_indirect_imm
{
struct
{
uint64_t pad : 12;
uint64_t adrp_immlo : 2;
uint64_t adrp_immhi : 19;
@ -107,7 +117,7 @@ static inline uint8_t* internal_detour_gen_jmp_indirect(uint8_t* code, const uin
union detour_arm64_indirect_imm jmp_ind_addr;
jmp_ind_addr.value = (((uint64_t)jump_val) & 0xFFFFFFFFFFFFF000) -
(((uint64_t)code) & 0xFFFFFFFFFFFFF000);
(((uint64_t)code) & 0xFFFFFFFFFFFFF000);
struct detour_arm64_indirect_jmp* ind_jmp = (struct detour_arm64_indirect_jmp*)code;
code = (uint8_t*)(ind_jmp + 1);
@ -140,8 +150,8 @@ static inline uint8_t* internal_detour_gen_jmp_immediate(uint8_t* code, uint8_t*
*(uint8_t**)literal = jmp_val;
const int32_t delta = (int32_t)(literal - code);
write_opcode(&code, 0x58000011 | ((delta / 4) << 5)); // LDR X17,[PC+n]
write_opcode(&code, 0xd61f0000 | (17 << 5)); // BR X17
write_opcode(&code, 0x58000011 | ((delta / 4) << 5)); // LDR X17,[PC+n]
write_opcode(&code, 0xd61f0000 | (17 << 5)); // BR X17
return code;
}
@ -154,7 +164,8 @@ static inline uint8_t* internal_detour_gen_brk(uint8_t* code, const uint8_t* lim
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
// displacement of +/- 4GiB. In the future, this could be changed to
@ -190,10 +201,10 @@ static inline bool internal_detour_is_code_os_patched(const uint8_t* code)
const uint32_t hpat_opcode1 = fetch_opcode(branch_target);
const uint32_t hpat_opcode2 = fetch_opcode(branch_target + 4);
if (hpat_opcode1 != 0x58008010) { // ldr <reg> [PC+PAGE_SIZE]
if (hpat_opcode1 != 0x58008010) { // ldr <reg> [PC+PAGE_SIZE]
return false;
}
if (hpat_opcode2 != 0xd61f0200) { // br <reg>
if (hpat_opcode2 != 0xd61f0200) { // br <reg>
return false;
}
return true;
@ -207,19 +218,19 @@ static inline bool internal_detour_does_code_end_function(const uint8_t* code)
if (internal_detour_is_code_os_patched(code)) {
return false;
}
if ((opcode & 0xffbffc1f) == 0xd61f0000 || // ret/br <reg>
(opcode & 0xfc000000) == 0x14000000) { // b <imm26>
if ((opcode & 0xffbffc1f) == 0xd61f0000 || // ret/br <reg>
(opcode & 0xfc000000) == 0x14000000) { // b <imm26>
return true;
}
}
return false;
}
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;
}
if (*(uint32_t *)code == 0x00000000) { // zero-filled padding.
if (*(uint32_t*)code == 0x00000000) { // zero-filled padding.
return 4;
}
return 0;
@ -231,4 +242,66 @@ static inline uint8_t* internal_detour_skip_jmp(uint8_t* code)
return code;
}
#endif //MACH_DETOURS_ARM64_H
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

View file

@ -38,12 +38,6 @@
#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.
static void* s_system_region_lower_bound = (void*)(uintptr_t)0x70000000;
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
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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

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
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_retain_regions = false;
@ -450,100 +404,60 @@ mach_error_t detour_transaction_commit_ex(detour_func_t** out_failed_target)
// Insert or remove each of the detours.
for (detour_operation* operation = s_pending_operations_head; operation != nullptr; operation = operation->next) {
if (operation->kind == detour_operation_kind_detach) {
memcpy(operation->target, operation->trampoline->restore_code, operation->trampoline->restore_code_size);
#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",
operation->trampoline,
operation->trampoline->ptr_remain,
operation->trampoline->ptr_detour,
operation->trampoline->restore_code_size));
switch (operation->kind) {
case detour_operation_kind_attach: {
DETOUR_TRACE(("detours: trampoline=%p, ptr_remain=%p, ptr_detour=%p, restore_code_size=%u\n",
operation->trampoline,
operation->trampoline->ptr_remain,
operation->trampoline->ptr_detour,
operation->trampoline->restore_code_size));
DETOUR_TRACE(("detours: target=%p: "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x [before]\n",
operation->target,
operation->target[0], operation->target[1], operation->target[2], operation->target[3],
operation->target[4], operation->target[5], operation->target[6], operation->target[7],
operation->target[8], operation->target[9], operation->target[10], operation->target[11]));
DETOUR_TRACE(("detours: target=%p: "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x [before]\n",
operation->target,
operation->target[0], operation->target[1], operation->target[2], operation->target[3],
operation->target[4], operation->target[5], operation->target[6], operation->target[7],
operation->target[8], operation->target[9], operation->target[10], operation->target[11]));
#ifdef DETOURS_ARM64
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);
UNUSED_VARIABLE(code);
*operation->pointer = operation->trampoline->code;
#endif // DETOURS_ARM64
internal_detour_operation_commit_detour(operation);
*operation->pointer = internal_detour_operation_get_trampoline_ptr(operation)
DETOUR_TRACE(("detours: target=%p: "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x [after]\n",
operation->target,
operation->target[0], operation->target[1], operation->target[2], operation->target[3],
operation->target[4], operation->target[5], operation->target[6], operation->target[7],
operation->target[8], operation->target[9], operation->target[10], operation->target[11]));
DETOUR_TRACE(("detours: target=%p: "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x [after]\n",
operation->target,
operation->target[0], operation->target[1], operation->target[2], operation->target[3],
operation->target[4], operation->target[5], operation->target[6], operation->target[7],
operation->target[8], operation->target[9], operation->target[10], operation->target[11]));
DETOUR_TRACE(("detours: trampoline=%p: "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x\n",
operation->trampoline,
operation->trampoline->code[0], operation->trampoline->code[1],
operation->trampoline->code[2], operation->trampoline->code[3],
operation->trampoline->code[4], operation->trampoline->code[5],
operation->trampoline->code[6], operation->trampoline->code[7],
operation->trampoline->code[8], operation->trampoline->code[9],
operation->trampoline->code[10], operation->trampoline->code[11]));
DETOUR_TRACE(("detours: trampoline=%p: "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x\n",
operation->trampoline,
operation->trampoline->code[0], operation->trampoline->code[1],
operation->trampoline->code[2], operation->trampoline->code[3],
operation->trampoline->code[4], operation->trampoline->code[5],
operation->trampoline->code[6], operation->trampoline->code[7],
operation->trampoline->code[8], operation->trampoline->code[9],
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.
for (detour_pending_thread* thread = s_pending_threads_head; thread != nullptr; thread = thread->next) {
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();
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;
}
}
}
internal_detour_update_thread_on_commit(thread, s_pending_operations_head);
}
// Restore all the page permissions