more tests and tweaks/fixes as a result
This commit is contained in:
parent
327dfb0cb7
commit
058069d1f3
6 changed files with 194 additions and 101 deletions
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "detours_internal.h"
|
#include "detours_internal.h"
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <mach/mach.h>
|
#include <mach/mach.h>
|
||||||
|
|
|
||||||
|
|
@ -44,16 +44,20 @@ static detour_region* s_default_region = nullptr;
|
||||||
|
|
||||||
static mach_error_t internal_detour_writable_trampoline_regions()
|
static mach_error_t internal_detour_writable_trampoline_regions()
|
||||||
{
|
{
|
||||||
|
mach_error_t result = err_none;
|
||||||
|
|
||||||
// Mark all the regions as writable.
|
// Mark all the regions as writable.
|
||||||
const mach_port_t port = mach_task_self();
|
const mach_port_t port = mach_task_self();
|
||||||
for (detour_region* pRegion = s_regions_head; pRegion != NULL; pRegion = pRegion->next) {
|
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,
|
const mach_error_t error = mach_vm_protect(port, (mach_vm_address_t)pRegion, DETOUR_REGION_SIZE, false,
|
||||||
VM_PROT_READ | VM_PROT_WRITE);
|
VM_PROT_READ | VM_PROT_WRITE);
|
||||||
if (error != err_none) {
|
if (error != err_none) {
|
||||||
return error;
|
DETOUR_BREAK();
|
||||||
|
result = error;
|
||||||
}
|
}
|
||||||
|
DETOUR_TRACE(("mach_vm_protect(%p, %" PRIu32 ", %d)\n", (void*)pRegion, DETOUR_REGION_SIZE, VM_PROT_READ | VM_PROT_WRITE));
|
||||||
}
|
}
|
||||||
return err_none;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void internal_detour_runnable_trampoline_regions()
|
static void internal_detour_runnable_trampoline_regions()
|
||||||
|
|
@ -66,6 +70,7 @@ static void internal_detour_runnable_trampoline_regions()
|
||||||
if (error != err_none) {
|
if (error != err_none) {
|
||||||
DETOUR_BREAK();
|
DETOUR_BREAK();
|
||||||
}
|
}
|
||||||
|
DETOUR_TRACE(("mach_vm_protect(%p, %" PRIu32 ", %d)\n", (void*)pRegion, DETOUR_REGION_SIZE, VM_PROT_READ | VM_PROT_EXECUTE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,7 +101,7 @@ static void* internal_detour_alloc_region_from_lo(const uint8_t* lo, const uint8
|
||||||
const vm_map_t task_self = mach_task_self();
|
const vm_map_t task_self = mach_task_self();
|
||||||
|
|
||||||
for (vm_address_t page = (vm_address_t)try_addr; page < (vm_address_t)hi; page += PAGE_SIZE) {
|
for (vm_address_t page = (vm_address_t)try_addr; page < (vm_address_t)hi; page += PAGE_SIZE) {
|
||||||
DETOUR_TRACE((" Try %p\n", (void*)page));
|
//DETOUR_TRACE((" Try %p\n", (void*)page));
|
||||||
|
|
||||||
const mach_error_t err = vm_allocate(task_self, &page, DETOUR_REGION_SIZE, 0);
|
const mach_error_t err = vm_allocate(task_self, &page, DETOUR_REGION_SIZE, 0);
|
||||||
if (err == err_none) {
|
if (err == err_none) {
|
||||||
|
|
@ -121,7 +126,7 @@ static void* internal_detour_alloc_region_from_hi(const uint8_t* lo, const uint8
|
||||||
const vm_map_t task_self = mach_task_self();
|
const vm_map_t task_self = mach_task_self();
|
||||||
|
|
||||||
for (vm_address_t page = try_addr; page > (vm_address_t)lo; page -= PAGE_SIZE) {
|
for (vm_address_t page = try_addr; page > (vm_address_t)lo; page -= PAGE_SIZE) {
|
||||||
DETOUR_TRACE((" Try %p\n", (void*)page));
|
//DETOUR_TRACE((" Try %p\n", (void*)page));
|
||||||
if ((void*)page >= s_system_region_lower_bound && (void*)page <= s_system_region_upper_bound) {
|
if ((void*)page >= s_system_region_lower_bound && (void*)page <= s_system_region_upper_bound) {
|
||||||
// Skip region reserved for system DLLs, but preserve address space entropy.
|
// Skip region reserved for system DLLs, but preserve address space entropy.
|
||||||
try_addr -= 0x08000000;
|
try_addr -= 0x08000000;
|
||||||
|
|
@ -339,6 +344,9 @@ mach_error_t detour_transaction_begin()
|
||||||
|
|
||||||
mach_error_t detour_transaction_abort()
|
mach_error_t detour_transaction_abort()
|
||||||
{
|
{
|
||||||
|
if (s_transaction_thread == THREAD_NULL) {
|
||||||
|
return err_none;
|
||||||
|
}
|
||||||
if (s_transaction_thread != mach_thread_self()) {
|
if (s_transaction_thread != mach_thread_self()) {
|
||||||
return detour_err_wrong_thread;
|
return detour_err_wrong_thread;
|
||||||
}
|
}
|
||||||
|
|
@ -349,6 +357,7 @@ mach_error_t detour_transaction_abort()
|
||||||
DETOUR_CHECK(
|
DETOUR_CHECK(
|
||||||
mach_vm_protect(port, (mach_vm_address_t)operation->target, operation->trampoline->restore_code_size, false,
|
mach_vm_protect(port, (mach_vm_address_t)operation->target, operation->trampoline->restore_code_size, false,
|
||||||
operation->perm));
|
operation->perm));
|
||||||
|
DETOUR_TRACE(("mach_vm_protect(%p, %" PRIu8 ", %d)\n", (void*)operation->target, operation->trampoline->restore_code_size, operation->perm));
|
||||||
|
|
||||||
if (operation->kind == detour_operation_kind_attach) {
|
if (operation->kind == detour_operation_kind_attach) {
|
||||||
if (operation->trampoline) {
|
if (operation->trampoline) {
|
||||||
|
|
@ -422,7 +431,7 @@ mach_error_t detour_transaction_commit_ex(detour_func_t** out_failed_target)
|
||||||
operation->target[8], operation->target[9], operation->target[10], operation->target[11]));
|
operation->target[8], operation->target[9], operation->target[10], operation->target[11]));
|
||||||
|
|
||||||
detour_platform_operation_commit_detour(operation);
|
detour_platform_operation_commit_detour(operation);
|
||||||
*operation->pointer = detour_platform_operation_get_trampoline_ptr(operation)
|
*operation->pointer = detour_platform_operation_get_trampoline_ptr(operation);
|
||||||
|
|
||||||
DETOUR_TRACE(("detours: target=%p: "
|
DETOUR_TRACE(("detours: target=%p: "
|
||||||
"%02x %02x %02x %02x "
|
"%02x %02x %02x %02x "
|
||||||
|
|
@ -467,6 +476,7 @@ mach_error_t detour_transaction_commit_ex(detour_func_t** out_failed_target)
|
||||||
DETOUR_CHECK(
|
DETOUR_CHECK(
|
||||||
mach_vm_protect(port, (mach_vm_address_t)operation->target, operation->trampoline->restore_code_size, false,
|
mach_vm_protect(port, (mach_vm_address_t)operation->target, operation->trampoline->restore_code_size, false,
|
||||||
operation->perm));
|
operation->perm));
|
||||||
|
DETOUR_TRACE(("mach_vm_protect(%p, %" PRIu8 ", %d)\n", (void*)operation->target, operation->trampoline->restore_code_size, operation->perm));
|
||||||
|
|
||||||
if (operation->kind == detour_operation_kind_detach && operation->trampoline) {
|
if (operation->kind == detour_operation_kind_detach && operation->trampoline) {
|
||||||
internal_detour_free_trampoline(operation->trampoline);
|
internal_detour_free_trampoline(operation->trampoline);
|
||||||
|
|
@ -675,10 +685,10 @@ mach_error_t detour_attach_ex(detour_func_t* inout_pointer, detour_func_t detour
|
||||||
const uint8_t* src = target;
|
const uint8_t* src = target;
|
||||||
uint8_t* trampoline_code = trampoline->code;
|
uint8_t* trampoline_code = trampoline->code;
|
||||||
uint8_t* trampoline_code_limit = trampoline_code + sizeof(trampoline->code);
|
uint8_t* trampoline_code_limit = trampoline_code + sizeof(trampoline->code);
|
||||||
uint32_t offset_target = 0;
|
uint32_t target_override_len = 0;
|
||||||
uint32_t align_idx = 0;
|
uint32_t align_idx = 0;
|
||||||
|
|
||||||
while (offset_target < DETOUR_PLATFORM_SIZE_OF_JMP) {
|
while (target_override_len < DETOUR_PLATFORM_SIZE_OF_JMP) {
|
||||||
const uint8_t* curr_op = src;
|
const uint8_t* curr_op = src;
|
||||||
uint32_t extra_len = 0;
|
uint32_t extra_len = 0;
|
||||||
|
|
||||||
|
|
@ -686,8 +696,8 @@ mach_error_t detour_attach_ex(detour_func_t* inout_pointer, detour_func_t detour
|
||||||
src = internal_detour_copy_instruction(trampoline_code, src, &extra_len);
|
src = internal_detour_copy_instruction(trampoline_code, src, &extra_len);
|
||||||
DETOUR_TRACE((" after: src=%p (copied %d bytes)\n", src, (int)(src - curr_op)));
|
DETOUR_TRACE((" after: src=%p (copied %d bytes)\n", src, (int)(src - curr_op)));
|
||||||
trampoline_code += (src - curr_op) + extra_len;
|
trampoline_code += (src - curr_op) + extra_len;
|
||||||
offset_target = (int32_t)(src - target);
|
target_override_len = (int32_t)(src - target);
|
||||||
trampoline->align[align_idx].offset_target = offset_target;
|
trampoline->align[align_idx].offset_target = target_override_len;
|
||||||
trampoline->align[align_idx].offset_trampoline = trampoline_code - trampoline->code;
|
trampoline->align[align_idx].offset_trampoline = trampoline_code - trampoline->code;
|
||||||
align_idx++;
|
align_idx++;
|
||||||
|
|
||||||
|
|
@ -701,14 +711,14 @@ mach_error_t detour_attach_ex(detour_func_t* inout_pointer, detour_func_t detour
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consume, but don't duplicate padding if it is needed and available.
|
// Consume, but don't duplicate padding if it is needed and available.
|
||||||
while (offset_target < DETOUR_PLATFORM_SIZE_OF_JMP) {
|
while (target_override_len < DETOUR_PLATFORM_SIZE_OF_JMP) {
|
||||||
const uint32_t len_filler = detour_platform_is_code_filler(src);
|
const uint32_t len_filler = detour_platform_is_code_filler(src);
|
||||||
if (len_filler == 0) {
|
if (len_filler == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
src += len_filler;
|
src += len_filler;
|
||||||
offset_target = (int32_t)(src - target);
|
target_override_len = (int32_t)(src - target);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DETOUR_DEBUG
|
#if DETOUR_DEBUG
|
||||||
|
|
@ -725,7 +735,7 @@ mach_error_t detour_attach_ex(detour_func_t* inout_pointer, detour_func_t detour
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (offset_target < DETOUR_PLATFORM_SIZE_OF_JMP || align_idx > ARRAYSIZE(trampoline->align)) {
|
if (target_override_len < DETOUR_PLATFORM_SIZE_OF_JMP || align_idx > ARRAYSIZE(trampoline->align)) {
|
||||||
// Too few instructions.
|
// Too few instructions.
|
||||||
error = detour_err_too_small;
|
error = detour_err_too_small;
|
||||||
if (s_ignore_too_small) {
|
if (s_ignore_too_small) {
|
||||||
|
|
@ -741,17 +751,17 @@ mach_error_t detour_attach_ex(detour_func_t* inout_pointer, detour_func_t detour
|
||||||
}
|
}
|
||||||
|
|
||||||
trampoline->code_size = (uint8_t)(trampoline_code - trampoline->code);
|
trampoline->code_size = (uint8_t)(trampoline_code - trampoline->code);
|
||||||
trampoline->restore_code_size = (uint8_t)offset_target;
|
trampoline->restore_code_size = (uint8_t)target_override_len;
|
||||||
memcpy(trampoline->restore_code, target, offset_target);
|
memcpy(trampoline->restore_code, target, target_override_len);
|
||||||
|
|
||||||
if (offset_target > sizeof(trampoline->code) - DETOUR_PLATFORM_SIZE_OF_JMP) {
|
if (target_override_len > sizeof(trampoline->code) - DETOUR_PLATFORM_SIZE_OF_JMP) {
|
||||||
// Too many instructions.
|
// Too many instructions.
|
||||||
error = detour_err_too_large;
|
error = detour_err_too_large;
|
||||||
DETOUR_BREAK();
|
DETOUR_BREAK();
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
trampoline->ptr_remain = target + offset_target;
|
trampoline->ptr_remain = target + target_override_len;
|
||||||
trampoline->ptr_detour = (uint8_t*)detour;
|
trampoline->ptr_detour = (uint8_t*)detour;
|
||||||
|
|
||||||
trampoline_code = trampoline->code + trampoline->code_size;
|
trampoline_code = trampoline->code + trampoline->code_size;
|
||||||
|
|
@ -762,7 +772,7 @@ mach_error_t detour_attach_ex(detour_func_t* inout_pointer, detour_func_t detour
|
||||||
UNUSED_VARIABLE(trampoline_code);
|
UNUSED_VARIABLE(trampoline_code);
|
||||||
|
|
||||||
const mach_port_t port = mach_task_self();
|
const mach_port_t port = mach_task_self();
|
||||||
const mach_vm_address_t page_addr = internal_detour_round_down_to_page((uintptr_t)target);
|
//const mach_vm_address_t page_addr = internal_detour_round_down_to_page((uintptr_t)target);
|
||||||
|
|
||||||
vm_region_submap_short_info_data_64_t region_info;
|
vm_region_submap_short_info_data_64_t region_info;
|
||||||
{
|
{
|
||||||
|
|
@ -779,11 +789,12 @@ mach_error_t detour_attach_ex(detour_func_t* inout_pointer, detour_func_t detour
|
||||||
}
|
}
|
||||||
const vm_prot_t old_perm = region_info.protection;
|
const vm_prot_t old_perm = region_info.protection;
|
||||||
|
|
||||||
error = mach_vm_protect(port, page_addr, PAGE_SIZE, false, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);
|
error = mach_vm_protect(port, (mach_vm_address_t)target, target_override_len, false, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);
|
||||||
if (error != err_none) {
|
if (error != err_none) {
|
||||||
DETOUR_BREAK();
|
DETOUR_BREAK();
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
DETOUR_TRACE(("mach_vm_protect(%p, %" PRIu32 ", %d)\n", (void*)target, target_override_len, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY));
|
||||||
|
|
||||||
DETOUR_TRACE(("detours: target=%p: "
|
DETOUR_TRACE(("detours: target=%p: "
|
||||||
"%02x %02x %02x %02x "
|
"%02x %02x %02x %02x "
|
||||||
|
|
@ -793,7 +804,7 @@ mach_error_t detour_attach_ex(detour_func_t* inout_pointer, detour_func_t detour
|
||||||
target[0], target[1], target[2], target[3],
|
target[0], target[1], target[2], target[3],
|
||||||
target[4], target[5], target[6], target[7],
|
target[4], target[5], target[6], target[7],
|
||||||
target[8], target[9], target[10], target[11]));
|
target[8], target[9], target[10], target[11]));
|
||||||
DETOUR_TRACE(("detours: trampoline =%p: "
|
DETOUR_TRACE(("detours: trampoline=%p: "
|
||||||
"%02x %02x %02x %02x "
|
"%02x %02x %02x %02x "
|
||||||
"%02x %02x %02x %02x "
|
"%02x %02x %02x %02x "
|
||||||
"%02x %02x %02x %02x\n",
|
"%02x %02x %02x %02x\n",
|
||||||
|
|
@ -860,7 +871,7 @@ mach_error_t detour_detach(detour_func_t* inout_pointer, detour_func_t detour)
|
||||||
detour = detour_platform_skip_jmp(detour);
|
detour = detour_platform_skip_jmp(detour);
|
||||||
|
|
||||||
// Verify that Trampoline is in place.
|
// Verify that Trampoline is in place.
|
||||||
const int32_t restore_code_size = trampoline->restore_code_size;
|
const uint32_t restore_code_size = trampoline->restore_code_size;
|
||||||
uint8_t* target = trampoline->ptr_remain - restore_code_size;
|
uint8_t* target = trampoline->ptr_remain - restore_code_size;
|
||||||
if (restore_code_size == 0 || restore_code_size > sizeof(trampoline->code)) {
|
if (restore_code_size == 0 || restore_code_size > sizeof(trampoline->code)) {
|
||||||
error = KERN_FAILURE;
|
error = KERN_FAILURE;
|
||||||
|
|
@ -899,12 +910,13 @@ mach_error_t detour_detach(detour_func_t* inout_pointer, detour_func_t detour)
|
||||||
}
|
}
|
||||||
const vm_prot_t old_perm = region_info.protection;
|
const vm_prot_t old_perm = region_info.protection;
|
||||||
|
|
||||||
error = mach_vm_protect(port, (mach_vm_address_t)target, PAGE_SIZE, false,
|
error = mach_vm_protect(port, (mach_vm_address_t)target, restore_code_size, false,
|
||||||
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);
|
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);
|
||||||
if (error != err_none) {
|
if (error != err_none) {
|
||||||
DETOUR_BREAK();
|
DETOUR_BREAK();
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
DETOUR_TRACE(("mach_vm_protect(%p, %" PRIu32 ", %d)\n", (void*)target, restore_code_size, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY));
|
||||||
|
|
||||||
op->kind = detour_operation_kind_detach;
|
op->kind = detour_operation_kind_detach;
|
||||||
op->pointer = (uint8_t**)inout_pointer;
|
op->pointer = (uint8_t**)inout_pointer;
|
||||||
|
|
@ -926,6 +938,7 @@ mach_error_t detour_attach_and_commit_ex(detour_func_t* inout_pointer, detour_fu
|
||||||
{
|
{
|
||||||
const mach_error_t error = detour_attach_ex(inout_pointer, detour, out_real_trampoline, out_real_target, out_real_detour);
|
const mach_error_t error = detour_attach_ex(inout_pointer, detour, out_real_trampoline, out_real_target, out_real_detour);
|
||||||
if (error != err_none) {
|
if (error != err_none) {
|
||||||
|
detour_transaction_abort();
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
return detour_transaction_commit();
|
return detour_transaction_commit();
|
||||||
|
|
@ -935,6 +948,7 @@ mach_error_t detour_detach_and_commit(detour_func_t* inout_pointer, detour_func_
|
||||||
{
|
{
|
||||||
const mach_error_t error = detour_detach(inout_pointer, detour);
|
const mach_error_t error = detour_detach(inout_pointer, detour);
|
||||||
if (error != err_none) {
|
if (error != err_none) {
|
||||||
|
detour_transaction_abort();
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
return detour_transaction_commit();
|
return detour_transaction_commit();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
# Copyright (c) Lysann Tranvouez. All rights reserved.
|
# Copyright (c) Lysann Tranvouez. All rights reserved.
|
||||||
|
|
||||||
add_executable(mach_detours_tests
|
add_executable(mach_detours_tests
|
||||||
test.cpp)
|
test_dylib_function.cpp
|
||||||
|
test_local_function.cpp
|
||||||
|
)
|
||||||
|
|
||||||
# The target function must be in a shared library because otherwise it might be in the same code page as the test.cpp functions.
|
# The target function must be in a shared library because otherwise it might be in the same code page as the test.cpp functions.
|
||||||
# Between attach and commit the target function's code page is not executable, which can mean our test driver code would not
|
# Between attach and commit the target function's code page is not executable, which can mean our test driver code would not
|
||||||
|
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
// Copyright (c) Lysann Tranvouez. All rights reserved.
|
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
|
||||||
|
|
||||||
#include <mach_detours.h>
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
#include "lib_function.h"
|
|
||||||
int (*realLibFunction)() = libFunction;
|
|
||||||
|
|
||||||
static int libFunctionDetourCounter = 0;
|
|
||||||
int libFunctionDetour()
|
|
||||||
{
|
|
||||||
libFunctionDetourCounter++;
|
|
||||||
return 94;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE( "Overriding custom function in dylib" )
|
|
||||||
{
|
|
||||||
libFunctionCounter = 0;
|
|
||||||
libFunctionDetourCounter = 0;
|
|
||||||
|
|
||||||
REQUIRE( libFunction() == 42 );
|
|
||||||
REQUIRE( libFunctionCounter == 1 );
|
|
||||||
REQUIRE( libFunctionDetourCounter == 0 );
|
|
||||||
|
|
||||||
CHECK( detour_transaction_begin() == err_none );
|
|
||||||
CHECK( detour_attach(reinterpret_cast<detour_func_t*>(&realLibFunction), reinterpret_cast<detour_func_t>(libFunctionDetour)) == err_none );
|
|
||||||
CHECK( detour_transaction_commit() == err_none );
|
|
||||||
|
|
||||||
REQUIRE( realLibFunction != libFunction );
|
|
||||||
|
|
||||||
REQUIRE( libFunctionCounter == 1 );
|
|
||||||
REQUIRE( libFunctionDetourCounter == 0 );
|
|
||||||
REQUIRE( libFunction() == 94 );
|
|
||||||
REQUIRE( libFunctionCounter == 1 );
|
|
||||||
REQUIRE( libFunctionDetourCounter == 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
static int localFunctionCounter = 0;
|
|
||||||
int localFunction()
|
|
||||||
{
|
|
||||||
localFunctionCounter++;
|
|
||||||
return 67;
|
|
||||||
}
|
|
||||||
static int localFunctionDetourCounter = 0;
|
|
||||||
int localFunctionDetour()
|
|
||||||
{
|
|
||||||
localFunctionDetourCounter++;
|
|
||||||
return 12;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE( "Overriding local function" )
|
|
||||||
{
|
|
||||||
int (*realFunction)() = localFunction;
|
|
||||||
localFunctionCounter = 0;
|
|
||||||
localFunctionDetourCounter = 0;
|
|
||||||
|
|
||||||
REQUIRE( localFunction() == 67 );
|
|
||||||
REQUIRE( localFunctionCounter == 1 );
|
|
||||||
REQUIRE( localFunctionDetourCounter == 0 );
|
|
||||||
|
|
||||||
CHECK( detour_transaction_begin() == err_none );
|
|
||||||
// Overriding a local function requires using detour_attach_and_commit instead of calling attach and commit individually.
|
|
||||||
// Otherwise when we return from attach (and before commit), the code page with the local function is marked as read+write (but _not_ executable).
|
|
||||||
// There is a good chance the code we return to (in this case the test function) is on the same memory page and can therefore not be executed (until we call commit).
|
|
||||||
CHECK( detour_attach_and_commit(reinterpret_cast<detour_func_t*>(&realFunction), reinterpret_cast<detour_func_t>(localFunctionDetour)) == err_none );
|
|
||||||
|
|
||||||
REQUIRE( realFunction != localFunction );
|
|
||||||
|
|
||||||
REQUIRE( localFunctionCounter == 1 );
|
|
||||||
REQUIRE( localFunctionDetourCounter == 0 );
|
|
||||||
REQUIRE( localFunction() == 12 );
|
|
||||||
REQUIRE( localFunctionCounter == 1 );
|
|
||||||
REQUIRE( localFunctionDetourCounter == 1 );
|
|
||||||
}
|
|
||||||
108
tests/test_dylib_function.cpp
Normal file
108
tests/test_dylib_function.cpp
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright (c) Lysann Tranvouez. All rights reserved.
|
||||||
|
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
#include <mach_detours.h>
|
||||||
|
|
||||||
|
#include "lib_function.h"
|
||||||
|
int (*realLibFunction)() = libFunction;
|
||||||
|
|
||||||
|
static int libFunctionDetourCounter = 0;
|
||||||
|
int libFunctionDetour()
|
||||||
|
{
|
||||||
|
libFunctionDetourCounter++;
|
||||||
|
return 94;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE( "Overriding custom function in dylib", "[dylib]" )
|
||||||
|
{
|
||||||
|
libFunctionCounter = 0;
|
||||||
|
libFunctionDetourCounter = 0;
|
||||||
|
|
||||||
|
SECTION( "attaching installs a detour" )
|
||||||
|
{
|
||||||
|
REQUIRE( realLibFunction == libFunction );
|
||||||
|
REQUIRE( libFunction() == 42 );
|
||||||
|
REQUIRE( libFunctionCounter == 1 );
|
||||||
|
REQUIRE( libFunctionDetourCounter == 0 );
|
||||||
|
|
||||||
|
REQUIRE( detour_transaction_begin() == err_none );
|
||||||
|
|
||||||
|
SECTION( "attach + transaction_commit" )
|
||||||
|
{
|
||||||
|
REQUIRE( detour_attach(reinterpret_cast<detour_func_t*>(&realLibFunction), reinterpret_cast<detour_func_t>(libFunctionDetour)) == err_none );
|
||||||
|
CHECK( detour_transaction_commit() == err_none );
|
||||||
|
}
|
||||||
|
SECTION( "attach_and_commit" )
|
||||||
|
{
|
||||||
|
CHECK( detour_attach_and_commit(reinterpret_cast<detour_func_t*>(&realLibFunction), reinterpret_cast<detour_func_t>(libFunctionDetour)) == err_none );
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK( realLibFunction != libFunction );
|
||||||
|
CHECK( libFunctionCounter == 1 );
|
||||||
|
CHECK( libFunctionDetourCounter == 0 );
|
||||||
|
CHECK( libFunction() == 94 );
|
||||||
|
CHECK( libFunctionCounter == 1 );
|
||||||
|
CHECK( libFunctionDetourCounter == 1 );
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
REQUIRE( detour_transaction_begin() == err_none );
|
||||||
|
CHECK( detour_detach_and_commit(reinterpret_cast<detour_func_t*>(&realLibFunction), reinterpret_cast<detour_func_t>(libFunctionDetour)) == err_none );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION( "detaching in separate transaction removes detour" )
|
||||||
|
{
|
||||||
|
REQUIRE( detour_transaction_begin() == err_none );
|
||||||
|
REQUIRE( detour_attach_and_commit(reinterpret_cast<detour_func_t*>(&realLibFunction), reinterpret_cast<detour_func_t>(libFunctionDetour)) == err_none );
|
||||||
|
REQUIRE( realLibFunction != libFunction );
|
||||||
|
|
||||||
|
REQUIRE( detour_transaction_begin() == err_none );
|
||||||
|
|
||||||
|
SECTION( "detach + transaction_commit" )
|
||||||
|
{
|
||||||
|
REQUIRE( detour_detach(reinterpret_cast<detour_func_t*>(&realLibFunction), reinterpret_cast<detour_func_t>(libFunctionDetour)) == err_none );
|
||||||
|
CHECK( detour_transaction_commit() == err_none );
|
||||||
|
}
|
||||||
|
SECTION( "detach_and_commit" )
|
||||||
|
{
|
||||||
|
CHECK( detour_detach_and_commit(reinterpret_cast<detour_func_t*>(&realLibFunction), reinterpret_cast<detour_func_t>(libFunctionDetour)) == err_none );
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK( realLibFunction == libFunction );
|
||||||
|
CHECK( libFunctionCounter == 0 );
|
||||||
|
CHECK( libFunctionDetourCounter == 0 );
|
||||||
|
CHECK( libFunction() == 42 );
|
||||||
|
CHECK( libFunctionCounter == 1 );
|
||||||
|
CHECK( libFunctionDetourCounter == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION( "aborting transaction means no detour" )
|
||||||
|
{
|
||||||
|
REQUIRE( detour_transaction_begin() == err_none );
|
||||||
|
REQUIRE( detour_attach(reinterpret_cast<detour_func_t*>(&realLibFunction), reinterpret_cast<detour_func_t>(libFunctionDetour)) == err_none );
|
||||||
|
CHECK( detour_transaction_abort() == err_none );
|
||||||
|
|
||||||
|
CHECK( realLibFunction == libFunction );
|
||||||
|
CHECK( libFunctionCounter == 0 );
|
||||||
|
CHECK( libFunctionDetourCounter == 0 );
|
||||||
|
CHECK( libFunction() == 42 );
|
||||||
|
CHECK( libFunctionCounter == 1 );
|
||||||
|
CHECK( libFunctionDetourCounter == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION( "an error in a transaction means no detour" )
|
||||||
|
{
|
||||||
|
REQUIRE( detour_transaction_begin() == err_none );
|
||||||
|
REQUIRE( detour_attach(reinterpret_cast<detour_func_t*>(&realLibFunction), reinterpret_cast<detour_func_t>(libFunctionDetour)) == err_none );
|
||||||
|
// cannot detach because trampoline is not yet in place
|
||||||
|
CHECK( detour_detach(reinterpret_cast<detour_func_t*>(&realLibFunction), reinterpret_cast<detour_func_t>(libFunctionDetour)) == KERN_FAILURE );
|
||||||
|
CHECK( detour_transaction_commit() == KERN_FAILURE );
|
||||||
|
|
||||||
|
CHECK( realLibFunction == libFunction );
|
||||||
|
CHECK( libFunctionCounter == 0 );
|
||||||
|
CHECK( libFunctionDetourCounter == 0 );
|
||||||
|
CHECK( libFunction() == 42 );
|
||||||
|
CHECK( libFunctionCounter == 1 );
|
||||||
|
CHECK( libFunctionDetourCounter == 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
47
tests/test_local_function.cpp
Normal file
47
tests/test_local_function.cpp
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright (c) Lysann Tranvouez. All rights reserved.
|
||||||
|
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
#include <mach_detours.h>
|
||||||
|
|
||||||
|
static int localFunctionCounter = 0;
|
||||||
|
int localFunction()
|
||||||
|
{
|
||||||
|
localFunctionCounter++;
|
||||||
|
return 67;
|
||||||
|
}
|
||||||
|
int (*realLocalFunction)() = localFunction;
|
||||||
|
static int localFunctionDetourCounter = 0;
|
||||||
|
int localFunctionDetour()
|
||||||
|
{
|
||||||
|
localFunctionDetourCounter++;
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE( "Overriding local function", "[local][attach]" )
|
||||||
|
{
|
||||||
|
localFunctionCounter = 0;
|
||||||
|
localFunctionDetourCounter = 0;
|
||||||
|
|
||||||
|
CHECK( realLocalFunction == localFunction );
|
||||||
|
CHECK( localFunction() == 67 );
|
||||||
|
CHECK( localFunctionCounter == 1 );
|
||||||
|
CHECK( localFunctionDetourCounter == 0 );
|
||||||
|
|
||||||
|
CHECK( detour_transaction_begin() == err_none );
|
||||||
|
// Overriding a local function requires using detour_attach_and_commit instead of calling attach and commit individually.
|
||||||
|
// Otherwise when we return from attach (and before commit), the code page with the local function is marked as read+write (but _not_ executable).
|
||||||
|
// There is a good chance the code we return to (in this case the test function) is on the same memory page and can therefore not be executed (until we call commit).
|
||||||
|
CHECK( detour_attach_and_commit(reinterpret_cast<detour_func_t*>(&realLocalFunction), reinterpret_cast<detour_func_t>(localFunctionDetour)) == err_none );
|
||||||
|
|
||||||
|
CHECK( realLocalFunction != localFunction );
|
||||||
|
CHECK( localFunctionCounter == 1 );
|
||||||
|
CHECK( localFunctionDetourCounter == 0 );
|
||||||
|
CHECK( localFunction() == 12 );
|
||||||
|
CHECK( localFunctionCounter == 1 );
|
||||||
|
CHECK( localFunctionDetourCounter == 1 );
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
CHECK( detour_transaction_begin() == err_none );
|
||||||
|
CHECK( detour_detach_and_commit(reinterpret_cast<detour_func_t*>(&realLocalFunction), reinterpret_cast<detour_func_t>(localFunctionDetour)) == err_none );
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue