FEX/ThunkLibs/include/common/Guest.h
Alyssa Rosenzweig a10f984b1c clang-format: left-align escaped newlines
alternative to #3638. this is theoretically better for side-by-side diffs. in
practice it may make other diffs worse since all the \'s change when part of the
macro change.

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
2024-05-20 09:47:21 -04:00

219 lines
9.0 KiB
C++

#pragma once
#include <stdint.h>
#include <type_traits>
#include "PackedArguments.h"
#if __SIZEOF_POINTER__ == 8
#define THUNK_ABI
#else
#ifdef __clang__
#define THUNK_ABI __fastcall
#else
#define THUNK_ABI __attribute__((fastcall))
#endif
#endif
template<typename signature>
THUNK_ABI const int (*fexthunks_invoke_callback)(void*);
#ifndef _M_ARM_64
#define MAKE_THUNK(lib, name, hash) \
extern "C" __attribute__((visibility("hidden"))) THUNK_ABI int fexthunks_##lib##_##name(void* args); \
asm(".text\nfexthunks_" #lib "_" #name ":\n.byte 0xF, 0x3F\n.byte " hash);
#define MAKE_CALLBACK_THUNK(name, signature, hash) \
extern "C" __attribute__((visibility("hidden"))) THUNK_ABI int fexthunks_##name(void* args); \
asm(".text\nfexthunks_" #name ":\n.byte 0xF, 0x3F\n.byte " hash); \
template<> \
THUNK_ABI inline constexpr int (*fexthunks_invoke_callback<signature>)(void*) = fexthunks_##name;
#else
// We're compiling for IDE integration, so provide a dummy-implementation that just calls an undefined function.
// The name of that function serves as an error message if this library somehow gets loaded at runtime.
extern "C" void BROKEN_INSTALL___TRIED_LOADING_AARCH64_BUILD_OF_GUEST_THUNK();
#define MAKE_THUNK(lib, name, hash) \
extern "C" int fexthunks_##lib##_##name(void* args) { \
BROKEN_INSTALL___TRIED_LOADING_AARCH64_BUILD_OF_GUEST_THUNK(); \
return 0; \
}
#define MAKE_CALLBACK_THUNK(name, signature, hash) \
extern "C" int fexthunks_##name(void* args); \
template<> \
inline constexpr int (*fexthunks_invoke_callback<signature>)(void*) = fexthunks_##name;
#endif
// Generated fexfn_pack_ symbols should be hidden by default, but clang does
// not support aliasing to static functions. Make them regular non-static
// functions on that compiler instead, hence.
#if defined(__clang__)
#define FEX_PACKFN_LINKAGE
#else
#define FEX_PACKFN_LINKAGE static
#endif
struct LoadlibArgs {
const char* Name;
uintptr_t CallbackThunks;
};
MAKE_THUNK(fex, loadlib,
"0x27, 0x7e, 0xb7, 0x69, 0x5b, 0xe9, 0xab, 0x12, 0x6e, 0xf7, 0x85, 0x9d, 0x4b, 0xc9, 0xa2, 0x44, 0x46, 0xcf, 0xbd, 0xb5, 0x87, "
"0x43, 0xef, 0x28, 0xa2, 0x65, 0xba, 0xfc, 0x89, 0x0f, 0x77, 0x80")
MAKE_THUNK(fex, is_lib_loaded,
"0xee, 0x57, 0xba, 0x0c, 0x5f, 0x6e, 0xef, 0x2a, 0x8c, 0xb5, 0x19, 0x81, 0xc9, 0x23, 0xe6, 0x51, 0xae, 0x65, 0x02, 0x8f, 0x2b, "
"0x5d, 0x59, 0x90, 0x6a, 0x7e, 0xe2, 0xe7, 0x1c, 0x33, 0x8a, 0xff")
MAKE_THUNK(fex, is_host_heap_allocation,
"0xf5, 0x77, 0x68, 0x43, 0xbb, 0x6b, 0x28, 0x18, 0x40, 0xb0, 0xdb, 0x8a, 0x66, 0xfb, 0x0e, 0x2d, 0x98, 0xc2, 0xad, 0xe2, 0x5a, "
"0x18, 0x5a, 0x37, 0x2e, 0x13, 0xc9, 0xe7, 0xb9, 0x8c, 0xa9, 0x3e")
MAKE_THUNK(fex, link_address_to_function,
"0xe6, 0xa8, 0xec, 0x1c, 0x7b, 0x74, 0x35, 0x27, 0xe9, 0x4f, 0x5b, 0x6e, 0x2d, 0xc9, 0xa0, 0x27, 0xd6, 0x1f, 0x2b, 0x87, 0x8f, "
"0x2d, 0x35, 0x50, 0xea, 0x16, 0xb8, 0xc4, 0x5e, 0x42, 0xfd, 0x77")
MAKE_THUNK(fex, allocate_host_trampoline_for_guest_function,
"0x9b, 0xb2, 0xf4, 0xb4, 0x83, 0x7d, 0x28, 0x93, 0x40, 0xcb, 0xf4, 0x7a, 0x0b, 0x47, 0x85, 0x87, 0xf9, 0xbc, 0xb5, 0x27, 0xca, "
"0xa6, 0x93, 0xa5, 0xc0, 0x73, 0x27, 0x24, 0xae, 0xc8, 0xb8, 0x5a")
#define LOAD_LIB_BASE(name, init_fn) \
__attribute__((constructor)) static void loadlib() { \
LoadlibArgs args = {#name}; \
fexthunks_fex_loadlib(&args); \
if ((init_fn)) ((void (*)())init_fn)(); \
}
#define LOAD_LIB(name) LOAD_LIB_BASE(name, nullptr)
#define LOAD_LIB_INIT(name, init_fn) LOAD_LIB_BASE(name, init_fn)
inline void LinkAddressToFunction(uintptr_t addr, uintptr_t target) {
struct args_t {
uint64_t original_callee;
uint64_t target_addr; // Function to call when branching to replaced_addr
};
args_t args = {addr, target};
fexthunks_fex_link_address_to_function(&args);
}
inline bool IsLibLoaded(const char* libname) {
struct {
const char* Name;
bool rv;
} argsrv = {libname};
fexthunks_fex_is_lib_loaded(&argsrv);
return argsrv.rv;
}
// Helper template that packs the given arguments and invokes a thunk at the
// address stored in the `r11` guest register. The signature of the thunk must
// be specified at compile-time via the Thunk template parameter.
// Other than reading the thunk address from `r11`, this is equivalent to the
// fexfn_pack_* functions generated for global API functions.
template<auto Thunk, typename Result, typename... Args>
inline Result CallHostFunction(Args... args) {
#ifndef _M_ARM_64
#if __SIZEOF_POINTER__ == 8
// This magic incantation of using a register variable with an empty asm block is necessary for correct operation!
// If we only use inline asm that sets a variable then the compiler will reorder the function
// prologue to be BEFORE our inline asm. Which makes sense in hindsight, but for anything with 8+ arguments this
// will clobber our r11 register we save the data that is inside of it.
// First we need to declare the r11 register variable
register uintptr_t host_addr asm("r11");
// We then create an empty *volatile* asm block saying that it is assigning the register variable.
// Yes, it is already set coming in to this function due to custom ABI.
// This gets both GCC and Clang to understand that the variable is set, seemingly at the start of the function.
// So its own internal live-range tracking extends its begining range to the start of the function.
//
// To verify this in the future, search for `mov r11` in binaryninja, and ensure that all uses inside of `CallHostFunction`
// don't have intersecting ranges.
//
// Note that this issue is more likely to occur when clang is used to compile thunks, since its optimizer is more aggressive at using R11.
// This magic incantation also works in that instance so this is about the best we can do without adding a new attribute to clang for
// modifying the ABI.
asm volatile("" : "=r"(host_addr));
#else
// Use mm0 to pass in host_addr (chosen to avoid conflicts with vectorcall).
// Note this register overlaps the x87 st(0) register (used to return float values),
// so applications that expect this register to be preserved could run into problems.
uintptr_t host_addr;
asm volatile("movd %%mm0, %0" : "=r"(host_addr));
#endif
#else
uintptr_t host_addr = 0;
#endif
PackedArguments<Result, Args..., uint64_t> packed_args = {
args..., host_addr
// Return value not explicitly initialized since an initializer would fail to compile for the void case
};
Thunk(reinterpret_cast<void*>(&packed_args));
if constexpr (!std::is_void_v<Result>) {
return packed_args.rv;
}
}
// Convenience wrapper that returns the function pointer to a CallHostFunction
// instantiation matching the function signature of `host_func`
template<typename Result, typename... Args>
static auto GetCallerForHostFunction(Result (*host_func)(Args...)) -> Result (*)(Args...) {
return &CallHostFunction<fexthunks_invoke_callback<Result(Args...)>, Result, Args...>;
}
// Ensures the given host function can safely be called from guest code.
template<typename Result, typename... Args>
inline void MakeHostFunctionGuestCallable(THUNK_ABI Result (*host_func)(Args...)) {
auto caller = (uintptr_t)GetCallerForHostFunction(host_func);
LinkAddressToFunction((uintptr_t)host_func, (uintptr_t)caller);
}
template<typename Target>
inline Target* AllocateHostTrampolineForGuestFunction(void THUNK_ABI (*GuestUnpacker)(uintptr_t, void*), Target* GuestTarget) {
if (!GuestTarget) {
return 0;
}
struct {
uint64_t GuestUnpacker;
uint64_t GuestTarget;
uint64_t rv;
} argsrv = {(uintptr_t)GuestUnpacker, (uintptr_t)GuestTarget};
fexthunks_fex_allocate_host_trampoline_for_guest_function((void*)&argsrv);
return (Target*)argsrv.rv;
}
template<typename F>
struct CallbackUnpack;
template<typename Result, typename... Args>
struct CallbackUnpack<Result(Args...)> {
static void THUNK_ABI Unpack(uintptr_t cb, void* argsv) {
using fn_t = Result(Args...);
auto callback = reinterpret_cast<fn_t*>(cb);
auto args = reinterpret_cast<PackedArguments<Result, Args...>*>(argsv);
Invoke(callback, *args);
}
};
template<typename Result, typename... Args>
struct CallbackUnpack<Result (*)(Args...)> : CallbackUnpack<Result(Args...)> {};
template<typename Target>
inline Target* AllocateHostTrampolineForGuestFunction(Target* GuestTarget) {
return AllocateHostTrampolineForGuestFunction(CallbackUnpack<Target*>::Unpack, GuestTarget);
}
inline bool IsHostHeapAllocation(void* ptr) {
struct {
void* ptr;
bool rv;
} args = {ptr, {}};
fexthunks_fex_is_host_heap_allocation(&args);
return args.rv;
}