reorganize files and add missing includes

This commit is contained in:
Lysann Tranvouez 2025-09-28 00:42:23 +02:00
parent d80ec89490
commit 21cd133480
5 changed files with 13 additions and 4 deletions

55
src/arm64/detours_arm64.h Normal file
View file

@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Lysann Tranvouez. All rights reserved.
#pragma once
#ifndef MACH_DETOURS_ARM64_H
#define MACH_DETOURS_ARM64_H
#include <stdint.h>
typedef struct detour_align
{
uint8_t obTarget;
uint8_t obTrampoline;
} detour_align;
typedef struct detour_trampoline
{
// An ARM64 instruction is 4 bytes long.
//
// The overwrite is always composed of 3 instructions (12 bytes) which perform an indirect jump
// using _DETOUR_TRAMPOLINE::pbDetour as the address holding the target location.
//
// Copied instructions can expand.
//
// The scheme using MovImmediate can cause an instruction
// to grow as much as 6 times.
// That would be Bcc or Tbz with a large address space:
// 4 instructions to form immediate
// inverted tbz/bcc
// br
//
// An expansion of 4 is not uncommon -- bl/blr and small address space:
// 3 instructions to form immediate
// br or brl
//
// A theoretical maximum for rbCode is thefore 4*4*6 + 16 = 112 (another 16 for jmp to pbRemain).
//
// With literals, the maximum expansion is 5, including the literals: 4*4*5 + 16 = 96.
//
// The number is rounded up to 128. m_rbScratchDst should match this.
//
uint8_t rbCode[128]; // target code + jmp to pbRemain
uint8_t cbCode; // size of moved target code.
uint8_t cbCodeBreak[3]; // padding to make debugging easier.
uint8_t rbRestore[24]; // original target code.
uint8_t cbRestore; // size of original target code.
uint8_t cbRestoreBreak[3]; // padding to make debugging easier.
detour_align rAlign[8]; // instruction alignment array.
uint8_t* pbRemain; // first instruction after moved code. [free list]
uint8_t* pbDetour; // first instruction of detour function.
} detour_trampoline;
static_assert(sizeof(detour_trampoline) == 192);
#endif //MACH_DETOURS_ARM64_H

20
src/detours_internal.h Normal file
View file

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Lysann Tranvouez. All rights reserved.
#pragma once
#ifndef MACH_DETOURS_INTERNAL_H
#define MACH_DETOURS_INTERNAL_H
#ifndef DETOUR_TRACE
#if DETOUR_DEBUG
#define DETOUR_TRACE(x) printf x
#define DETOUR_BREAK() raise(SIGTRAP)
#else
#include <signal.h>
#include <stdio.h>
#define DETOUR_TRACE(x)
#define DETOUR_BREAK()
#endif
#endif
#endif //MACH_DETOURS_INTERNAL_H

104
src/mach_detours.c Normal file
View file

@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Lysann Tranvouez. All rights reserved.
#include "mach_detours.h"
#include "detours_internal.h"
#include "arm64/detours_arm64.h"
#include <stdatomic.h>
#include <stdint.h>
#include <mach/mach_init.h>
#include <mach/mach_vm.h>
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.
} detour_region;
static const uint32_t DETOUR_REGION_SIGNATURE = 'Rrtd';
static const uint32_t DETOUR_REGION_SIZE = 0x10000;
static const uint32_t DETOUR_TRAMPOLINES_PER_REGION = (DETOUR_REGION_SIZE / sizeof(detour_trampoline)) - 1;
static detour_region* s_regions_head = nullptr;
static detour_region* s_default_region = nullptr;
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);
if (error != err_none) {
return error;
}
}
return err_none;
}
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);
if (error != err_none) {
DETOUR_BREAK();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Transactions
typedef enum detour_operation_kind {
attach,
detach,
} detour_operation_kind;
typedef struct detour_operation
{
struct detour_operation* next;
detour_operation_kind kind;
uint8_t** pointer;
uint8_t* target;
struct 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 _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;
static mach_error_t s_pending_error = err_none;
static void** s_pending_error_pointer = nullptr;
mach_error_t detour_transaction_begin()
{
// Make sure only one thread can start a transaction.
thread_t expected = THREAD_NULL;
// ReSharper disable once CppIncompatiblePointerConversion
if (!atomic_compare_exchange_strong(&s_transaction_thread, &expected, mach_thread_self())) {
return detour_err_in_progress;
}
s_pending_operations_head = nullptr;
s_pending_threads_head = nullptr;
s_pending_error_pointer = nullptr;
// Make sure the trampoline pages are writable.
s_pending_error = internal_detour_writable_trampoline_regions();
return s_pending_error;
}