diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 0fd9b90..c96d19c 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -13,6 +13,7 @@
+
\ No newline at end of file
diff --git a/.idea/editor.xml b/.idea/editor.xml
index 0844927..6a4c9be 100644
--- a/.idea/editor.xml
+++ b/.idea/editor.xml
@@ -249,6 +249,7 @@
+
diff --git a/src/arm64/detours_arm64.h b/src/arm64/detours_arm64.h
index c1e2e1c..c56e99e 100644
--- a/src/arm64/detours_arm64.h
+++ b/src/arm64/detours_arm64.h
@@ -9,8 +9,8 @@
typedef struct detour_align
{
- uint8_t obTarget;
- uint8_t obTrampoline;
+ uint8_t offset_target;
+ uint8_t offset_trampoline;
} detour_align;
typedef struct detour_trampoline
@@ -54,4 +54,93 @@ typedef struct detour_trampoline
static_assert(sizeof(detour_trampoline) == 192);
+typedef uint32_t detours_arm64_opcode_t;
+
+static inline detours_arm64_opcode_t fetch_opcode(uint8_t* code)
+{
+ return *(detours_arm64_opcode_t*)code;
+}
+
+static inline void write_opcode(uint8_t** int_out_code, detours_arm64_opcode_t opcode)
+{
+ uint8_t* code = *int_out_code;
+ *(detours_arm64_opcode_t*)code = opcode;
+ *int_out_code += sizeof(detours_arm64_opcode_t);
+}
+
+struct detours_arm64_indirect_jmp {
+ struct {
+ uint32_t Rd : 5;
+ uint32_t immhi : 19;
+ uint32_t iop : 5;
+ uint32_t immlo : 2;
+ uint32_t op : 1;
+ } ardp;
+
+ struct {
+ uint32_t Rt : 5;
+ uint32_t Rn : 5;
+ uint32_t imm : 12;
+ uint32_t opc : 2;
+ uint32_t iop1 : 2;
+ uint32_t V : 1;
+ uint32_t iop2 : 3;
+ uint32_t size : 2;
+ } ldr;
+
+ uint32_t br;
+};
+
+union detours_arm64_indirect_imm {
+ struct {
+ uint64_t pad : 12;
+ uint64_t adrp_immlo : 2;
+ uint64_t adrp_immhi : 19;
+ };
+
+ int64_t value;
+};
+
+static inline uint8_t* internal_detour_gen_jmp_indirect(uint8_t* code, uint64_t* jump_val)
+{
+ // adrp x17, [jmpval]
+ // ldr x17, [x17, jmpval]
+ // br x17
+
+ union detours_arm64_indirect_imm jmp_ind_addr;
+
+ jmp_ind_addr.value = (((uint64_t)jump_val) & 0xFFFFFFFFFFFFF000) -
+ (((uint64_t)code) & 0xFFFFFFFFFFFFF000);
+
+ struct detours_arm64_indirect_jmp* ind_jmp = (struct detours_arm64_indirect_jmp*)code;
+ code = (uint8_t*)(ind_jmp + 1);
+
+ ind_jmp->ardp.Rd = 17;
+ ind_jmp->ardp.immhi = jmp_ind_addr.adrp_immhi;
+ ind_jmp->ardp.iop = 0x10;
+ ind_jmp->ardp.immlo = jmp_ind_addr.adrp_immlo;
+ ind_jmp->ardp.op = 1;
+
+ ind_jmp->ldr.Rt = 17;
+ ind_jmp->ldr.Rn = 17;
+ ind_jmp->ldr.imm = (((uint64_t)jump_val) & 0xFFF) / 8;
+ ind_jmp->ldr.opc = 1;
+ ind_jmp->ldr.iop1 = 1;
+ ind_jmp->ldr.V = 0;
+ ind_jmp->ldr.iop2 = 7;
+ ind_jmp->ldr.size = 3;
+
+ ind_jmp->br = 0xD61F0220;
+
+ return code;
+}
+
+static inline uint8_t* internal_detour_gen_brk(uint8_t* code, uint8_t* limit)
+{
+ while (code < limit) {
+ write_opcode(&code, 0xd4100000 | (0xf000 << 5));
+ }
+ return code;
+}
+
#endif //MACH_DETOURS_ARM64_H
\ No newline at end of file
diff --git a/src/detours_internal.h b/src/detours_internal.h
index 5d204e5..bafc9e3 100644
--- a/src/detours_internal.h
+++ b/src/detours_internal.h
@@ -25,4 +25,8 @@
#endif
#endif
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(x) (sizeof(x)/sizeof(x[0]))
+#endif
+
#endif //MACH_DETOURS_INTERNAL_H
\ No newline at end of file
diff --git a/src/mach_detours.c b/src/mach_detours.c
index d3f444e..98183a2 100644
--- a/src/mach_detours.c
+++ b/src/mach_detours.c
@@ -6,6 +6,7 @@
#include "detours_internal.h"
#include "arm64/detours_arm64.h"
+#include
#include
#include
#include
@@ -13,14 +14,20 @@
#include
#include
+#ifdef __arm64__
+#define DETOURS_ARM64
+#else
+#error Unsupported architecture (arm64)
+#endif
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Trampoline Memory Management
typedef struct detour_region
{
uint32_t signature;
- struct detour_region* next; // Next region in list of regions.
- detour_trampoline* free_list_head; // List of free trampolines in this region.
+ struct detour_region* next; // Next region in list of regions.
+ detour_trampoline* free_list_head; // List of free trampolines in this region.
} detour_region;
static const uint32_t DETOUR_REGION_SIGNATURE = 'Rrtd';
@@ -34,7 +41,8 @@ static mach_error_t internal_detour_writable_trampoline_regions()
// Mark all the regions as writable.
const mach_port_t port = mach_task_self();
for (detour_region* pRegion = s_regions_head; pRegion != NULL; pRegion = pRegion->next) {
- const mach_error_t error = mach_vm_protect(port, (mach_vm_address_t)pRegion, DETOUR_REGION_SIZE, false, VM_PROT_READ | VM_PROT_WRITE);
+ const mach_error_t error = mach_vm_protect(port, (mach_vm_address_t)pRegion, DETOUR_REGION_SIZE, false,
+ VM_PROT_READ | VM_PROT_WRITE);
if (error != err_none) {
return error;
}
@@ -47,7 +55,8 @@ static void internal_detour_runnable_trampoline_regions()
// Mark all the regions as executable.
const mach_port_t port = mach_task_self();
for (detour_region* pRegion = s_regions_head; pRegion != NULL; pRegion = pRegion->next) {
- const mach_error_t error = mach_vm_protect(port, (mach_vm_address_t)pRegion, DETOUR_REGION_SIZE, false, VM_PROT_READ | VM_PROT_EXECUTE);
+ const mach_error_t error = mach_vm_protect(port, (mach_vm_address_t)pRegion, DETOUR_REGION_SIZE, false,
+ VM_PROT_READ | VM_PROT_EXECUTE);
if (error != err_none) {
DETOUR_BREAK();
}
@@ -63,10 +72,78 @@ static void internal_detour_free_trampoline(detour_trampoline* trampoline)
region->free_list_head = trampoline;
}
+static bool internal_detour_is_region_empty(detour_region* region)
+{
+ // Stop if the region isn't a region (this would be bad).
+ if (region->signature != DETOUR_REGION_SIGNATURE) {
+ DETOUR_BREAK();
+ return false;
+ }
+
+ uint8_t* region_begin = (uint8_t*)region;
+ uint8_t* region_limit = region_begin + DETOUR_REGION_SIZE;
+
+ // Stop if any of the trampolines aren't free.
+ detour_trampoline* trampoline = ((detour_trampoline*)region) + 1;
+ for (int i = 0; i < DETOUR_TRAMPOLINES_PER_REGION; i++) {
+ if (trampoline[i].ptr_remain != NULL &&
+ (trampoline[i].ptr_remain < region_begin || trampoline[i].ptr_remain >= region_limit)) {
+ return false;
+ }
+ }
+
+ // OK, the region is empty.
+ return true;
+}
+
+static void internal_detour_free_unused_trampoline_regions()
+{
+ detour_region** ptr_region_base = &s_regions_head;
+ detour_region* curr_region = s_regions_head;
+
+ const mach_port_t port = mach_task_self();
+
+ while (curr_region) {
+ if (internal_detour_is_region_empty(curr_region)) {
+ *ptr_region_base = curr_region->next;
+
+ vm_deallocate(port, (vm_address_t)curr_region, DETOUR_REGION_SIZE);
+ s_default_region = nullptr;
+ } else {
+ ptr_region_base = &curr_region->next;
+ }
+ curr_region = *ptr_region_base;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Trampoline Helpers
+
+static uint8_t internal_detour_align_from_trampoline(detour_trampoline* trampoline, 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(detour_trampoline* trampoline, 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 {
+typedef enum detour_operation_kind
+{
detour_operation_kind_attach,
detour_operation_kind_detach,
} detour_operation_kind;
@@ -87,6 +164,9 @@ typedef struct detour_pending_thread
thread_t thread;
} detour_pending_thread;
+static bool s_ignore_too_small = false;
+static bool s_retain_regions = false;
+
static _Atomic(thread_t) s_transaction_thread = THREAD_NULL;
static detour_operation* s_pending_operations_head = nullptr;
static detour_pending_thread* s_pending_threads_head = nullptr;
@@ -122,7 +202,9 @@ mach_error_t detour_transaction_abort()
// Restore all the page permissions.
const mach_port_t port = mach_task_self();
for (detour_operation* operation = s_pending_operations_head; operation != nullptr;) {
- DETOUR_CHECK(mach_vm_protect(port, (mach_vm_address_t)operation->target, operation->trampoline->restore_code_size, false, operation->perm));
+ DETOUR_CHECK(
+ mach_vm_protect(port, (mach_vm_address_t)operation->target, operation->trampoline->restore_code_size, false,
+ operation->perm));
if (operation->kind == detour_operation_kind_attach) {
if (operation->trampoline) {
@@ -154,3 +236,169 @@ mach_error_t detour_transaction_abort()
return err_none;
}
+
+mach_error_t detour_transaction_commit()
+{
+ return detour_transaction_commit_ex(nullptr);
+}
+
+mach_error_t detour_transaction_commit_ex(detour_func_t** out_failed_target)
+{
+ if (out_failed_target != NULL) {
+ *out_failed_target = s_pending_error_pointer;
+ }
+ if (s_transaction_thread != mach_thread_self()) {
+ return detour_err_wrong_thread;
+ }
+
+ // If any of the pending operations failed, then we abort the whole transaction.
+ if (s_pending_error != err_none) {
+ DETOUR_BREAK();
+ detour_transaction_abort();
+ return s_pending_error;
+ }
+
+ // 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));
+
+ 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);
+ *operation->pointer = operation->trampoline->code;
+#endif // DETOURS_ARM64
+
+ 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]));
+ }
+ }
+
+ // 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;
+ 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);
+ printf("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);
+ printf("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
+ bool freed_trampoline = false;
+ const mach_port_t port = mach_task_self();
+ for (detour_operation* operation = s_pending_operations_head; operation != nullptr;) {
+ DETOUR_CHECK(
+ mach_vm_protect(port, (mach_vm_address_t)operation->target, operation->trampoline->restore_code_size, false,
+ operation->perm));
+
+ if (operation->kind == detour_operation_kind_detach && operation->trampoline) {
+ internal_detour_free_trampoline(operation->trampoline);
+ operation->trampoline = nullptr;
+ freed_trampoline = true;
+ }
+
+ detour_operation* next = operation->next;
+ free(operation);
+ operation = next;
+ }
+ s_pending_operations_head = nullptr;
+
+ // Free any trampoline regions that are now unused.
+ if (freed_trampoline && !s_retain_regions) {
+ internal_detour_free_unused_trampoline_regions();
+ }
+
+ // Make sure the trampoline pages are no longer writable.
+ internal_detour_runnable_trampoline_regions();
+
+ // Resume any suspended threads.
+ for (detour_pending_thread* thread = s_pending_threads_head; thread != nullptr;) {
+ // There is nothing we can do if this fails.
+ DETOUR_CHECK(thread_resume(thread->thread));
+
+ detour_pending_thread* next = thread->next;
+ free(thread);
+ thread = next;
+ }
+ s_pending_threads_head = nullptr;
+ s_transaction_thread = THREAD_NULL;
+
+ if (out_failed_target) {
+ *out_failed_target = s_pending_error_pointer;
+ }
+ return s_pending_error;
+}