Skip to content

Instantly share code, notes, and snippets.

@skmp
Created June 23, 2022 11:07
Show Gist options
  • Save skmp/1aa00f7c89bf1825449d74aaa7d17985 to your computer and use it in GitHub Desktop.
Save skmp/1aa00f7c89bf1825449d74aaa7d17985 to your computer and use it in GitHub Desktop.
Copyable Functions v2
// 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