mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 09:00:17 +02:00
- Do compiler/architecture checks EARLY, don't waste time doing random configuration stuff if the user can't even compile in the first place - MSVC is unsupported, I assume? So add a check to disallow. There's literally no MSVC or MSC_VER checks anywhere, so... - Rather than using the MSVC architecture definitions, use our own `ARCHITECTURE_arm64` et al. Hijacking existing "standard" definitions is a very bad idea. Also makes it more readable in CMake - Change the x86 host check to `x86|amd64`. Some systems still refer to themselves as x86 despite being 64-bit for... reasons, and I saw one a very long time ago that referred to it as amd64. This should basically never come up, nor is it really relevant given that FEX is for arm64... but it kinda annoyed me so whatever. TODOs: - Should we check `CMAKE_SIZEOF_VOID_P (equal) 64`? I don't think anyone is even trying to compile this thing on armv7 or older, but might as well? maybe? - What's the status of *BSD, Solaris, macOS? Technically macOS does support Wine, not sure about the others. Signed-off-by: crueter <crueter@eden-emu.dev>
219 lines
9.0 KiB
C++
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 ARCHITECTURE_arm64
|
|
#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 ARCHITECTURE_arm64
|
|
#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;
|
|
}
|