Created
June 23, 2022 11:07
-
-
Save skmp/1aa00f7c89bf1825449d74aaa7d17985 to your computer and use it in GitHub Desktop.
Copyable Functions v2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Example of creating function specializations at run time from a "canonical" template provided by the compiler | |
// More details: https://github.com/FEX-Emu/fex-assorted-tests-bins/tree/main/src/copyable-functions | |
#include <cstdint> | |
#include <cstring> | |
#include <cstdio> | |
#include <cstdlib> | |
#include <cassert> | |
#include <tuple> | |
#include <sys/mman.h> | |
// New and updated v2 | |
// Depends on asm goto to resurect dead code after return | |
// x86/x86-64, for other architectures, the asm / init_offsets logic needs to be updated | |
class binder { | |
using offsets_t = std::tuple<int, int>; | |
struct alignas(16) instance_info_t { | |
void *target; | |
void *marshaler; | |
}; | |
template<typename R, typename... Args> | |
struct inner; | |
template<typename R, typename... Args> | |
struct inner<R(Args...)> { | |
using target_t = R(Args...); | |
using marshaler_t = R(Args..., target_t *target); | |
static const offsets_t offsets; | |
static R canonical(Args... args) { | |
instance_info_t *instance_info; | |
if constexpr (sizeof(void*) == 8) | |
asm("0: mov $0x1122334444332211, %0" :"=r"(instance_info)); | |
else | |
asm("0: mov $0x11223344, %0" :"=r"(instance_info)); | |
// force the label to exist | |
asm goto (""::::data); | |
return reinterpret_cast<marshaler_t *>(instance_info->marshaler)(args..., reinterpret_cast<target_t *>(instance_info->target)); | |
data: | |
asm( | |
"1: .quad 0xdeadbeefdeadbeef \n" | |
".quad 0b - 1b + 2" | |
); | |
__builtin_unreachable(); | |
} | |
}; | |
static int find_pattern(uint8_t *canonical_fn_ptr, uint64_t pattern, size_t size) { | |
constexpr auto max_fn_size = 1024; | |
for (int offset = 0; offset < max_fn_size; offset++ ) { | |
if (memcmp(canonical_fn_ptr + offset, &pattern, size) == 0) { | |
return offset; | |
} | |
} | |
assert("Failed to find pattern in canonical function" && 0); | |
return -1; | |
} | |
static offsets_t init_offsets(uint8_t *canonical_ptr) { | |
auto end_offset = find_pattern(canonical_ptr, 0xDEADBEEFDEADBEEF, 8); | |
auto patch_offset = end_offset + *reinterpret_cast<int64_t*>(canonical_ptr + end_offset + 8); | |
return { | |
patch_offset, | |
end_offset, | |
}; | |
} | |
static void *make_instance(void *canonical, void *target, void *marshaler, offsets_t offsets) { | |
auto canonical_fn_ptr = (uint8_t*)canonical; | |
auto [found_patch_offset, found_end_offset] = offsets; | |
assert("Failed to find patch offset in canonical function" && found_patch_offset != -1); | |
assert("Failed to find end in canonical function" && found_end_offset != -1); | |
auto function_size = found_end_offset; | |
auto align_minus_one = alignof(instance_info_t) - 1; | |
auto alloc_size = (function_size + align_minus_one + sizeof(instance_info_t)) & ~align_minus_one; | |
printf ("Section len: %d, alloc len: %d\n", (int)function_size, (int)alloc_size); | |
printf("found_patch_offset: %d\n", found_patch_offset); | |
// alloc | |
auto instance = (uint8_t*)mmap(0, alloc_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | |
// copy | |
memcpy(instance, canonical_fn_ptr, function_size); | |
// bind | |
auto code_offset = 0; | |
auto info_offset = alloc_size - sizeof(instance_info_t); | |
printf("code_offset offset: %d\n", (int)code_offset); | |
printf("info_offset offset: %d\n", (int)info_offset); | |
auto info = (instance_info_t*)(instance + info_offset); | |
info->target = target; | |
info->marshaler = marshaler; | |
return (instance + code_offset); | |
} | |
public: | |
template<typename T> | |
static T *make_instance(T *target, typename inner<T>::marshaler_t *marshaler); | |
}; | |
#define DECL_COPYABLE_TRAMPLOLINE(fn_type) \ | |
template<> const binder::offsets_t binder::inner<fn_type>::offsets = init_offsets(reinterpret_cast<uint8_t*>(&canonical)); \ | |
template<> \ | |
fn_type *binder::make_instance<fn_type>(fn_type *target, binder::inner<fn_type>::marshaler_t *marshaler) { \ | |
return reinterpret_cast<fn_type *>(binder::make_instance(reinterpret_cast<void*>(&binder::inner<fn_type>::canonical), reinterpret_cast<void*>(target), reinterpret_cast<void*>(marshaler), binder::inner<fn_type>::offsets)); \ | |
} | |
int test_func (int a, const char *b); | |
using type_one = decltype(test_func); | |
int test_func_marshaler (int a, const char *b, type_one *fn_ptr) { | |
printf("%s: Called with %d, %s, %p\n", __func__, a, b, fn_ptr); | |
return fn_ptr(a, b); | |
} | |
int test_func_to_call_1(int a, const char *b) { printf("%s: Called with %d, %s\n", __func__, a, b); return -1; } | |
int test_func_to_call_2(int a, const char *b) { printf("%s: Called with %d, %s\n", __func__, a, b); return -2; } | |
int test_func2 (const char *a, int b); | |
using type_two = decltype(test_func2); | |
int test_func2_marshaler (const char *a, int b, type_two *target) { | |
printf("%s: Called with %s, %d, %p\n", __func__, a, b, target); | |
return target(a, b); | |
} | |
int test_func2_to_call_1(const char *b, int a) { printf("%s: Called with %d, %s\n", __func__, a, b); return -1; } | |
int test_func2_to_call_2(const char *b, int a) { printf("%s: Called with %d, %s\n", __func__, a, b); return -2; } | |
DECL_COPYABLE_TRAMPLOLINE(type_one) | |
DECL_COPYABLE_TRAMPLOLINE(type_two) | |
int main (void) | |
{ | |
auto fn1 = binder::make_instance<type_one>(&test_func_to_call_1, &test_func_marshaler); | |
auto fn2 = binder::make_instance<type_one>(&test_func_to_call_2, &test_func_marshaler); | |
auto fn3 = binder::make_instance<type_two>(&test_func2_to_call_2, &test_func2_marshaler); | |
auto fn4 = binder::make_instance<type_two>(&test_func2_to_call_2, &test_func2_marshaler); | |
auto rv1 = fn1(1, "test stuff here 1"); | |
printf("rv1: %d\n", rv1); | |
auto rv2 = fn2(2, "test stuff here 2"); | |
printf("rv2: %d\n", rv2); | |
auto rv3 = fn3("test stuff here 1", 3); | |
printf("rv3: %d\n", rv1); | |
auto rv4 = fn4("test stuff here 2", 4); | |
printf("rv4: %d\n", rv2); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment