diff --git a/FEXCore/Source/CMakeLists.txt b/FEXCore/Source/CMakeLists.txt index c3183d53ce..3a56e03b82 100644 --- a/FEXCore/Source/CMakeLists.txt +++ b/FEXCore/Source/CMakeLists.txt @@ -127,7 +127,6 @@ set (SRCS Interface/Core/X86Tables/VEXTables.cpp Interface/Core/X86Tables/X87Tables.cpp Interface/Core/X86Tables/XOPTables.cpp - Interface/HLE/Thunks/Thunks.cpp Interface/GDBJIT/GDBJIT.cpp Interface/IR/AOTIR.cpp Interface/IR/IRDumper.cpp diff --git a/FEXCore/Source/Interface/Context/Context.cpp b/FEXCore/Source/Interface/Context/Context.cpp index 91a0ff082c..65ed7b9114 100644 --- a/FEXCore/Source/Interface/Context/Context.cpp +++ b/FEXCore/Source/Interface/Context/Context.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "FEXCore/Debug/InternalThreadState.h" #include @@ -48,6 +49,10 @@ void FEXCore::Context::ContextImpl::SetSyscallHandler(FEXCore::HLE::SyscallHandl SourcecodeResolver = Handler->GetSourcecodeResolver(); } +void FEXCore::Context::ContextImpl::SetThunkHandler(FEXCore::ThunkHandler* Handler) { + ThunkHandler = Handler; +} + FEXCore::CPUID::FunctionResults FEXCore::Context::ContextImpl::RunCPUIDFunction(uint32_t Function, uint32_t Leaf) { return CPUID.RunFunction(Function, Leaf); } diff --git a/FEXCore/Source/Interface/Context/Context.h b/FEXCore/Source/Interface/Context/Context.h index ad8b20cc09..429a33e8d7 100644 --- a/FEXCore/Source/Interface/Context/Context.h +++ b/FEXCore/Source/Interface/Context/Context.h @@ -68,17 +68,9 @@ struct CustomIRResult { void* Creator; void* Data; - explicit operator bool() const noexcept { - return !lock; - } - - CustomIRResult(std::unique_lock&& lock, void* Creator, void* Data) + CustomIRResult(void* Creator, void* Data) : Creator(Creator) - , Data(Data) - , lock(std::move(lock)) {} - -private: - std::unique_lock lock; + , Data(Data) {} }; using BlockDelinkerFunc = void (*)(FEXCore::Core::CpuStateFrame* Frame, FEXCore::Context::ExitFunctionLinkData* Record); @@ -155,6 +147,7 @@ class ContextImpl final : public FEXCore::Context::Context { #endif void SetSignalDelegator(FEXCore::SignalDelegator* SignalDelegation) override; void SetSyscallHandler(FEXCore::HLE::SyscallHandler* Handler) override; + void SetThunkHandler(FEXCore::ThunkHandler* Handler) override; FEXCore::CPUID::FunctionResults RunCPUIDFunction(uint32_t Function, uint32_t Leaf) override; FEXCore::CPUID::XCRResults RunXCRFunction(uint32_t Function) override; @@ -194,9 +187,10 @@ class ContextImpl final : public FEXCore::Context::Context { bool IsAddressInCodeBuffer(FEXCore::Core::InternalThreadState* Thread, uintptr_t Address) const override; // returns false if a handler was already registered - CustomIRResult AddCustomIREntrypoint(uintptr_t Entrypoint, CustomIREntrypointHandler Handler, void* Creator = nullptr, void* Data = nullptr); + std::optional + AddCustomIREntrypoint(uintptr_t Entrypoint, CustomIREntrypointHandler Handler, void* Creator = nullptr, void* Data = nullptr); - void AppendThunkDefinitions(std::span Definitions) override; + void AddThunkTrampolineIRHandler(uintptr_t Entrypoint, uintptr_t GuestThunkEntrypoint) override; public: friend class FEXCore::HLE::SyscallHandler; @@ -228,9 +222,6 @@ class ContextImpl final : public FEXCore::Context::Context { FEX_CONFIG_OPT(SMCChecks, SMCCHECKS); FEX_CONFIG_OPT(MaxInstPerBlock, MAXINST); FEX_CONFIG_OPT(RootFSPath, ROOTFS); - FEX_CONFIG_OPT(ThunkHostLibsPath, THUNKHOSTLIBS); - FEX_CONFIG_OPT(ThunkHostLibsPath32, THUNKHOSTLIBS32); - FEX_CONFIG_OPT(ThunkConfigFile, THUNKCONFIG); FEX_CONFIG_OPT(GlobalJITNaming, GLOBALJITNAMING); FEX_CONFIG_OPT(LibraryJITNaming, LIBRARYJITNAMING); FEX_CONFIG_OPT(BlockJITNaming, BLOCKJITNAMING); @@ -255,7 +246,7 @@ class ContextImpl final : public FEXCore::Context::Context { FEXCore::CPUIDEmu CPUID; FEXCore::HLE::SyscallHandler* SyscallHandler {}; FEXCore::HLE::SourcecodeResolver* SourcecodeResolver {}; - fextl::unique_ptr ThunkHandler; + FEXCore::ThunkHandler* ThunkHandler {}; fextl::unique_ptr Dispatcher; FEXCore::Context::ExitHandler CustomExitHandler; diff --git a/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.cpp b/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.cpp index 58bc8a1469..e1472e9d0e 100644 --- a/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.cpp +++ b/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.cpp @@ -4,7 +4,6 @@ #include "FEXCore/Utils/AllocatorHooks.h" #include "Interface/Core/Dispatcher/Dispatcher.h" #include "Interface/Context/Context.h" -#include "Interface/HLE/Thunks/Thunks.h" #include #include diff --git a/FEXCore/Source/Interface/Core/Core.cpp b/FEXCore/Source/Interface/Core/Core.cpp index 37726b277c..9774cddfc7 100644 --- a/FEXCore/Source/Interface/Core/Core.cpp +++ b/FEXCore/Source/Interface/Core/Core.cpp @@ -19,7 +19,6 @@ desc: Glues Frontend, OpDispatcher and IR Opts & Compilation, LookupCache, Dispa #include "Interface/Core/JIT/JITCore.h" #include "Interface/Core/Dispatcher/Dispatcher.h" #include "Interface/Core/X86Tables/X86Tables.h" -#include "Interface/HLE/Thunks/Thunks.h" #include "Interface/IR/IR.h" #include "Interface/IR/IREmitter.h" #include "Interface/IR/Passes/RegisterAllocationPass.h" @@ -34,6 +33,7 @@ desc: Glues Frontend, OpDispatcher and IR Opts & Compilation, LookupCache, Dispa #include #include #include +#include #include #include #include @@ -346,7 +346,6 @@ bool ContextImpl::InitCore() { SignalDelegation->SetConfig(SignalConfig); #ifndef _WIN32 - ThunkHandler = FEXCore::ThunkHandler::Create(); #elif !defined(_M_ARM64EC) // WOW64 always needs the interrupt fault check to be enabled. Config.NeedsPendingInterruptFaultCheck = true; @@ -386,9 +385,6 @@ void ContextImpl::ExecuteThread(FEXCore::Core::InternalThreadState* Thread) { void ContextImpl::InitializeThreadTLSData(FEXCore::Core::InternalThreadState* Thread) { // Let's do some initial bookkeeping here - if (ThunkHandler) { - ThunkHandler->RegisterTLSState(Thread); - } #ifndef _WIN32 Alloc::OSAllocator::RegisterTLSData(Thread); #endif @@ -982,7 +978,8 @@ void ContextImpl::ThreadRemoveCodeEntry(FEXCore::Core::InternalThreadState* Thre Thread->LookupCache->Erase(Thread->CurrentFrame, GuestRIP); } -CustomIRResult ContextImpl::AddCustomIREntrypoint(uintptr_t Entrypoint, CustomIREntrypointHandler Handler, void* Creator, void* Data) { +std::optional +ContextImpl::AddCustomIREntrypoint(uintptr_t Entrypoint, CustomIREntrypointHandler Handler, void* Creator, void* Data) { LOGMAN_THROW_A_FMT(Config.Is64BitMode || !(Entrypoint >> 32), "64-bit Entrypoint in 32-bit mode {:x}", Entrypoint); std::unique_lock lk(CustomIRMutex); @@ -992,10 +989,50 @@ CustomIRResult ContextImpl::AddCustomIREntrypoint(uintptr_t Entrypoint, CustomIR if (!InsertedIterator.second) { const auto& [fn, Creator, Data] = InsertedIterator.first->second; - return CustomIRResult(std::move(lk), Creator, Data); - } else { - lk.unlock(); - return CustomIRResult(std::move(lk), 0, 0); + return CustomIRResult(Creator, Data); + } + + return std::nullopt; +} + +void ContextImpl::AddThunkTrampolineIRHandler(uintptr_t Entrypoint, uintptr_t GuestThunkEntrypoint) { + LOGMAN_THROW_AA_FMT(Entrypoint, "Tried to link null pointer address to guest function"); + LOGMAN_THROW_AA_FMT(GuestThunkEntrypoint, "Tried to link address to null pointer guest function"); + if (!Config.Is64BitMode) { + LOGMAN_THROW_AA_FMT((Entrypoint >> 32) == 0, "Tried to link 64-bit address in 32-bit mode"); + LOGMAN_THROW_AA_FMT((GuestThunkEntrypoint >> 32) == 0, "Tried to link 64-bit address in 32-bit mode"); + } + + LogMan::Msg::DFmt("Thunks: Adding guest trampoline from address {:#x} to guest function {:#x}", Entrypoint, GuestThunkEntrypoint); + + auto Result = AddCustomIREntrypoint( + Entrypoint, + [this, GuestThunkEntrypoint](uintptr_t Entrypoint, FEXCore::IR::IREmitter* emit) { + auto IRHeader = emit->_IRHeader(emit->Invalid(), Entrypoint, 0, 0); + auto Block = emit->CreateCodeNode(); + IRHeader.first->Blocks = emit->WrapNode(Block); + emit->SetCurrentCodeBlock(Block); + + const uint8_t GPRSize = GetGPRSize(); + + if (GPRSize == 8) { + emit->_StoreRegister(emit->_Constant(Entrypoint), X86State::REG_R11, IR::GPRClass, GPRSize); + } else { + emit->_StoreContext(GPRSize, IR::FPRClass, emit->_VCastFromGPR(8, 8, emit->_Constant(Entrypoint)), offsetof(Core::CPUState, mm[0][0])); + } + emit->_ExitFunction(emit->_Constant(GuestThunkEntrypoint)); + }, + ThunkHandler, (void*)GuestThunkEntrypoint); + + if (Result.has_value()) { + if (Result->Creator != ThunkHandler) { + ERROR_AND_DIE_FMT("Input address for AddThunkTrampoline is already linked by another module"); + } + if (Result->Data != (void*)GuestThunkEntrypoint) { + // NOTE: This may happen in Vulkan thunks if the Vulkan driver resolves two different symbols + // to the same function (e.g. vkGetPhysicalDeviceFeatures2/vkGetPhysicalDeviceFeatures2KHR) + LogMan::Msg::EFmt("Input address for AddThunkTrampoline is already linked elsewhere"); + } } } @@ -1018,12 +1055,6 @@ void ContextImpl::UnloadAOTIRCacheEntry(IR::AOTIRCacheEntry* Entry) { IRCaptureCache.UnloadAOTIRCacheEntry(Entry); } -void ContextImpl::AppendThunkDefinitions(std::span Definitions) { - if (ThunkHandler) { - ThunkHandler->AppendThunkDefinitions(Definitions); - } -} - void ContextImpl::ConfigureAOTGen(FEXCore::Core::InternalThreadState* Thread, fextl::set* ExternalBranches, uint64_t SectionMaxAddress) { Thread->FrontendDecoder->SetExternalBranches(ExternalBranches); Thread->FrontendDecoder->SetSectionMaxAddress(SectionMaxAddress); diff --git a/FEXCore/Source/Interface/Core/JIT/Arm64/Arm64Relocations.cpp b/FEXCore/Source/Interface/Core/JIT/Arm64/Arm64Relocations.cpp index 67f7fc4dc5..c643ca733b 100644 --- a/FEXCore/Source/Interface/Core/JIT/Arm64/Arm64Relocations.cpp +++ b/FEXCore/Source/Interface/Core/JIT/Arm64/Arm64Relocations.cpp @@ -7,7 +7,8 @@ desc: relocation logic of the arm64 splatter backend */ #include "Interface/Context/Context.h" #include "Interface/Core/JIT/Arm64/JITClass.h" -#include "Interface/HLE/Thunks/Thunks.h" + +#include namespace FEXCore::CPU { diff --git a/FEXCore/Source/Interface/Core/JIT/Arm64/BranchOps.cpp b/FEXCore/Source/Interface/Core/JIT/Arm64/BranchOps.cpp index 8fc9dd2495..9591f2e126 100644 --- a/FEXCore/Source/Interface/Core/JIT/Arm64/BranchOps.cpp +++ b/FEXCore/Source/Interface/Core/JIT/Arm64/BranchOps.cpp @@ -11,11 +11,11 @@ tags: backend|arm64 #include "Interface/Core/JIT/Arm64/JITClass.h" +#include #include #include #include #include -#include namespace FEXCore::CPU { #define DEF_OP(x) void Arm64JITCore::Op_##x(IR::IROp_Header const* IROp, IR::NodeID Node) diff --git a/FEXCore/include/FEXCore/Core/Context.h b/FEXCore/include/FEXCore/Core/Context.h index 387aada428..d05f9f72c9 100644 --- a/FEXCore/include/FEXCore/Core/Context.h +++ b/FEXCore/include/FEXCore/Core/Context.h @@ -20,6 +20,7 @@ namespace FEXCore { class CodeLoader; struct HostFeatures; class ForkableSharedMutex; +class ThunkHandler; } // namespace FEXCore namespace FEXCore::Core { @@ -175,6 +176,7 @@ class Context { #endif FEX_DEFAULT_VISIBILITY virtual void SetSignalDelegator(FEXCore::SignalDelegator* SignalDelegation) = 0; FEX_DEFAULT_VISIBILITY virtual void SetSyscallHandler(FEXCore::HLE::SyscallHandler* Handler) = 0; + FEX_DEFAULT_VISIBILITY virtual void SetThunkHandler(FEXCore::ThunkHandler* Handler) = 0; FEX_DEFAULT_VISIBILITY virtual FEXCore::CPUID::FunctionResults RunCPUIDFunction(uint32_t Function, uint32_t Leaf) = 0; FEX_DEFAULT_VISIBILITY virtual FEXCore::CPUID::XCRResults RunXCRFunction(uint32_t Function) = 0; @@ -211,14 +213,6 @@ class Context { */ FEX_DEFAULT_VISIBILITY virtual bool IsAddressInCodeBuffer(FEXCore::Core::InternalThreadState* Thread, uintptr_t Address) const = 0; - /** - * @brief Allows the frontend to register its own thunk handlers independent of what is controlled in the backend. - * - * @param CTX A valid non-null context instance. - * @param Definitions A vector of thunk definitions that the frontend controls - */ - FEX_DEFAULT_VISIBILITY virtual void AppendThunkDefinitions(std::span Definitions) = 0; - /** * @brief Informs the context if hardware TSO is supported. * Once hardware TSO is enabled, then TSO emulation through atomics is disabled and relies on the hardware. @@ -235,6 +229,14 @@ class Context { */ FEX_DEFAULT_VISIBILITY virtual void EnableExitOnHLT() = 0; + /** + * @brief Adds a new Thunk trampoline handler + * + * @param Entrypoint The guest PC that the custom thunk trampoline IR handler will be installed at. + * @param GuestThunkEntrypoint The thunk entrypoint that the IR handler will redirect to. + */ + FEX_DEFAULT_VISIBILITY virtual void AddThunkTrampolineIRHandler(uintptr_t Entrypoint, uintptr_t GuestThunkEntrypoint) = 0; + private: }; diff --git a/FEXCore/Source/Interface/HLE/Thunks/Thunks.h b/FEXCore/include/FEXCore/Core/Thunks.h similarity index 64% rename from FEXCore/Source/Interface/HLE/Thunks/Thunks.h rename to FEXCore/include/FEXCore/Core/Thunks.h index 6977924f53..df97e64271 100644 --- a/FEXCore/Source/Interface/HLE/Thunks/Thunks.h +++ b/FEXCore/include/FEXCore/Core/Thunks.h @@ -7,13 +7,12 @@ tags: glue|thunks #pragma once -#include "Interface/IR/IR.h" - #include #include +#include namespace FEXCore::Context { -class ContextImpl; +class Context; } namespace FEXCore::Core { @@ -30,11 +29,6 @@ typedef void ThunkedFunction(void* ArgsRv); class ThunkHandler { public: virtual ThunkedFunction* LookupThunk(const IR::SHA256Sum& sha256) = 0; - virtual void RegisterTLSState(FEXCore::Core::InternalThreadState* Thread) = 0; virtual ~ThunkHandler() {} - - static fextl::unique_ptr Create(); - - virtual void AppendThunkDefinitions(std::span Definitions) = 0; }; }; // namespace FEXCore diff --git a/Source/Tools/FEXLoader/FEXLoader.cpp b/Source/Tools/FEXLoader/FEXLoader.cpp index 2c590aa1c4..ea5188c85c 100644 --- a/Source/Tools/FEXLoader/FEXLoader.cpp +++ b/Source/Tools/FEXLoader/FEXLoader.cpp @@ -21,6 +21,7 @@ desc: Glues the ELF loader, FEXCore and LinuxSyscalls to launch an elf under fex #include "LinuxSyscalls/x64/Syscalls.h" #include "LinuxSyscalls/SignalDelegator.h" #include "Linux/Utils/ELFContainer.h" +#include "Thunks.h" #include #include @@ -510,9 +511,11 @@ int main(int argc, char** argv, char** const envp) { FEX::TSO::SetupTSOEmulation(CTX.get()); auto SignalDelegation = FEX::HLE::CreateSignalDelegator(CTX.get(), Program.ProgramName, SupportsAVX); + auto ThunkHandler = FEX::HLE::CreateThunkHandler(); - auto SyscallHandler = Loader.Is64BitMode() ? FEX::HLE::x64::CreateHandler(CTX.get(), SignalDelegation.get()) : - FEX::HLE::x32::CreateHandler(CTX.get(), SignalDelegation.get(), std::move(Allocator)); + auto SyscallHandler = Loader.Is64BitMode() ? + FEX::HLE::x64::CreateHandler(CTX.get(), SignalDelegation.get(), ThunkHandler.get()) : + FEX::HLE::x32::CreateHandler(CTX.get(), SignalDelegation.get(), ThunkHandler.get(), std::move(Allocator)); // Load VDSO in to memory prior to mapping our ELFs. auto VDSOMapping = FEX::VDSO::LoadVDSOThunks(Loader.Is64BitMode(), SyscallHandler.get()); @@ -545,6 +548,7 @@ int main(int argc, char** argv, char** const envp) { CTX->SetSignalDelegator(SignalDelegation.get()); CTX->SetSyscallHandler(SyscallHandler.get()); + CTX->SetThunkHandler(ThunkHandler.get()); FEX_CONFIG_OPT(GdbServer, GDBSERVER); fextl::unique_ptr DebugServer; @@ -559,9 +563,10 @@ int main(int argc, char** argv, char** const envp) { auto ParentThread = SyscallHandler->TM.CreateThread(Loader.DefaultRIP(), Loader.GetStackPointer()); SyscallHandler->TM.TrackThread(ParentThread); SignalDelegation->RegisterTLSState(ParentThread); + ThunkHandler->RegisterTLSState(ParentThread); // Pass in our VDSO thunks - CTX->AppendThunkDefinitions(FEX::VDSO::GetVDSOThunkDefinitions()); + ThunkHandler->AppendThunkDefinitions(FEX::VDSO::GetVDSOThunkDefinitions()); SignalDelegation->SetVDSOSigReturn(); SyscallHandler->DeserializeSeccompFD(ParentThread, FEXSeccompFD); diff --git a/Source/Tools/LinuxEmulation/CMakeLists.txt b/Source/Tools/LinuxEmulation/CMakeLists.txt index 99005c166f..bdcade84cb 100644 --- a/Source/Tools/LinuxEmulation/CMakeLists.txt +++ b/Source/Tools/LinuxEmulation/CMakeLists.txt @@ -2,6 +2,7 @@ add_compile_options(-fno-operator-names) set (SRCS VDSO_Emulation.cpp + Thunks.cpp LinuxSyscalls/GdbServer.cpp LinuxSyscalls/EmulatedFiles/EmulatedFiles.cpp LinuxSyscalls/FaultSafeUserMemAccess.cpp diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp index 92a80ee32d..9b2e280a75 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp @@ -21,6 +21,7 @@ desc: Glue logic, brk allocations #include "LinuxSyscalls/x64/Syscalls.h" #include "LinuxSyscalls/x32/Types.h" #include "LinuxSyscalls/x64/Types.h" +#include "Thunks.h" #include #include @@ -740,12 +741,13 @@ void SyscallHandler::DefaultProgramBreak(uint64_t Base, uint64_t Size) { DataSpaceStartingSize = Size; } -SyscallHandler::SyscallHandler(FEXCore::Context::Context* _CTX, FEX::HLE::SignalDelegator* _SignalDelegation) +SyscallHandler::SyscallHandler(FEXCore::Context::Context* _CTX, FEX::HLE::SignalDelegator* _SignalDelegation, FEX::HLE::ThunkHandler* ThunkHandler) : TM {_CTX, _SignalDelegation} , SeccompEmulator {this, _SignalDelegation} , FM {_CTX} , CTX {_CTX} - , SignalDelegation {_SignalDelegation} { + , SignalDelegation {_SignalDelegation} + , ThunkHandler {ThunkHandler} { FEX::HLE::_SyscallHandler = this; HostKernelVersion = CalculateHostKernelVersion(); GuestKernelVersion = CalculateGuestKernelVersion(); @@ -872,6 +874,15 @@ void SyscallHandler::UnlockAfterFork(FEXCore::Core::InternalThreadState* LiveThr TM.UnlockAfterFork(LiveThread, Child); } +void SyscallHandler::RegisterTLSState(FEX::HLE::ThreadStateObject* Thread) { + SignalDelegation->RegisterTLSState(Thread); + ThunkHandler->RegisterTLSState(Thread); +} + +void SyscallHandler::UninstallTLSState(FEX::HLE::ThreadStateObject* Thread) { + SignalDelegation->UninstallTLSState(Thread); +} + static bool isHEX(char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); } diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h index d864f37cd1..5417a3c6ec 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h @@ -14,6 +14,7 @@ desc: Glue logic, STRACE magic #include "LinuxSyscalls/Seccomp/SeccompEmulator.h" #include +#include #include #include #include @@ -66,6 +67,8 @@ namespace FEX::HLE { class SyscallHandler; class SignalDelegator; +class ThunkHandler; + void RegisterEpoll(FEX::HLE::SyscallHandler* Handler); void RegisterFD(FEX::HLE::SyscallHandler* Handler); void RegisterFS(FEX::HLE::SyscallHandler* Handler); @@ -189,6 +192,10 @@ class SyscallHandler : public FEXCore::HLE::SyscallHandler, FEXCore::HLE::Source return SignalDelegation; } + FEX::HLE::ThunkHandler* GetThunkHandler() { + return ThunkHandler; + } + FEX_CONFIG_OPT(IsInterpreter, IS_INTERPRETER); FEX_CONFIG_OPT(IsInterpreterInstalled, INTERPRETER_INSTALLED); FEX_CONFIG_OPT(Filename, APP_FILENAME); @@ -254,6 +261,9 @@ class SyscallHandler : public FEXCore::HLE::SyscallHandler, FEXCore::HLE::Source void LockBeforeFork(FEXCore::Core::InternalThreadState* Thread); void UnlockAfterFork(FEXCore::Core::InternalThreadState* LiveThread, bool Child); + void RegisterTLSState(FEX::HLE::ThreadStateObject* Thread); + void UninstallTLSState(FEX::HLE::ThreadStateObject* Thread); + SourcecodeResolver* GetSourcecodeResolver() override { return this; } @@ -272,7 +282,7 @@ class SyscallHandler : public FEXCore::HLE::SyscallHandler, FEXCore::HLE::Source constexpr static uint64_t TASK_MAX_64BIT = (1ULL << 48); protected: - SyscallHandler(FEXCore::Context::Context* _CTX, FEX::HLE::SignalDelegator* _SignalDelegation); + SyscallHandler(FEXCore::Context::Context* _CTX, FEX::HLE::SignalDelegator* _SignalDelegation, FEX::HLE::ThunkHandler* ThunkHandler); fextl::vector Definitions {std::max(FEX::HLE::x64::SYSCALL_x64_MAX, FEX::HLE::x32::SYSCALL_x86_MAX), { @@ -296,6 +306,7 @@ class SyscallHandler : public FEXCore::HLE::SyscallHandler, FEXCore::HLE::Source private: FEX::HLE::SignalDelegator* SignalDelegation; + FEX::HLE::ThunkHandler* ThunkHandler; std::mutex FutexMutex; std::mutex SyscallMutex; diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.cpp index 0b8fe142b5..0a1178019b 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.cpp @@ -57,9 +57,9 @@ static void* ThreadHandler(void* Data) { Thread->ThreadInfo.PID = ::getpid(); Thread->ThreadInfo.TID = FHU::Syscalls::gettid(); - FEX::HLE::_SyscallHandler->GetSignalDelegator()->RegisterTLSState(Thread); + FEX::HLE::_SyscallHandler->RegisterTLSState(Thread); CTX->ExecutionThread(Thread->Thread); - FEX::HLE::_SyscallHandler->GetSignalDelegator()->UninstallTLSState(Thread); + FEX::HLE::_SyscallHandler->UninstallTLSState(Thread); FEX::HLE::_SyscallHandler->TM.DestroyThread(Thread); return nullptr; } @@ -216,13 +216,13 @@ uint64_t HandleNewClone(FEX::HLE::ThreadStateObject* Thread, FEXCore::Context::C FEX::HLE::_SyscallHandler->TM.TrackThread(Thread); } - FEX::HLE::_SyscallHandler->GetSignalDelegator()->RegisterTLSState(Thread); + FEX::HLE::_SyscallHandler->RegisterTLSState(Thread); // Start exuting the thread directly // Our host clone starts in a new stack space, so it can't return back to the JIT space CTX->ExecutionThread(Thread->Thread); - FEX::HLE::_SyscallHandler->GetSignalDelegator()->UninstallTLSState(Thread); + FEX::HLE::_SyscallHandler->UninstallTLSState(Thread); // The rest of the context remains as is and the thread will continue executing return Thread->Thread->StatusCode; diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Syscalls.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Syscalls.cpp index a25382a18d..18275ea7f0 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Syscalls.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Syscalls.cpp @@ -43,8 +43,8 @@ void RegisterTimer(FEX::HLE::SyscallHandler* Handler); void RegisterPassthrough(FEX::HLE::SyscallHandler* Handler); x32SyscallHandler::x32SyscallHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation, - fextl::unique_ptr Allocator) - : SyscallHandler {ctx, _SignalDelegation} + FEX::HLE::ThunkHandler* ThunkHandler, fextl::unique_ptr Allocator) + : SyscallHandler {ctx, _SignalDelegation, ThunkHandler} , AllocHandler {std::move(Allocator)} { OSABI = FEXCore::HLE::SyscallOSABI::OS_LINUX32; RegisterSyscallHandlers(); @@ -91,9 +91,9 @@ void x32SyscallHandler::RegisterSyscallHandlers() { #endif } -fextl::unique_ptr -CreateHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation, fextl::unique_ptr Allocator) { - return fextl::make_unique(ctx, _SignalDelegation, std::move(Allocator)); +fextl::unique_ptr CreateHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation, + FEX::HLE::ThunkHandler* ThunkHandler, fextl::unique_ptr Allocator) { + return fextl::make_unique(ctx, _SignalDelegation, ThunkHandler, std::move(Allocator)); } } // namespace FEX::HLE::x32 diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Syscalls.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Syscalls.h index b41b322073..0930f602af 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Syscalls.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Syscalls.h @@ -28,13 +28,15 @@ namespace Core { namespace FEX::HLE { class SignalDelegator; -} +class ThunkHandler; +} // namespace FEX::HLE namespace FEX::HLE::x32 { class x32SyscallHandler final : public FEX::HLE::SyscallHandler { public: - x32SyscallHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation, fextl::unique_ptr Allocator); + x32SyscallHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation, FEX::HLE::ThunkHandler* ThunkHandler, + fextl::unique_ptr Allocator); FEX::HLE::MemAllocator* GetAllocator() { return AllocHandler.get(); @@ -65,8 +67,8 @@ class x32SyscallHandler final : public FEX::HLE::SyscallHandler { fextl::unique_ptr AllocHandler {}; }; -fextl::unique_ptr -CreateHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation, fextl::unique_ptr Allocator); +fextl::unique_ptr CreateHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation, + FEX::HLE::ThunkHandler* ThunkHandler, fextl::unique_ptr Allocator); ////// // REGISTER_SYSCALL_IMPL implementation // Given a syscall name + a lambda, and it will generate an strace string, extract number of arguments diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Syscalls.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Syscalls.cpp index e96460d8ca..e0495ed83d 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Syscalls.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Syscalls.cpp @@ -23,8 +23,8 @@ void RegisterTime(FEX::HLE::SyscallHandler* Handler); void RegisterNotImplemented(FEX::HLE::SyscallHandler* Handler); void RegisterPassthrough(FEX::HLE::SyscallHandler* Handler); -x64SyscallHandler::x64SyscallHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation) - : SyscallHandler {ctx, _SignalDelegation} { +x64SyscallHandler::x64SyscallHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation, FEX::HLE::ThunkHandler* ThunkHandler) + : SyscallHandler {ctx, _SignalDelegation, ThunkHandler} { OSABI = FEXCore::HLE::SyscallOSABI::OS_LINUX64; RegisterSyscallHandlers(); @@ -80,7 +80,8 @@ void x64SyscallHandler::RegisterSyscallHandlers() { #endif } -fextl::unique_ptr CreateHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation) { - return fextl::make_unique(ctx, _SignalDelegation); +fextl::unique_ptr +CreateHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation, FEX::HLE::ThunkHandler* ThunkHandler) { + return fextl::make_unique(ctx, _SignalDelegation, ThunkHandler); } } // namespace FEX::HLE::x64 diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Syscalls.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Syscalls.h index 3f88828059..86b5d47186 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Syscalls.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Syscalls.h @@ -23,6 +23,7 @@ tags: LinuxSyscalls|syscalls-x86-64 namespace FEX::HLE { class SignalDelegator; class SyscallHandler; +class ThunkHandler; } // namespace FEX::HLE namespace FEXCore::Core { @@ -32,7 +33,7 @@ struct InternalThreadState; namespace FEX::HLE::x64 { class x64SyscallHandler final : public FEX::HLE::SyscallHandler { public: - x64SyscallHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation); + x64SyscallHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation, FEX::HLE::ThunkHandler* ThunkHandler); void* GuestMmap(FEXCore::Core::InternalThreadState* Thread, void* addr, size_t length, int prot, int flags, int fd, off_t offset) override; int GuestMunmap(FEXCore::Core::InternalThreadState* Thread, void* addr, uint64_t length) override; @@ -60,7 +61,8 @@ class x64SyscallHandler final : public FEX::HLE::SyscallHandler { void RegisterSyscallHandlers(); }; -fextl::unique_ptr CreateHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation); +fextl::unique_ptr +CreateHandler(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* _SignalDelegation, FEX::HLE::ThunkHandler* ThunkHandler); ////// // REGISTER_SYSCALL_IMPL implementation diff --git a/FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp b/Source/Tools/LinuxEmulation/Thunks.cpp similarity index 66% rename from FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp rename to Source/Tools/LinuxEmulation/Thunks.cpp index fc3860cabd..323aad54fe 100644 --- a/FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp +++ b/Source/Tools/LinuxEmulation/Thunks.cpp @@ -6,29 +6,26 @@ tags: glue|thunks $end_info$ */ -#include "Interface/IR/IR.h" -#include "Interface/IR/IREmitter.h" +#include "Thunks.h" +#include "LinuxSyscalls/Syscalls.h" +#include "LinuxSyscalls/ThreadManager.h" #include #include +#include +#include #include #include #include #include #include #include -#include "Thunks.h" #include -#ifndef _WIN32 #include -#endif -#include -#include "FEXCore/Core/X86Enums.h" #include #include -#include #include #include #include @@ -43,7 +40,6 @@ FEX_DEFAULT_VISIBILITY JEMALLOC_NOTHROW extern int glibc_je_is_known_allocation( } #endif -#ifndef _WIN32 static __attribute__((aligned(16), naked, section("HostToGuestTrampolineTemplate"))) void HostToGuestTrampolineTemplate() { #if defined(_M_X86_64) asm("lea 0f(%rip), %r11 \n" @@ -71,21 +67,10 @@ static __attribute__((aligned(16), naked, section("HostToGuestTrampolineTemplate extern char __start_HostToGuestTrampolineTemplate[]; extern char __stop_HostToGuestTrampolineTemplate[]; -#endif -namespace FEXCore { -#ifndef _WIN32 -struct LoadlibArgs { - const char* Name; -}; - -static thread_local FEXCore::Core::InternalThreadState* Thread = nullptr; +namespace FEX::HLE { - -struct ExportEntry { - uint8_t* sha256; - ThunkedFunction* Fn; -}; +static thread_local FEX::HLE::ThreadStateObject* ThreadObject {}; struct TrampolineInstanceInfo { void* HostPacker; @@ -119,41 +104,17 @@ struct GuestcallInfoHash { } }; -// Bits in a SHA256 sum are already randomly distributed, so truncation yields a suitable hash function -struct TruncatingSHA256Hash { - size_t operator()(const FEXCore::IR::SHA256Sum& SHA256Sum) const noexcept { - return (const size_t&)SHA256Sum; - } -}; +namespace ThunkFunctions { + void LoadLib(void* ArgsV); + void IsLibLoaded(void* ArgsRV); + void IsHostHeapAllocation(void* ArgsRV); + void LinkAddressToGuestFunction(void* argsv); + void AllocateHostTrampolineForGuestFunction(void* ArgsRV); +} // namespace ThunkFunctions -HostToGuestTrampolinePtr* MakeHostTrampolineForGuestFunction(void* HostPacker, uintptr_t GuestTarget, uintptr_t GuestUnpacker); - -struct ThunkHandler_impl final : public ThunkHandler { +struct ThunkHandler_impl final : public FEX::HLE::ThunkHandler { std::shared_mutex ThunksMutex; - fextl::unordered_map Thunks = { - {// sha256(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}, - &LoadLib}, - {// sha256(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}, - &IsLibLoaded}, - {// sha256(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}, - &IsHostHeapAllocation}, - {// sha256(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}, - &LinkAddressToGuestFunction}, - {// sha256(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}, - &AllocateHostTrampolineForGuestFunction}, - }; - // Can't be a string_view. We need to keep a copy of the library name in-case string_view pointer goes away. // Ideally we track when a library has been unloaded and remove it from this set before the memory backing goes away. fextl::set Libs; @@ -163,224 +124,138 @@ struct ThunkHandler_impl final : public ThunkHandler { uint8_t* HostTrampolineInstanceDataPtr; size_t HostTrampolineInstanceDataAvailable = 0; - /* Set arg0/1 to arg regs, use CTX::HandleCallback to handle the callback */ static void CallCallback(void* callback, void* arg0, void* arg1) { - if (!Thread) { + if (!ThreadObject) { ERROR_AND_DIE_FMT("Thunked library attempted to invoke guest callback asynchronously"); } - auto CTX = static_cast(Thread->CTX); - if (CTX->Config.Is64BitMode) { - Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RDI] = (uintptr_t)arg0; - Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSI] = (uintptr_t)arg1; + auto CTX = static_cast(ThreadObject->Thread->CTX); + auto ThunkHandler = reinterpret_cast(FEX::HLE::_SyscallHandler->GetThunkHandler()); + + if (ThunkHandler->Is64BitMode()) { + ThreadObject->Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RDI] = (uintptr_t)arg0; + ThreadObject->Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSI] = (uintptr_t)arg1; } else { if ((reinterpret_cast(arg1) >> 32) != 0) { ERROR_AND_DIE_FMT("Tried to call guest function with arguments packed to a 64-bit address"); } - Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RCX] = (uintptr_t)arg0; - Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RDX] = (uintptr_t)arg1; + ThreadObject->Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RCX] = (uintptr_t)arg0; + ThreadObject->Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RDX] = (uintptr_t)arg1; } - Thread->CTX->HandleCallback(Thread, (uintptr_t)callback); + CTX->HandleCallback(ThreadObject->Thread, (uintptr_t)callback); } - /** - * Instructs the Core to redirect calls to functions at the given - * address to another function. The original callee address is passed - * to the target function through an implicit argument stored in r11. - * - * For 32-bit the implicit argument is stored in the lower 32-bits of mm0. - * - * The primary use case of this is ensuring that host function pointers - * returned from thunked APIs can safely be called by the guest. - */ - static void LinkAddressToGuestFunction(void* argsv) { - struct args_t { - uintptr_t original_callee; - uintptr_t target_addr; // Guest function to call when branching to original_callee - }; + FEXCore::ThunkedFunction* LookupThunk(const FEXCore::IR::SHA256Sum& sha256) override { - auto args = reinterpret_cast(argsv); - auto CTX = static_cast(Thread->CTX); - - LOGMAN_THROW_AA_FMT(args->original_callee, "Tried to link null pointer address to guest function"); - LOGMAN_THROW_AA_FMT(args->target_addr, "Tried to link address to null pointer guest function"); - if (!CTX->Config.Is64BitMode) { - LOGMAN_THROW_AA_FMT((args->original_callee >> 32) == 0, "Tried to link 64-bit address in 32-bit mode"); - LOGMAN_THROW_AA_FMT((args->target_addr >> 32) == 0, "Tried to link 64-bit address in 32-bit mode"); - } - - LogMan::Msg::DFmt("Thunks: Adding guest trampoline from address {:#x} to guest function {:#x}", args->original_callee, args->target_addr); - - auto Result = CTX->AddCustomIREntrypoint( - args->original_callee, - [CTX, GuestThunkEntrypoint = args->target_addr](uintptr_t Entrypoint, FEXCore::IR::IREmitter* emit) { - auto IRHeader = emit->_IRHeader(emit->Invalid(), Entrypoint, 0, 0); - auto Block = emit->CreateCodeNode(); - IRHeader.first->Blocks = emit->WrapNode(Block); - emit->SetCurrentCodeBlock(Block); - - const uint8_t GPRSize = CTX->GetGPRSize(); + std::shared_lock lk(ThunksMutex); - if (GPRSize == 8) { - emit->_StoreRegister(emit->_Constant(Entrypoint), X86State::REG_R11, IR::GPRClass, GPRSize); - } else { - emit->_StoreContext(GPRSize, IR::FPRClass, emit->_VCastFromGPR(8, 8, emit->_Constant(Entrypoint)), offsetof(Core::CPUState, mm[0][0])); - } - emit->_ExitFunction(emit->_Constant(GuestThunkEntrypoint)); - }, - CTX->ThunkHandler.get(), (void*)args->target_addr); + auto it = Thunks.find(sha256); - if (!Result) { - if (Result.Creator != CTX->ThunkHandler.get()) { - ERROR_AND_DIE_FMT("Input address for LinkAddressToGuestFunction is already linked by another module"); - } - if (Result.Data != (void*)args->target_addr) { - // NOTE: This may happen in Vulkan thunks if the Vulkan driver resolves two different symbols - // to the same function (e.g. vkGetPhysicalDeviceFeatures2/vkGetPhysicalDeviceFeatures2KHR) - LogMan::Msg::EFmt("Input address for LinkAddressToGuestFunction is already linked elsewhere"); - } + if (it != Thunks.end()) { + return it->second; + } else { + return nullptr; } } - /** - * Guest-side helper to initiate creation of a host trampoline for - * calling guest functions. This must be followed by a host-side call - * to FinalizeHostTrampolineForGuestFunction to make the trampoline - * usable. - * - * This two-step initialization is equivalent to a host-side call to - * MakeHostTrampolineForGuestFunction. The split is needed if the - * host doesn't have all information needed to create the trampoline - * on its own. - */ - static void AllocateHostTrampolineForGuestFunction(void* ArgsRV) { - struct ArgsRV_t { - uintptr_t GuestUnpacker; - uintptr_t GuestTarget; - uintptr_t rv; // Pointer to host trampoline + TrampolineInstanceInfo - }* args = reinterpret_cast(ArgsRV); - - args->rv = (uintptr_t)MakeHostTrampolineForGuestFunction(nullptr, args->GuestTarget, args->GuestUnpacker); + void RegisterTLSState(FEX::HLE::ThreadStateObject* _ThreadObject) override { + ThreadObject = _ThreadObject; } - /** - * Checks if the given pointer is allocated on the host heap. - * - * This is useful for thunking APIs that need to work with both guest - * and host heap pointers. - */ - static void IsHostHeapAllocation(void* ArgsRV) { -#ifdef ENABLE_JEMALLOC_GLIBC - struct ArgsRV_t { - void* ptr; - bool rv; - }* args = reinterpret_cast(ArgsRV); - - args->rv = glibc_je_is_known_allocation(args->ptr); -#else - // Thunks usage without jemalloc isn't supported - ERROR_AND_DIE_FMT("Unsupported: Thunks querying for host heap allocation information"); -#endif - } - - static void LoadLib(void* ArgsV) { - auto CTX = static_cast(Thread->CTX); - - auto Args = reinterpret_cast(ArgsV); - - std::string_view Name = Args->Name; - - auto SOName = (CTX->Config.Is64BitMode() ? CTX->Config.ThunkHostLibsPath() : CTX->Config.ThunkHostLibsPath32()) + "/" + Name.data() + "-host.so"; - - LogMan::Msg::DFmt("LoadLib: {} -> {}", Name, SOName); - - auto Handle = dlopen(SOName.c_str(), RTLD_LOCAL | RTLD_NOW); - if (!Handle) { - ERROR_AND_DIE_FMT("LoadLib: Failed to dlopen thunk library {}: {}", SOName, dlerror()); + void AppendThunkDefinitions(std::span Definitions) override { + for (auto& Definition : Definitions) { + Thunks.emplace(Definition.Sum, Definition.ThunkFunction); } + } - // Library names often include dashes, which may not be used in C++ identifiers. - // They are replaced with underscores hence. - auto InitSym = "fexthunks_exports_" + fextl::string {Name}; - std::replace(InitSym.begin(), InitSym.end(), '-', '_'); - - ExportEntry* (*InitFN)(); - (void*&)InitFN = dlsym(Handle, InitSym.c_str()); - if (!InitFN) { - ERROR_AND_DIE_FMT("LoadLib: Failed to find export {}", InitSym); - } + void LoadLib(std::string_view Name); - auto Exports = InitFN(); - if (!Exports) { - ERROR_AND_DIE_FMT("LoadLib: Failed to initialize thunk library {}. " - "Check if the corresponding host library is installed " - "or disable thunking of this library.", - Name); +private: + // Bits in a SHA256 sum are already randomly distributed, so truncation yields a suitable hash function + struct TruncatingSHA256Hash { + size_t operator()(const FEXCore::IR::SHA256Sum& SHA256Sum) const noexcept { + return (const size_t&)SHA256Sum; } + }; - auto That = reinterpret_cast(CTX->ThunkHandler.get()); + fextl::unordered_map Thunks = { + {// sha256(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}, + &ThunkFunctions::LoadLib}, + {// sha256(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}, + &ThunkFunctions::IsLibLoaded}, + {// sha256(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}, + &ThunkFunctions::IsHostHeapAllocation}, + {// sha256(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}, + &ThunkFunctions::LinkAddressToGuestFunction}, + {// sha256(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}, + &ThunkFunctions::AllocateHostTrampolineForGuestFunction}, + }; - { - std::lock_guard lk(That->ThunksMutex); + FEX_CONFIG_OPT(Is64BitMode, IS64BIT_MODE); + FEX_CONFIG_OPT(ThunkHostLibsPath, THUNKHOSTLIBS); + FEX_CONFIG_OPT(ThunkHostLibsPath32, THUNKHOSTLIBS32); +}; - That->Libs.insert(fextl::string {Name}); +void ThunkHandler_impl::LoadLib(std::string_view Name) { + auto SOName = (Is64BitMode() ? ThunkHostLibsPath() : ThunkHostLibsPath32()) + "/" + Name.data() + "-host.so"; - int i; - for (i = 0; Exports[i].sha256; i++) { - That->Thunks[*reinterpret_cast(Exports[i].sha256)] = Exports[i].Fn; - } + LogMan::Msg::DFmt("LoadLib: {} -> {}", Name, SOName); - LogMan::Msg::DFmt("Loaded {} syms", i); - } + auto Handle = dlopen(SOName.c_str(), RTLD_LOCAL | RTLD_NOW); + if (!Handle) { + ERROR_AND_DIE_FMT("LoadLib: Failed to dlopen thunk library {}: {}", SOName, dlerror()); } - static void IsLibLoaded(void* ArgsRV) { - struct ArgsRV_t { - const char* Name; - bool rv; - }; - - auto& [Name, rv] = *reinterpret_cast(ArgsRV); + // Library names often include dashes, which may not be used in C++ identifiers. + // They are replaced with underscores hence. + auto InitSym = "fexthunks_exports_" + fextl::string {Name}; + std::replace(InitSym.begin(), InitSym.end(), '-', '_'); - auto CTX = static_cast(Thread->CTX); - auto That = reinterpret_cast(CTX->ThunkHandler.get()); + struct ExportEntry { + uint8_t* sha256; + FEXCore::ThunkedFunction* Fn; + }; - { - std::shared_lock lk(That->ThunksMutex); - rv = That->Libs.contains(Name); - } + ExportEntry* (*InitFN)(); + (void*&)InitFN = dlsym(Handle, InitSym.c_str()); + if (!InitFN) { + ERROR_AND_DIE_FMT("LoadLib: Failed to find export {}", InitSym); } - ThunkedFunction* LookupThunk(const IR::SHA256Sum& sha256) override { + auto Exports = InitFN(); + if (!Exports) { + ERROR_AND_DIE_FMT("LoadLib: Failed to initialize thunk library {}. " + "Check if the corresponding host library is installed " + "or disable thunking of this library.", + Name); + } - std::shared_lock lk(ThunksMutex); + { + std::lock_guard lk(ThunksMutex); - auto it = Thunks.find(sha256); + Libs.insert(fextl::string {Name}); - if (it != Thunks.end()) { - return it->second; - } else { - return nullptr; + int i; + for (i = 0; Exports[i].sha256; i++) { + Thunks[*reinterpret_cast(Exports[i].sha256)] = Exports[i].Fn; } - } - void RegisterTLSState(FEXCore::Core::InternalThreadState* _Thread) override { - Thread = _Thread; + LogMan::Msg::DFmt("Loaded {} syms", i); } - - void AppendThunkDefinitions(std::span Definitions) override { - for (auto& Definition : Definitions) { - Thunks.emplace(Definition.Sum, Definition.ThunkFunction); - } - } -}; - -fextl::unique_ptr ThunkHandler::Create() { - return fextl::make_unique(); } /** @@ -410,8 +285,7 @@ FEX_DEFAULT_VISIBILITY HostToGuestTrampolinePtr* MakeHostTrampolineForGuestFunction(void* HostPacker, uintptr_t GuestTarget, uintptr_t GuestUnpacker) { LOGMAN_THROW_AA_FMT(GuestTarget, "Tried to create host-trampoline to null pointer guest function"); - const auto CTX = static_cast(Thread->CTX); - const auto ThunkHandler = reinterpret_cast(CTX->ThunkHandler.get()); + const auto ThunkHandler = reinterpret_cast(FEX::HLE::_SyscallHandler->GetThunkHandler()); const GuestcallInfo gci = {GuestUnpacker, GuestTarget}; @@ -460,7 +334,6 @@ MakeHostTrampolineForGuestFunction(void* HostPacker, uintptr_t GuestTarget, uint } FEX_DEFAULT_VISIBILITY void FinalizeHostTrampolineForGuestFunction(HostToGuestTrampolinePtr* TrampolineAddress, void* HostPacker) { - if (TrampolineAddress == nullptr) { return; } @@ -476,16 +349,106 @@ FEX_DEFAULT_VISIBILITY void FinalizeHostTrampolineForGuestFunction(HostToGuestTr } } +namespace ThunkFunctions { + void LoadLib(void* ArgsV) { + struct LoadlibArgs { + const char* Name; + }; + + auto Args = reinterpret_cast(ArgsV); + auto ThunkHandler = reinterpret_cast(FEX::HLE::_SyscallHandler->GetThunkHandler()); + + ThunkHandler->LoadLib(Args->Name); + } + + void IsLibLoaded(void* ArgsRV) { + struct ArgsRV_t { + const char* Name; + bool rv; + }; + + auto& [Name, rv] = *reinterpret_cast(ArgsRV); + auto ThunkHandler = reinterpret_cast(FEX::HLE::_SyscallHandler->GetThunkHandler()); + + { + std::shared_lock lk(ThunkHandler->ThunksMutex); + rv = ThunkHandler->Libs.contains(Name); + } + } + + /** + * Checks if the given pointer is allocated on the host heap. + * + * This is useful for thunking APIs that need to work with both guest + * and host heap pointers. + */ + void IsHostHeapAllocation(void* ArgsRV) { +#ifdef ENABLE_JEMALLOC_GLIBC + struct ArgsRV_t { + void* ptr; + bool rv; + }* args = reinterpret_cast(ArgsRV); + + args->rv = glibc_je_is_known_allocation(args->ptr); +#else + // Thunks usage without jemalloc isn't supported + ERROR_AND_DIE_FMT("Unsupported: Thunks querying for host heap allocation information"); +#endif + } + + /** + * Instructs the Core to redirect calls to functions at the given + * address to another function. The original callee address is passed + * to the target function through an implicit argument stored in r11. + * + * For 32-bit the implicit argument is stored in the lower 32-bits of mm0. + * + * The primary use case of this is ensuring that host function pointers + * returned from thunked APIs can safely be called by the guest. + */ + void LinkAddressToGuestFunction(void* argsv) { + struct args_t { + uintptr_t original_callee; + uintptr_t target_addr; // Guest function to call when branching to original_callee + }; + + auto args = reinterpret_cast(argsv); + auto CTX = static_cast(ThreadObject->Thread->CTX); + CTX->AddThunkTrampolineIRHandler(args->original_callee, args->target_addr); + } + + /** + * Guest-side helper to initiate creation of a host trampoline for + * calling guest functions. This must be followed by a host-side call + * to FinalizeHostTrampolineForGuestFunction to make the trampoline + * usable. + * + * This two-step initialization is equivalent to a host-side call to + * MakeHostTrampolineForGuestFunction. The split is needed if the + * host doesn't have all information needed to create the trampoline + * on its own. + */ + void AllocateHostTrampolineForGuestFunction(void* ArgsRV) { + struct ArgsRV_t { + uintptr_t GuestUnpacker; + uintptr_t GuestTarget; + uintptr_t rv; // Pointer to host trampoline + TrampolineInstanceInfo + }* args = reinterpret_cast(ArgsRV); + + args->rv = (uintptr_t)MakeHostTrampolineForGuestFunction(nullptr, args->GuestTarget, args->GuestUnpacker); + } +} // namespace ThunkFunctions + FEX_DEFAULT_VISIBILITY void* GetGuestStack() { - if (!Thread) { + if (!ThreadObject) { ERROR_AND_DIE_FMT("Thunked library attempted to query guest stack pointer asynchronously"); } - return (void*)(uintptr_t)((Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSP])); + return (void*)(uintptr_t)((ThreadObject->Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSP])); } FEX_DEFAULT_VISIBILITY void MoveGuestStack(uintptr_t NewAddress) { - if (!Thread) { + if (!ThreadObject) { ERROR_AND_DIE_FMT("Thunked library attempted to query guest stack pointer asynchronously"); } @@ -493,13 +456,10 @@ FEX_DEFAULT_VISIBILITY void MoveGuestStack(uintptr_t NewAddress) { ERROR_AND_DIE_FMT("Tried to set stack pointer for 32-bit guest to a 64-bit address"); } - Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSP] = NewAddress; + ThreadObject->Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSP] = NewAddress; } -#else -fextl::unique_ptr ThunkHandler::Create() { - ERROR_AND_DIE_FMT("Unsupported"); +fextl::unique_ptr CreateThunkHandler() { + return fextl::make_unique(); } -#endif - -} // namespace FEXCore +} // namespace FEX::HLE diff --git a/Source/Tools/LinuxEmulation/Thunks.h b/Source/Tools/LinuxEmulation/Thunks.h new file mode 100644 index 0000000000..163c6e0843 --- /dev/null +++ b/Source/Tools/LinuxEmulation/Thunks.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +#include +#include + +#include + +namespace FEX::HLE { +struct ThreadStateObject; + +class ThunkHandler : public FEXCore::ThunkHandler { +public: + virtual void RegisterTLSState(FEX::HLE::ThreadStateObject* ThreadObject) = 0; + /** + * @brief Allows the frontend to register its own thunk handlers independent of what is controlled in the backend. + * + * @param CTX A valid non-null context instance. + * @param Definitions A vector of thunk definitions that the frontend controls + */ + virtual void AppendThunkDefinitions(std::span Definitions) = 0; +}; +fextl::unique_ptr CreateThunkHandler(); +} // namespace FEX::HLE diff --git a/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp b/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp index f8a17dcb8e..d922be9e38 100644 --- a/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp +++ b/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp @@ -300,8 +300,8 @@ int main(int argc, char** argv, char** const envp) { if (!IsHostRunner) { #ifndef _WIN32 - auto SyscallHandler = Loader.Is64BitMode() ? FEX::HLE::x64::CreateHandler(CTX.get(), SignalDelegation.get()) : - FEX::HLE::x32::CreateHandler(CTX.get(), SignalDelegation.get(), std::move(Allocator)); + auto SyscallHandler = Loader.Is64BitMode() ? FEX::HLE::x64::CreateHandler(CTX.get(), SignalDelegation.get(), nullptr) : + FEX::HLE::x32::CreateHandler(CTX.get(), SignalDelegation.get(), nullptr, std::move(Allocator)); #else auto SyscallHandler = FEX::WindowsHandlers::CreateSyscallHandler(); diff --git a/ThunkLibs/include/common/Host.h b/ThunkLibs/include/common/Host.h index 4bd071cd18..2d568490d7 100644 --- a/ThunkLibs/include/common/Host.h +++ b/ThunkLibs/include/common/Host.h @@ -15,12 +15,12 @@ category: thunklibs ~ These are generated + glue logic 1:1 thunks unless noted o #include "PackedArguments.h" -// Import FEXCore functions for use in host thunk libraries. +// Import FEX::HLE functions for use in host thunk libraries. // // Note these are statically linked into the FEX executable. The linker hence // doesn't know about them when linking thunk libraries. This issue is avoided // by declaring the functions as weak symbols. -namespace FEXCore { +namespace FEX::HLE { struct HostToGuestTrampolinePtr; __attribute__((weak)) HostToGuestTrampolinePtr* MakeHostTrampolineForGuestFunction(void* HostPacker, uintptr_t GuestTarget, uintptr_t GuestUnpacker); @@ -30,7 +30,7 @@ __attribute__((weak)) HostToGuestTrampolinePtr* FinalizeHostTrampolineForGuestFu __attribute__((weak)) void* GetGuestStack(); __attribute__((weak)) void MoveGuestStack(uintptr_t NewAddress); -} // namespace FEXCore +} // namespace FEX::HLE template struct function_traits; @@ -504,19 +504,19 @@ auto Projection(guest_layout& data) { * restored. */ class GuestStackBumpAllocator final { - uintptr_t Top = reinterpret_cast(FEXCore::GetGuestStack()); + uintptr_t Top = reinterpret_cast(FEX::HLE::GetGuestStack()); uintptr_t Next = Top; public: ~GuestStackBumpAllocator() { - FEXCore::MoveGuestStack(Top); + FEX::HLE::MoveGuestStack(Top); } template T* New(Args&&... args) { Next -= sizeof(T); Next &= ~uintptr_t {alignof(T) - 1}; - FEXCore::MoveGuestStack(Next); + FEX::HLE::MoveGuestStack(Next); return new (reinterpret_cast(Next)) T {std::forward(args)...}; } }; @@ -625,19 +625,19 @@ struct GuestWrapperForHostFunction { template void MakeHostTrampolineForGuestFunctionAt(uintptr_t GuestTarget, uintptr_t GuestUnpacker, FuncType** Func) { - *Func = (FuncType*)FEXCore::MakeHostTrampolineForGuestFunction((void*)&CallbackUnpack::CallGuestPtr, GuestTarget, GuestUnpacker); + *Func = (FuncType*)FEX::HLE::MakeHostTrampolineForGuestFunction((void*)&CallbackUnpack::CallGuestPtr, GuestTarget, GuestUnpacker); } template void FinalizeHostTrampolineForGuestFunction(F* PreallocatedTrampolineForGuestFunction) { - FEXCore::FinalizeHostTrampolineForGuestFunction((FEXCore::HostToGuestTrampolinePtr*)PreallocatedTrampolineForGuestFunction, - (void*)&CallbackUnpack::CallGuestPtr); + FEX::HLE::FinalizeHostTrampolineForGuestFunction((FEX::HLE::HostToGuestTrampolinePtr*)PreallocatedTrampolineForGuestFunction, + (void*)&CallbackUnpack::CallGuestPtr); } template void FinalizeHostTrampolineForGuestFunction(guest_layout PreallocatedTrampolineForGuestFunction) { - FEXCore::FinalizeHostTrampolineForGuestFunction((FEXCore::HostToGuestTrampolinePtr*)PreallocatedTrampolineForGuestFunction.data, - (void*)&CallbackUnpack::CallGuestPtr); + FEX::HLE::FinalizeHostTrampolineForGuestFunction((FEX::HLE::HostToGuestTrampolinePtr*)PreallocatedTrampolineForGuestFunction.data, + (void*)&CallbackUnpack::CallGuestPtr); } // In the case of the thunk host_loader being the default, FEX need to use dlsym with RTLD_DEFAULT. diff --git a/ThunkLibs/libwayland-client/Host.cpp b/ThunkLibs/libwayland-client/Host.cpp index aa3ba829c9..b07d9f6398 100644 --- a/ThunkLibs/libwayland-client/Host.cpp +++ b/ThunkLibs/libwayland-client/Host.cpp @@ -335,8 +335,8 @@ fexfn_impl_libwayland_client_wl_proxy_add_listener(struct wl_proxy* proxy, guest WaylandFinalizeHostTrampolineForGuestListener<>(callback); } else if (signature == "a") { // E.g. xdg_toplevel::wm_capabilities - FEXCore::FinalizeHostTrampolineForGuestFunction((FEXCore::HostToGuestTrampolinePtr*)callback, - (void*)CallGuestPtrWithWaylandArray); + FEX::HLE::FinalizeHostTrampolineForGuestFunction((FEX::HLE::HostToGuestTrampolinePtr*)callback, + (void*)CallGuestPtrWithWaylandArray); } else if (signature == "hu") { // E.g. zwp_linux_dmabuf_feedback_v1::format_table WaylandFinalizeHostTrampolineForGuestListener<'h', 'u'>(callback); @@ -354,8 +354,8 @@ fexfn_impl_libwayland_client_wl_proxy_add_listener(struct wl_proxy* proxy, guest WaylandFinalizeHostTrampolineForGuestListener<'i', 'i'>(callback); } else if (signature == "iia") { // E.g. xdg_toplevel::configure - FEXCore::FinalizeHostTrampolineForGuestFunction((FEXCore::HostToGuestTrampolinePtr*)callback, - (void*)CallGuestPtrWithWaylandArray); + FEX::HLE::FinalizeHostTrampolineForGuestFunction((FEX::HLE::HostToGuestTrampolinePtr*)callback, + (void*)CallGuestPtrWithWaylandArray); } else if (signature == "iiiiissi") { // E.g. wl_output_listener::geometry WaylandFinalizeHostTrampolineForGuestListener<'i', 'i', 'i', 'i', 'i', 's', 's', 'i'>(callback); @@ -388,8 +388,8 @@ fexfn_impl_libwayland_client_wl_proxy_add_listener(struct wl_proxy* proxy, guest WaylandFinalizeHostTrampolineForGuestListener<'u', 'o'>(callback); } else if (signature == "uoa") { // E.g. wl_keyboard_listener::enter - FEXCore::FinalizeHostTrampolineForGuestFunction((FEXCore::HostToGuestTrampolinePtr*)callback, - (void*)CallGuestPtrWithWaylandArray); + FEX::HLE::FinalizeHostTrampolineForGuestFunction((FEX::HLE::HostToGuestTrampolinePtr*)callback, + (void*)CallGuestPtrWithWaylandArray); } else if (signature == "uoff") { // E.g. wl_pointer_listener::enter WaylandFinalizeHostTrampolineForGuestListener<'u', 'o', 'f', 'f'>(callback);