From d032aec7f1c4f69c62c6a3dbce1362a6591feb91 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Sat, 30 Mar 2024 06:24:49 -0700 Subject: [PATCH] WIP: Moves Thread state tracking to frontend The FEXCore ThreadManagement object has been a byproduct of leaking Linux details in to FEXCore for a long time. Since we have moved most of the thread management to the frontend, we can finally move this out of FEXCore and start taking advantage of the `FrontendPtr`. --- FEXCore/Source/Interface/Context/Context.h | 3 +- FEXCore/Source/Interface/Core/Core.cpp | 19 +-- FEXCore/include/FEXCore/Core/Context.h | 2 +- FEXCore/include/FEXCore/Core/CoreState.h | 1 - .../FEXCore/Debug/InternalThreadState.h | 1 - .../FEXCore/HLE/Linux/ThreadManagement.h | 29 ---- Source/Tools/FEXLoader/FEXLoader.cpp | 6 +- .../LinuxSyscalls/GdbServer.cpp | 17 +-- .../LinuxSyscalls/SignalDelegator.cpp | 31 ++-- .../LinuxSyscalls/SignalDelegator.h | 7 +- .../LinuxEmulation/LinuxSyscalls/Syscalls.cpp | 21 +-- .../LinuxEmulation/LinuxSyscalls/Syscalls.h | 83 +---------- .../LinuxSyscalls/Syscalls/Thread.cpp | 67 +++++---- .../LinuxSyscalls/Syscalls/Thread.h | 8 +- .../LinuxSyscalls/ThreadManager.cpp | 66 ++++---- .../LinuxSyscalls/ThreadManager.h | 141 ++++++++++++++++++ .../LinuxSyscalls/x32/Thread.cpp | 9 +- .../LinuxSyscalls/x64/Thread.cpp | 1 - .../TestHarnessRunner/TestHarnessRunner.cpp | 14 +- 19 files changed, 282 insertions(+), 244 deletions(-) delete mode 100644 FEXCore/include/FEXCore/HLE/Linux/ThreadManagement.h create mode 100644 Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h diff --git a/FEXCore/Source/Interface/Context/Context.h b/FEXCore/Source/Interface/Context/Context.h index 1887890d29..66caf5cfd3 100644 --- a/FEXCore/Source/Interface/Context/Context.h +++ b/FEXCore/Source/Interface/Context/Context.h @@ -130,7 +130,7 @@ namespace FEXCore::Context { * - HandleCallback(Thread, RIP); */ - FEXCore::Core::InternalThreadState* CreateThread(uint64_t InitialRIP, uint64_t StackPointer, FEXCore::Core::CPUState *NewThreadState, uint64_t ParentTID) override; + FEXCore::Core::InternalThreadState* CreateThread(uint64_t InitialRIP, uint64_t StackPointer, FEXCore::Core::CPUState *NewThreadState) override; // Public for threading void ExecutionThread(FEXCore::Core::InternalThreadState *Thread) override; @@ -277,7 +277,6 @@ namespace FEXCore::Context { static void ThreadRemoveCodeEntryFromJit(FEXCore::Core::CpuStateFrame *Frame, uint64_t GuestRIP) { auto Thread = Frame->Thread; - LOGMAN_THROW_A_FMT(Thread->ThreadManager.GetTID() == FHU::Syscalls::gettid(), "Must be called from owning thread {}, not {}", Thread->ThreadManager.GetTID(), FHU::Syscalls::gettid()); auto lk = GuardSignalDeferringSection(static_cast(Thread->CTX)->CodeInvalidationMutex, Thread); ThreadRemoveCodeEntry(Thread, GuestRIP); diff --git a/FEXCore/Source/Interface/Core/Core.cpp b/FEXCore/Source/Interface/Core/Core.cpp index f262cb6ef1..21353a6e17 100644 --- a/FEXCore/Source/Interface/Core/Core.cpp +++ b/FEXCore/Source/Interface/Core/Core.cpp @@ -38,7 +38,6 @@ desc: Glues Frontend, OpDispatcher and IR Opts & Compilation, LookupCache, Dispa #include #include #include -#include #include #include #include @@ -354,10 +353,6 @@ namespace FEXCore::Context { void ContextImpl::InitializeThreadTLSData(FEXCore::Core::InternalThreadState *Thread) { - // Let's do some initial bookkeeping here - Thread->ThreadManager.TID = FHU::Syscalls::gettid(); - Thread->ThreadManager.PID = ::getpid(); - if (ThunkHandler) { ThunkHandler->RegisterTLSState(Thread); } @@ -402,7 +397,7 @@ namespace FEXCore::Context { Thread->PassManager->Finalize(); } - FEXCore::Core::InternalThreadState* ContextImpl::CreateThread(uint64_t InitialRIP, uint64_t StackPointer, FEXCore::Core::CPUState *NewThreadState, uint64_t ParentTID) { + FEXCore::Core::InternalThreadState* ContextImpl::CreateThread(uint64_t InitialRIP, uint64_t StackPointer, FEXCore::Core::CPUState *NewThreadState) { FEXCore::Core::InternalThreadState *Thread = new FEXCore::Core::InternalThreadState{}; Thread->CurrentFrame->State.gregs[X86State::REG_RSP] = StackPointer; @@ -413,8 +408,6 @@ namespace FEXCore::Context { memcpy(&Thread->CurrentFrame->State, NewThreadState, sizeof(FEXCore::Core::CPUState)); } - // Set up the thread manager state - Thread->ThreadManager.parent_tid = ParentTID; Thread->CurrentFrame->Thread = Thread; InitializeCompiler(Thread); @@ -909,16 +902,6 @@ namespace FEXCore::Context { // If it is the parent thread that died then just leave FEX_TODO("This doesn't make sense when the parent thread doesn't outlive its children"); - - if (Thread->ThreadManager.parent_tid == 0) { - CoreShuttingDown.store(true); - Thread->ExitReason = FEXCore::Context::ExitReason::EXIT_SHUTDOWN; - - if (CustomExitHandler) { - CustomExitHandler(Thread->ThreadManager.TID, Thread->ExitReason); - } - } - #ifndef _WIN32 Alloc::OSAllocator::UninstallTLSData(Thread); #endif diff --git a/FEXCore/include/FEXCore/Core/Context.h b/FEXCore/include/FEXCore/Core/Context.h index c968b6fd0b..8c23c22526 100644 --- a/FEXCore/include/FEXCore/Core/Context.h +++ b/FEXCore/include/FEXCore/Core/Context.h @@ -201,7 +201,7 @@ namespace FEXCore::Context { * @return A new InternalThreadState object for using with a new guest thread. */ - FEX_DEFAULT_VISIBILITY virtual FEXCore::Core::InternalThreadState* CreateThread(uint64_t InitialRIP, uint64_t StackPointer, FEXCore::Core::CPUState *NewThreadState = nullptr, uint64_t ParentTID = 0) = 0; + FEX_DEFAULT_VISIBILITY virtual FEXCore::Core::InternalThreadState* CreateThread(uint64_t InitialRIP, uint64_t StackPointer, FEXCore::Core::CPUState *NewThreadState = nullptr) = 0; FEX_DEFAULT_VISIBILITY virtual void ExecutionThread(FEXCore::Core::InternalThreadState *Thread) = 0; FEX_DEFAULT_VISIBILITY virtual void DestroyThread(FEXCore::Core::InternalThreadState *Thread, bool NeedsTLSUninstall = false) = 0; diff --git a/FEXCore/include/FEXCore/Core/CoreState.h b/FEXCore/include/FEXCore/Core/CoreState.h index a11d677a42..33e11237b5 100644 --- a/FEXCore/include/FEXCore/Core/CoreState.h +++ b/FEXCore/include/FEXCore/Core/CoreState.h @@ -3,7 +3,6 @@ #include "FEXCore/Core/X86Enums.h" #include "FEXCore/IR/IR.h" -#include #include #include #include diff --git a/FEXCore/include/FEXCore/Debug/InternalThreadState.h b/FEXCore/include/FEXCore/Debug/InternalThreadState.h index f5bdfe2a64..7910b920eb 100644 --- a/FEXCore/include/FEXCore/Debug/InternalThreadState.h +++ b/FEXCore/include/FEXCore/Debug/InternalThreadState.h @@ -108,7 +108,6 @@ namespace FEXCore::Core { fextl::unique_ptr FrontendDecoder; fextl::unique_ptr PassManager; - FEXCore::HLE::ThreadManagement ThreadManager; fextl::unique_ptr SymbolBuffer; int StatusCode{}; diff --git a/FEXCore/include/FEXCore/HLE/Linux/ThreadManagement.h b/FEXCore/include/FEXCore/HLE/Linux/ThreadManagement.h deleted file mode 100644 index b07f30576f..0000000000 --- a/FEXCore/include/FEXCore/HLE/Linux/ThreadManagement.h +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MIT -#pragma once -#include -#include - -namespace FEXCore::HLE { -// XXX: This should map multiple IDs correctly -// Tracking relationships between thread IDs and such -class ThreadManagement { -public: - uint64_t GetUID() const { return UID; } - uint64_t GetGID() const { return GID; } - uint64_t GetEUID() const { return EUID; } - uint64_t GetEGID() const { return EGID; } - uint64_t GetTID() const { return TID; } - uint64_t GetPID() const { return PID; } - - uint64_t UID{1000}; - uint64_t GID{1000}; - uint64_t EUID{1000}; - uint64_t EGID{1000}; - std::atomic TID{1}; - uint64_t PID{1}; - int32_t *set_child_tid{0}; - int32_t *clear_child_tid{0}; - uint64_t parent_tid{0}; - uint64_t robust_list_head{0}; -}; -} diff --git a/Source/Tools/FEXLoader/FEXLoader.cpp b/Source/Tools/FEXLoader/FEXLoader.cpp index 797ba75c17..640c019ce3 100644 --- a/Source/Tools/FEXLoader/FEXLoader.cpp +++ b/Source/Tools/FEXLoader/FEXLoader.cpp @@ -560,7 +560,7 @@ int main(int argc, char **argv, char **const envp) { FEX::AOT::AOTGenSection(CTX.get(), Section); } } else { - CTX->RunUntilExit(ParentThread); + CTX->RunUntilExit(ParentThread->Thread); } if (AOTEnabled) { @@ -581,10 +581,10 @@ int main(int argc, char **argv, char **const envp) { } } - auto ProgramStatus = ParentThread->StatusCode; + auto ProgramStatus = ParentThread->Thread->StatusCode; SignalDelegation->UninstallTLSState(ParentThread); - CTX->DestroyThread(ParentThread); + SyscallHandler->TM.DestroyThread(ParentThread); DebugServer.reset(); SyscallHandler.reset(); diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp index 521af25f41..9221dfc746 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp @@ -22,7 +22,6 @@ desc: Provides a gdb interface to the guest state #include #include #include -#include #include #include #include @@ -341,14 +340,14 @@ fextl::string GdbServer::readRegs() { FEXCore::Core::CPUState state{}; auto Threads = SyscallHandler->TM.GetThreads(); - FEXCore::Core::InternalThreadState *CurrentThread { Threads->at(0) }; + FEX::HLE::ThreadStateObject *CurrentThread { Threads->at(0) }; bool Found = false; for (auto &Thread : *Threads) { if (Thread->ThreadManager.GetTID() != CurrentDebuggingThread) { continue; } - memcpy(&state, Thread->CurrentFrame, sizeof(state)); + memcpy(&state, Thread->Thread->CurrentFrame, sizeof(state)); CurrentThread = Thread; Found = true; break; @@ -356,14 +355,14 @@ fextl::string GdbServer::readRegs() { if (!Found) { // If set to an invalid thread then just get the parent thread ID - memcpy(&state, CurrentThread->CurrentFrame, sizeof(state)); + memcpy(&state, CurrentThread->Thread->CurrentFrame, sizeof(state)); } // Encode the GDB context definition memcpy(&GDB.gregs[0], &state.gregs[0], sizeof(GDB.gregs)); memcpy(&GDB.rip, &state.rip, sizeof(GDB.rip)); - GDB.eflags = CTX->ReconstructCompactedEFLAGS(CurrentThread, false, nullptr, 0); + GDB.eflags = CTX->ReconstructCompactedEFLAGS(CurrentThread->Thread, false, nullptr, 0); for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_MMS; ++i) { memcpy(&GDB.mm[i], &state.mm[i], sizeof(GDB.mm)); @@ -392,14 +391,14 @@ GdbServer::HandledPacketType GdbServer::readReg(const fextl::string& packet) { FEXCore::Core::CPUState state{}; auto Threads = SyscallHandler->TM.GetThreads(); - FEXCore::Core::InternalThreadState *CurrentThread { Threads->at(0) }; + FEX::HLE::ThreadStateObject *CurrentThread { Threads->at(0) }; bool Found = false; for (auto &Thread : *Threads) { if (Thread->ThreadManager.GetTID() != CurrentDebuggingThread) { continue; } - memcpy(&state, Thread->CurrentFrame, sizeof(state)); + memcpy(&state, Thread->Thread->CurrentFrame, sizeof(state)); CurrentThread = Thread; Found = true; break; @@ -407,7 +406,7 @@ GdbServer::HandledPacketType GdbServer::readReg(const fextl::string& packet) { if (!Found) { // If set to an invalid thread then just get the parent thread ID - memcpy(&state, CurrentThread->CurrentFrame, sizeof(state)); + memcpy(&state, CurrentThread->Thread->CurrentFrame, sizeof(state)); } @@ -419,7 +418,7 @@ GdbServer::HandledPacketType GdbServer::readReg(const fextl::string& packet) { return {encodeHex((unsigned char *)(&state.rip), sizeof(uint64_t)), HandledPacketType::TYPE_ACK}; } else if (addr == offsetof(GDBContextDefinition, eflags)) { - uint32_t eflags = CTX->ReconstructCompactedEFLAGS(CurrentThread, false, nullptr, 0); + uint32_t eflags = CTX->ReconstructCompactedEFLAGS(CurrentThread->Thread, false, nullptr, 0); return {encodeHex((unsigned char *)(&eflags), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; } diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.cpp index 510db10bd9..70d709853f 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.cpp @@ -14,7 +14,6 @@ desc: Handles host -> host and host -> guest signal routing, emulates procmask & #include #include #include -#include #include #include #include @@ -57,7 +56,7 @@ namespace FEX::HLE { static SignalDelegator *GlobalDelegator{}; struct ThreadState { - FEXCore::Core::InternalThreadState *Thread{}; + FEX::HLE::ThreadStateObject *Thread{}; void *AltStackPtr{}; stack_t GuestAltStack { @@ -153,12 +152,14 @@ namespace FEX::HLE { void SignalDelegator::HandleSignal(int Signal, void *Info, void *UContext) { // Let the host take first stab at handling the signal - auto Thread = GetTLSThread(); + auto ThreadDataObject = GetTLSThread(); - if (!Thread) { + if (!ThreadDataObject) { LogMan::Msg::AFmt("[{}] Thread has received a signal and hasn't registered itself with the delegate! Programming error!", FHU::Syscalls::gettid()); } else { + auto Thread = ThreadDataObject->Thread; + SignalHandler &Handler = HostHandlers[Signal]; for (auto &HandlerFunc : Handler.Handlers) { if (HandlerFunc(Thread, Signal, Info, UContext)) { @@ -1387,7 +1388,9 @@ namespace FEX::HLE { return; } Thread->SignalReason.store(Event); - FHU::Syscalls::tgkill(Thread->ThreadManager.PID, Thread->ThreadManager.TID, SignalDelegator::SIGNAL_FOR_PAUSE); + auto ThreadObject = static_cast(Thread->FrontendPtr); + + FHU::Syscalls::tgkill(ThreadObject->ThreadManager.PID, ThreadObject->ThreadManager.TID, SignalDelegator::SIGNAL_FOR_PAUSE); } /** @} */ @@ -1809,11 +1812,11 @@ namespace FEX::HLE { GlobalDelegator = nullptr; } - FEXCore::Core::InternalThreadState *SignalDelegator::GetTLSThread() { + FEX::HLE::ThreadStateObject *SignalDelegator::GetTLSThread() { return ThreadData.Thread; } - void SignalDelegator::RegisterTLSState(FEXCore::Core::InternalThreadState *Thread) { + void SignalDelegator::RegisterTLSState(FEX::HLE::ThreadStateObject *Thread) { ThreadData.Thread = Thread; // Set up our signal alternative stack @@ -1834,14 +1837,14 @@ namespace FEX::HLE { // Get the current host signal mask ::syscall(SYS_rt_sigprocmask, 0, nullptr, &ThreadData.CurrentSignalMask.Val, 8); - if (Thread != (FEXCore::Core::InternalThreadState*)UINTPTR_MAX) { + if (Thread->Thread) { // Reserve a small amount of deferred signal frames. Usually the stack won't be utilized beyond // 1 or 2 signals but add a few more just in case. - Thread->DeferredSignalFrames.reserve(8); + Thread->Thread->DeferredSignalFrames.reserve(8); } } - void SignalDelegator::UninstallTLSState(FEXCore::Core::InternalThreadState *Thread) { + void SignalDelegator::UninstallTLSState(FEX::HLE::ThreadStateObject *Thread) { FEXCore::Allocator::munmap(ThreadData.AltStackPtr, SIGSTKSZ * 16); ThreadData.AltStackPtr = nullptr; @@ -1944,7 +1947,7 @@ namespace FEX::HLE { bool UsingAltStack{}; uint64_t AltStackBase = reinterpret_cast(ThreadData.GuestAltStack.ss_sp); uint64_t AltStackEnd = AltStackBase + ThreadData.GuestAltStack.ss_size; - uint64_t GuestSP = Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSP]; + uint64_t GuestSP = Thread->Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSP]; if (!(ThreadData.GuestAltStack.ss_flags & SS_DISABLE) && GuestSP >= AltStackBase && @@ -2004,7 +2007,7 @@ namespace FEX::HLE { if (PendingSignals != 0) { for (int i = 0; i < 64; ++i) { if (PendingSignals & (1ULL << i)) { - FHU::Syscalls::tgkill(Thread->ThreadManager.PID, Thread->ThreadManager.TID, i + 1); + FHU::Syscalls::tgkill(ThreadData.Thread->ThreadManager.PID, ThreadData.Thread->ThreadManager.TID, i + 1); // We might not even return here which is spooky } } @@ -2053,7 +2056,7 @@ namespace FEX::HLE { *oldset = OldSet; } - CheckForPendingSignals(GetTLSThread()); + CheckForPendingSignals(GetTLSThread()->Thread); return 0; } @@ -2116,7 +2119,7 @@ namespace FEX::HLE { // then this is safe-ish ThreadData.CurrentSignalMask = ThreadData.PreviousSuspendMask; - CheckForPendingSignals(GetTLSThread()); + CheckForPendingSignals(GetTLSThread()->Thread); return Result == -1 ? -errno : Result; diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.h index b928c86d90..b2f3f24af9 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.h @@ -35,6 +35,7 @@ namespace Core { } namespace FEX::HLE { + struct ThreadStateObject; using HostSignalDelegatorFunction = std::function; using HostSignalDelegatorFunctionForGuest = std::function; @@ -54,8 +55,8 @@ namespace FEX::HLE { // Called from the signal trampoline function. void HandleSignal(int Signal, void *Info, void *UContext); - void RegisterTLSState(FEXCore::Core::InternalThreadState *Thread); - void UninstallTLSState(FEXCore::Core::InternalThreadState *Thread); + void RegisterTLSState(FEX::HLE::ThreadStateObject *Thread); + void UninstallTLSState(FEX::HLE::ThreadStateObject *Thread); /** * @brief Registers a signal handler for the host to handle a signal @@ -130,7 +131,7 @@ namespace FEX::HLE { void SaveTelemetry(); private: - FEXCore::Core::InternalThreadState *GetTLSThread(); + FEX::HLE::ThreadStateObject *GetTLSThread(); // Called from the thunk handler to handle the signal void HandleGuestSignal(FEXCore::Core::InternalThreadState *Thread, int Signal, void *Info, void *UContext); diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp index 51c14158da..15a4e34a29 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp @@ -25,7 +25,6 @@ desc: Glue logic, brk allocations #include #include #include -#include #include #include #include @@ -381,7 +380,7 @@ static bool AllFlagsSet(uint64_t Flags, uint64_t Mask) { } struct StackFrameData { - FEXCore::Core::InternalThreadState *Thread{}; + FEX::HLE::ThreadStateObject *Thread{}; FEXCore::Context::Context *CTX{}; FEXCore::Core::CpuStateFrame NewFrame{}; FEX::HLE::clone3_args GuestArgs{}; @@ -460,7 +459,7 @@ static void PrintFlags(uint64_t Flags){ static uint64_t Clone2Handler(FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clone3_args *args) { StackFrameData *Data = (StackFrameData *)FEXCore::Allocator::malloc(sizeof(StackFrameData)); - Data->Thread = Frame->Thread; + Data->Thread = static_cast(Frame->Thread->FrontendPtr); Data->CTX = Frame->Thread->CTX; Data->GuestArgs = *args; @@ -488,7 +487,7 @@ static uint64_t Clone3Handler(FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clo constexpr size_t Offset = sizeof(StackFramePlusRet); StackFramePlusRet *Data = (StackFramePlusRet*)(reinterpret_cast(args->NewStack) + args->StackSize - Offset); Data->Ret = (uint64_t)Clone3HandlerRet; - Data->Data.Thread = Frame->Thread; + Data->Data.Thread = static_cast(Frame->Thread->FrontendPtr); Data->Data.CTX = Frame->Thread->CTX; Data->Data.GuestArgs = *args; @@ -626,6 +625,7 @@ uint64_t CloneHandler(FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clone3_args } auto Thread = Frame->Thread; + auto ThreadObject = static_cast(Thread->FrontendPtr); if (AnyFlagsSet(flags, CLONE_PTRACE)) { PrintFlags(flags); @@ -634,27 +634,28 @@ uint64_t CloneHandler(FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clone3_args if (!(flags & CLONE_THREAD)) { // CLONE_PARENT is ignored (Implied by CLONE_THREAD) - return FEX::HLE::ForkGuest(Thread, Frame, flags, + return FEX::HLE::ForkGuest(ThreadObject, Frame, flags, reinterpret_cast(args->args.stack), args->args.stack_size, reinterpret_cast(args->args.parent_tid), reinterpret_cast(args->args.child_tid), reinterpret_cast(args->args.tls)); } else { - auto NewThread = FEX::HLE::CreateNewThread(Thread->CTX, Frame, args); + auto ThreadObject = FEX::HLE::CreateNewThread(Thread->CTX, Frame, args); + auto Thread = ThreadObject->Thread; // Return the new threads TID - uint64_t Result = NewThread->ThreadManager.GetTID(); + uint64_t Result = ThreadObject->ThreadManager.GetTID(); // Actually start the thread - FEX::HLE::_SyscallHandler->TM.RunThread(NewThread); + FEX::HLE::_SyscallHandler->TM.RunThread(ThreadObject); if (flags & CLONE_VFORK) { // If VFORK is set then the calling process is suspended until the thread exits with execve or exit - NewThread->ExecutionThread->join(nullptr); + ThreadObject->Thread->ExecutionThread->join(nullptr); // Normally a thread cleans itself up on exit. But because we need to join, we are now responsible - Thread->CTX->DestroyThread(NewThread); + Thread->CTX->DestroyThread(ThreadObject->Thread); } SYSCALL_ERRNO(); diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h index f71f442f37..1bbbf173ec 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h @@ -10,6 +10,7 @@ desc: Glue logic, STRACE magic #include "LinuxSyscalls/FileManagement.h" #include "LinuxSyscalls/LinuxAllocator.h" +#include "LinuxSyscalls/ThreadManager.h" #include #include @@ -89,88 +90,6 @@ struct ExecveAtArgs { uint64_t ExecveHandler(const char *pathname, char* const* argv, char* const* envp, ExecveAtArgs Args); -class ThreadManager final { - public: - ThreadManager(FEXCore::Context::Context *CTX, FEX::HLE::SignalDelegator *SignalDelegation) - : CTX {CTX} - , SignalDelegation {SignalDelegation} {} - - ~ThreadManager(); - - FEXCore::Core::InternalThreadState *CreateThread(uint64_t InitialRIP, uint64_t StackPointer, FEXCore::Core::CPUState *NewThreadState = nullptr, uint64_t ParentTID = 0); - void TrackThread(FEXCore::Core::InternalThreadState *Thread) { - std::lock_guard lk(ThreadCreationMutex); - Threads.emplace_back(Thread); - } - - void DestroyThread(FEXCore::Core::InternalThreadState *Thread); - void StopThread(FEXCore::Core::InternalThreadState *Thread); - void RunThread(FEXCore::Core::InternalThreadState *Thread); - - void Pause(); - void Run(); - void Step(); - void Stop(bool IgnoreCurrentThread = false); - - void WaitForIdle(); - void WaitForIdleWithTimeout(); - void WaitForThreadsToRun(); - - void SleepThread(FEXCore::Context::Context *CTX, FEXCore::Core::CpuStateFrame *Frame); - - void UnlockAfterFork(FEXCore::Core::InternalThreadState *Thread, bool Child); - - void IncrementIdleRefCount() { - ++IdleWaitRefCount; - } - - void InvalidateGuestCodeRange(FEXCore::Core::InternalThreadState *CallingThread, uint64_t Start, uint64_t Length) { - std::lock_guard lk(ThreadCreationMutex); - - // Potential deferred since Thread might not be valid. - // Thread object isn't valid very early in frontend's initialization. - // To be more optimal the frontend should provide this code with a valid Thread object earlier. - auto CodeInvalidationlk = GuardSignalDeferringSectionWithFallback(CTX->GetCodeInvalidationMutex(), CallingThread); - - for (auto &Thread : Threads) { - CTX->InvalidateGuestCodeRange(Thread, Start, Length); - } - } - - void InvalidateGuestCodeRange(FEXCore::Core::InternalThreadState *CallingThread, uint64_t Start, uint64_t Length, FEXCore::Context::CodeRangeInvalidationFn callback) { - std::lock_guard lk(ThreadCreationMutex); - - // Potential deferred since Thread might not be valid. - // Thread object isn't valid very early in frontend's initialization. - // To be more optimal the frontend should provide this code with a valid Thread object earlier. - auto CodeInvalidationlk = GuardSignalDeferringSectionWithFallback(CTX->GetCodeInvalidationMutex(), CallingThread); - - for (auto &Thread : Threads) { - CTX->InvalidateGuestCodeRange(Thread, Start, Length, callback); - } - } - - fextl::vector const *GetThreads() const { - return &Threads; - } - - private: - FEXCore::Context::Context *CTX; - FEX::HLE::SignalDelegator *SignalDelegation; - - FEXCore::ForkableUniqueMutex ThreadCreationMutex; - fextl::vector Threads; - - // Thread idling support. - bool Running{}; - std::mutex IdleWaitMutex; - std::condition_variable IdleWaitCV; - std::atomic IdleWaitRefCount{}; - - void HandleThreadDeletion(FEXCore::Core::InternalThreadState *Thread); - void NotifyPause(); -}; - class SyscallHandler : public FEXCore::HLE::SyscallHandler, FEXCore::HLE::SourcecodeResolver, public FEXCore::Allocator::FEXAllocOperators { public: ThreadManager TM; diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.cpp index 513e341678..3ff29bfe94 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.cpp @@ -44,7 +44,7 @@ namespace FEX::HLE { struct ExecutionThreadHandler { FEXCore::Context::Context *CTX; - FEXCore::Core::InternalThreadState *Thread; + FEX::HLE::ThreadStateObject *Thread; }; static void *ThreadHandler(void* Data) { @@ -53,13 +53,17 @@ namespace FEX::HLE { auto Thread = Handler->Thread; FEXCore::Allocator::free(Handler); FEX::HLE::_SyscallHandler->GetSignalDelegator()->RegisterTLSState(Thread); - CTX->ExecutionThread(Thread); + + Thread->ThreadManager.TID = FHU::Syscalls::gettid(); + Thread->ThreadManager.PID = ::getpid(); + + CTX->ExecutionThread(Thread->Thread); FEX::HLE::_SyscallHandler->GetSignalDelegator()->UninstallTLSState(Thread); FEX::HLE::_SyscallHandler->TM.DestroyThread(Thread); return nullptr; } - FEXCore::Core::InternalThreadState *CreateNewThread(FEXCore::Context:: Context *CTX, FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clone3_args *args) { + FEX::HLE::ThreadStateObject *CreateNewThread(FEXCore::Context:: Context *CTX, FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clone3_args *args) { uint64_t flags = args->args.flags; FEXCore::Core::CPUState NewThreadState{}; // Clone copies the parent thread's state @@ -79,29 +83,29 @@ namespace FEX::HLE { if (FEX::HLE::_SyscallHandler->Is64BitMode()) { if (flags & CLONE_SETTLS) { - x64::SetThreadArea(NewThread->CurrentFrame, reinterpret_cast(args->args.tls)); + x64::SetThreadArea(NewThread->Thread->CurrentFrame, reinterpret_cast(args->args.tls)); } // Set us to start just after the syscall instruction - x64::AdjustRipForNewThread(NewThread->CurrentFrame); + x64::AdjustRipForNewThread(NewThread->Thread->CurrentFrame); } else { if (flags & CLONE_SETTLS) { - x32::SetThreadArea(NewThread->CurrentFrame, reinterpret_cast(args->args.tls)); + x32::SetThreadArea(NewThread->Thread->CurrentFrame, reinterpret_cast(args->args.tls)); } - x32::AdjustRipForNewThread(NewThread->CurrentFrame); + x32::AdjustRipForNewThread(NewThread->Thread->CurrentFrame); } // We need to do some post-thread creation setup. - NewThread->StartPaused = true; + NewThread->Thread->StartPaused = true; // Initialize a new thread for execution. ExecutionThreadHandler *Arg = reinterpret_cast(FEXCore::Allocator::malloc(sizeof(ExecutionThreadHandler))); Arg->CTX = CTX; Arg->Thread = NewThread; - NewThread->ExecutionThread = FEXCore::Threads::Thread::Create(ThreadHandler, Arg); + NewThread->Thread->ExecutionThread = FEXCore::Threads::Thread::Create(ThreadHandler, Arg); // Wait for the thread to have started. - NewThread->ThreadWaiting.Wait(); + NewThread->Thread->ThreadWaiting.Wait(); if (FEX::HLE::_SyscallHandler->NeedXIDCheck()) { // The first time an application creates a thread, GLIBC installs their SETXID signal handler. @@ -150,7 +154,7 @@ namespace FEX::HLE { return NewThread; } - uint64_t HandleNewClone(FEXCore::Core::InternalThreadState *Thread, FEXCore::Context::Context *CTX, FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clone3_args *CloneArgs) { + uint64_t HandleNewClone(FEX::HLE::ThreadStateObject *Thread, FEXCore::Context::Context *CTX, FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clone3_args *CloneArgs) { auto GuestArgs = &CloneArgs->args; uint64_t flags = GuestArgs->flags; auto NewThread = Thread; @@ -174,7 +178,7 @@ namespace FEX::HLE { // CLONE_PARENT_SETTID, CLONE_CHILD_SETTID, CLONE_CHILD_CLEARTID, CLONE_PIDFD will be handled by kernel // Call execution thread directly since we already are on the new thread - NewThread->StartRunning.NotifyAll(); // Clear the start running flag + NewThread->Thread->StartRunning.NotifyAll(); // Clear the start running flag CreatedNewThreadObject = true; } else{ @@ -186,32 +190,32 @@ namespace FEX::HLE { ::syscall(SYS_rt_sigprocmask, SIG_SETMASK, &CloneArgs->SignalMask, nullptr, sizeof(CloneArgs->SignalMask)); - Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RAX] = 0; + Thread->Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RAX] = 0; if (GuestArgs->stack == 0) { // Copies in the original thread's stack } else { - Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSP] = GuestArgs->stack; + Thread->Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSP] = GuestArgs->stack; } } if (CloneArgs->Type == TYPE_CLONE3) { // If we are coming from a clone3 handler then we need to adjust RSP. - Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSP] += CloneArgs->args.stack_size; + Thread->Thread->CurrentFrame->State.gregs[FEXCore::X86State::REG_RSP] += CloneArgs->args.stack_size; } if (FEX::HLE::_SyscallHandler->Is64BitMode()) { if (flags & CLONE_SETTLS) { - x64::SetThreadArea(NewThread->CurrentFrame, reinterpret_cast(GuestArgs->tls)); + x64::SetThreadArea(NewThread->Thread->CurrentFrame, reinterpret_cast(GuestArgs->tls)); } // Set us to start just after the syscall instruction - x64::AdjustRipForNewThread(NewThread->CurrentFrame); + x64::AdjustRipForNewThread(NewThread->Thread->CurrentFrame); } else { if (flags & CLONE_SETTLS) { - x32::SetThreadArea(NewThread->CurrentFrame, reinterpret_cast(GuestArgs->tls)); + x32::SetThreadArea(NewThread->Thread->CurrentFrame, reinterpret_cast(GuestArgs->tls)); } - x32::AdjustRipForNewThread(NewThread->CurrentFrame); + x32::AdjustRipForNewThread(NewThread->Thread->CurrentFrame); } // Depending on clone settings, our TID and PID could have changed @@ -227,15 +231,15 @@ namespace FEX::HLE { // 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); + CTX->ExecutionThread(Thread->Thread); FEX::HLE::_SyscallHandler->GetSignalDelegator()->UninstallTLSState(Thread); // The rest of the context remains as is and the thread will continue executing - return Thread->StatusCode; + return Thread->Thread->StatusCode; } - uint64_t ForkGuest(FEXCore::Core::InternalThreadState *Thread, FEXCore::Core::CpuStateFrame *Frame, uint32_t flags, void *stack, size_t StackSize, pid_t *parent_tid, pid_t *child_tid, void *tls) { + uint64_t ForkGuest(FEX::HLE::ThreadStateObject *Thread, FEXCore::Core::CpuStateFrame *Frame, uint32_t flags, void *stack, size_t StackSize, pid_t *parent_tid, pid_t *child_tid, void *tls) { // Just before we fork, we lock all syscall mutexes so that both processes will end up with a locked mutex uint64_t Mask{~0ULL}; @@ -364,11 +368,11 @@ namespace FEX::HLE { }); REGISTER_SYSCALL_IMPL_FLAGS(fork, SyscallFlags::DEFAULT, [](FEXCore::Core::CpuStateFrame *Frame) -> uint64_t { - return ForkGuest(Frame->Thread, Frame, 0, 0, 0, 0, 0, 0); + return ForkGuest(static_cast(Frame->Thread->FrontendPtr), Frame, 0, 0, 0, 0, 0, 0); }); REGISTER_SYSCALL_IMPL_FLAGS(vfork, SyscallFlags::DEFAULT, [](FEXCore::Core::CpuStateFrame *Frame) -> uint64_t { - return ForkGuest(Frame->Thread, Frame, CLONE_VFORK, 0, 0, 0, 0, 0); + return ForkGuest(static_cast(Frame->Thread->FrontendPtr), Frame, CLONE_VFORK, 0, 0, 0, 0, 0); }); REGISTER_SYSCALL_IMPL_FLAGS(getpgrp, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, @@ -392,11 +396,12 @@ namespace FEX::HLE { // Since this thread is hard stopping, we can't track the TLS/DTV teardown in FEX's thread handling. FEXCore::Allocator::YesIKnowImNotSupposedToUseTheGlibcAllocator::HardDisable(); - if (Thread->ThreadManager.clear_child_tid) { - std::atomic *Addr = reinterpret_cast*>(Thread->ThreadManager.clear_child_tid); + auto ThreadObject = static_cast(Thread->FrontendPtr); + if (ThreadObject->ThreadManager.clear_child_tid) { + std::atomic *Addr = reinterpret_cast*>(ThreadObject->ThreadManager.clear_child_tid); Addr->store(0); syscall(SYSCALL_DEF(futex), - Thread->ThreadManager.clear_child_tid, + ThreadObject->ThreadManager.clear_child_tid, FUTEX_WAKE, ~0ULL, 0, @@ -405,7 +410,7 @@ namespace FEX::HLE { } Thread->StatusCode = status; - FEX::HLE::_SyscallHandler->TM.StopThread(Thread); + FEX::HLE::_SyscallHandler->TM.StopThread(ThreadObject); return 0; }); @@ -495,8 +500,10 @@ namespace FEX::HLE { REGISTER_SYSCALL_IMPL_FLAGS(set_tid_address, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY, [](FEXCore::Core::CpuStateFrame *Frame, int *tidptr) -> uint64_t { auto Thread = Frame->Thread; - Thread->ThreadManager.clear_child_tid = tidptr; - return Thread->ThreadManager.GetTID(); + auto ThreadObject = static_cast(Thread->FrontendPtr); + + ThreadObject->ThreadManager.clear_child_tid = tidptr; + return ThreadObject->ThreadManager.GetTID(); }); REGISTER_SYSCALL_IMPL_FLAGS(exit_group, SyscallFlags::OPTIMIZETHROUGH | SyscallFlags::NOSYNCSTATEONENTRY | SyscallFlags::NORETURN, diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.h index db5609d0f5..99b63f89ca 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.h @@ -15,7 +15,9 @@ struct CPUState; } namespace FEX::HLE { - FEXCore::Core::InternalThreadState *CreateNewThread(FEXCore::Context::Context *CTX, FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clone3_args *args); - uint64_t HandleNewClone(FEXCore::Core::InternalThreadState *Thread, FEXCore::Context::Context *CTX, FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clone3_args *GuestArgs); - uint64_t ForkGuest(FEXCore::Core::InternalThreadState *Thread, FEXCore::Core::CpuStateFrame *Frame, uint32_t flags, void *stack, size_t StackSize, pid_t *parent_tid, pid_t *child_tid, void *tls); + struct ThreadStateObject; + + FEX::HLE::ThreadStateObject *CreateNewThread(FEXCore::Context::Context *CTX, FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clone3_args *args); + uint64_t HandleNewClone(FEX::HLE::ThreadStateObject *Thread, FEXCore::Context::Context *CTX, FEXCore::Core::CpuStateFrame *Frame, FEX::HLE::clone3_args *GuestArgs); + uint64_t ForkGuest(FEX::HLE::ThreadStateObject *Thread, FEXCore::Core::CpuStateFrame *Frame, uint32_t flags, void *stack, size_t StackSize, pid_t *parent_tid, pid_t *child_tid, void *tls); } diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.cpp index e6ae03a3b8..e14d656fee 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.cpp @@ -6,14 +6,18 @@ #include namespace FEX::HLE { - FEXCore::Core::InternalThreadState *ThreadManager::CreateThread(uint64_t InitialRIP, uint64_t StackPointer, FEXCore::Core::CPUState *NewThreadState, uint64_t ParentTID) { - auto Thread = CTX->CreateThread(InitialRIP, StackPointer, NewThreadState, ParentTID); + FEX::HLE::ThreadStateObject *ThreadManager::CreateThread(uint64_t InitialRIP, uint64_t StackPointer, FEXCore::Core::CPUState *NewThreadState, uint64_t ParentTID) { + auto ThreadStateObject = new FEX::HLE::ThreadStateObject; + ThreadStateObject->ThreadManager.parent_tid = ParentTID; + + ThreadStateObject->Thread = CTX->CreateThread(InitialRIP, StackPointer, NewThreadState); + ThreadStateObject->Thread->FrontendPtr = ThreadStateObject; ++IdleWaitRefCount; - return Thread; + return ThreadStateObject; } - void ThreadManager::DestroyThread(FEXCore::Core::InternalThreadState *Thread) { + void ThreadManager::DestroyThread(FEX::HLE::ThreadStateObject *Thread) { { std::lock_guard lk(ThreadCreationMutex); auto It = std::find(Threads.begin(), Threads.end(), Thread); @@ -24,29 +28,30 @@ namespace FEX::HLE { HandleThreadDeletion(Thread); } - void ThreadManager::StopThread(FEXCore::Core::InternalThreadState *Thread) { - if (Thread->RunningEvents.Running.exchange(false)) { - SignalDelegation->SignalThread(Thread, FEXCore::Core::SignalEvent::Stop); + void ThreadManager::StopThread(FEX::HLE::ThreadStateObject *Thread) { + if (Thread->Thread->RunningEvents.Running.exchange(false)) { + SignalDelegation->SignalThread(Thread->Thread, FEXCore::Core::SignalEvent::Stop); } } - void ThreadManager::RunThread(FEXCore::Core::InternalThreadState *Thread) { + void ThreadManager::RunThread(FEX::HLE::ThreadStateObject *Thread) { // Tell the thread to start executing - Thread->StartRunning.NotifyAll(); + Thread->Thread->StartRunning.NotifyAll(); } - void ThreadManager::HandleThreadDeletion(FEXCore::Core::InternalThreadState *Thread) { - if (Thread->ExecutionThread) { - if (Thread->ExecutionThread->joinable()) { - Thread->ExecutionThread->join(nullptr); + void ThreadManager::HandleThreadDeletion(FEX::HLE::ThreadStateObject *Thread) { + if (Thread->Thread->ExecutionThread) { + if (Thread->Thread->ExecutionThread->joinable()) { + Thread->Thread->ExecutionThread->join(nullptr); } - if (Thread->ExecutionThread->IsSelf()) { - Thread->ExecutionThread->detach(); + if (Thread->Thread->ExecutionThread->IsSelf()) { + Thread->Thread->ExecutionThread->detach(); } } - CTX->DestroyThread(Thread); + CTX->DestroyThread(Thread->Thread); + delete Thread; --IdleWaitRefCount; IdleWaitCV.notify_all(); } @@ -55,7 +60,7 @@ namespace FEX::HLE { // Tell all the threads that they should pause std::lock_guard lk(ThreadCreationMutex); for (auto &Thread : Threads) { - SignalDelegation->SignalThread(Thread, FEXCore::Core::SignalEvent::Pause); + SignalDelegation->SignalThread(Thread->Thread, FEXCore::Core::SignalEvent::Pause); } } @@ -68,11 +73,11 @@ namespace FEX::HLE { // Spin up all the threads std::lock_guard lk(ThreadCreationMutex); for (auto &Thread : Threads) { - Thread->SignalReason.store(FEXCore::Core::SignalEvent::Return); + Thread->Thread->SignalReason.store(FEXCore::Core::SignalEvent::Return); } for (auto &Thread : Threads) { - Thread->StartRunning.NotifyAll(); + Thread->Thread->StartRunning.NotifyAll(); } } @@ -117,7 +122,7 @@ namespace FEX::HLE { // Walk the threads and tell them to clear their caches // Useful when our block size is set to a large number and we need to step a single instruction for (auto &Thread : Threads) { - CTX->ClearCodeCache(Thread); + CTX->ClearCodeCache(Thread->Thread); } } @@ -130,7 +135,7 @@ namespace FEX::HLE { void ThreadManager::Stop(bool IgnoreCurrentThread) { pid_t tid = FHU::Syscalls::gettid(); - FEXCore::Core::InternalThreadState* CurrentThread{}; + FEX::HLE::ThreadStateObject *CurrentThread{}; // Tell all the threads that they should stop { @@ -147,15 +152,15 @@ namespace FEX::HLE { continue; } - if (Thread->RunningEvents.Running.load()) { + if (Thread->Thread->RunningEvents.Running.load()) { StopThread(Thread); } // If the thread is waiting to start but immediately killed then there can be a hang // This occurs in the case of gdb attach with immediate kill - if (Thread->RunningEvents.WaitingToStart.load()) { - Thread->RunningEvents.EarlyExit = true; - Thread->StartRunning.NotifyAll(); + if (Thread->Thread->RunningEvents.WaitingToStart.load()) { + Thread->Thread->RunningEvents.EarlyExit = true; + Thread->Thread->StartRunning.NotifyAll(); } } } @@ -190,12 +195,12 @@ namespace FEX::HLE { // This function is called after fork // We need to cleanup some of the thread data that is dead for (auto &DeadThread : Threads) { - if (DeadThread == LiveThread) { + if (DeadThread->Thread == LiveThread) { continue; } // Setting running to false ensures that when they are shutdown we won't send signals to kill them - DeadThread->RunningEvents.Running = false; + DeadThread->Thread->RunningEvents.Running = false; // Despite what google searches may susgest, glibc actually has special code to handle forks // with multiple active threads. @@ -206,7 +211,8 @@ namespace FEX::HLE { // Deconstructing the Interneal thread state should clean up most of the state. // But if anything on the now deleted stack is holding a refrence to the heap, it will be leaked - CTX->DestroyThread(DeadThread); + CTX->DestroyThread(DeadThread->Thread); + delete DeadThread; // FIXME: Make sure sure nothing gets leaked via the heap. Ideas: // * Make sure nothing is allocated on the heap without ref in InternalThreadState @@ -216,7 +222,9 @@ namespace FEX::HLE { // Remove all threads but the live thread from Threads Threads.clear(); - Threads.push_back(LiveThread); + + auto LiveThreadData = static_cast(LiveThread->FrontendPtr); + Threads.push_back(LiveThreadData); // Clean up dead stacks FEXCore::Threads::Thread::CleanupAfterFork(); diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h new file mode 100644 index 0000000000..aeeb5ad41b --- /dev/null +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: MIT +/* +$info$ +tags: LinuxSyscalls|common +desc: Glue logic, STRACE magic +$end_info$ +*/ +#pragma once + +#include +#include +#include + +#include +#include + +namespace FEXCore { + namespace Context { + class Context; + } + namespace Core { + struct CpuStateFrame; + struct InternalThreadState; + } +} + + +namespace FEX::HLE { +class SyscallHandler; +class SignalDelegator; + + +class LinuxThreadData final{ +public: + uint64_t GetUID() const { return UID; } + uint64_t GetGID() const { return GID; } + uint64_t GetEUID() const { return EUID; } + uint64_t GetEGID() const { return EGID; } + uint64_t GetTID() const { return TID; } + uint64_t GetPID() const { return PID; } + + uint64_t UID{1000}; + uint64_t GID{1000}; + uint64_t EUID{1000}; + uint64_t EGID{1000}; + std::atomic TID{1}; + uint64_t PID{1}; + int32_t *set_child_tid{0}; + int32_t *clear_child_tid{0}; + uint64_t parent_tid{0}; + uint64_t robust_list_head{0}; +}; + +struct ThreadStateObject : public FEXCore::Allocator::FEXAllocOperators { + FEXCore::Core::InternalThreadState *Thread; + LinuxThreadData ThreadManager; +}; + +class ThreadManager final { + public: + ThreadManager(FEXCore::Context::Context *CTX, FEX::HLE::SignalDelegator *SignalDelegation) + : CTX {CTX} + , SignalDelegation {SignalDelegation} {} + + ~ThreadManager(); + + FEX::HLE::ThreadStateObject *CreateThread(uint64_t InitialRIP, uint64_t StackPointer, FEXCore::Core::CPUState *NewThreadState = nullptr, uint64_t ParentTID = 0); + void TrackThread(FEX::HLE::ThreadStateObject *Thread) { + std::lock_guard lk(ThreadCreationMutex); + Threads.emplace_back(Thread); + } + + void DestroyThread(FEX::HLE::ThreadStateObject *Thread); + void StopThread(FEX::HLE::ThreadStateObject *Thread); + void RunThread(FEX::HLE::ThreadStateObject *Thread); + + void Pause(); + void Run(); + void Step(); + void Stop(bool IgnoreCurrentThread = false); + + void WaitForIdle(); + void WaitForIdleWithTimeout(); + void WaitForThreadsToRun(); + + void SleepThread(FEXCore::Context::Context *CTX, FEXCore::Core::CpuStateFrame *Frame); + + void UnlockAfterFork(FEXCore::Core::InternalThreadState *Thread, bool Child); + + void IncrementIdleRefCount() { + ++IdleWaitRefCount; + } + + void InvalidateGuestCodeRange(FEXCore::Core::InternalThreadState *CallingThread, uint64_t Start, uint64_t Length) { + std::lock_guard lk(ThreadCreationMutex); + + // Potential deferred since Thread might not be valid. + // Thread object isn't valid very early in frontend's initialization. + // To be more optimal the frontend should provide this code with a valid Thread object earlier. + auto CodeInvalidationlk = GuardSignalDeferringSectionWithFallback(CTX->GetCodeInvalidationMutex(), CallingThread); + + for (auto &Thread : Threads) { + CTX->InvalidateGuestCodeRange(Thread->Thread, Start, Length); + } + } + + void InvalidateGuestCodeRange(FEXCore::Core::InternalThreadState *CallingThread, uint64_t Start, uint64_t Length, FEXCore::Context::CodeRangeInvalidationFn callback) { + std::lock_guard lk(ThreadCreationMutex); + + // Potential deferred since Thread might not be valid. + // Thread object isn't valid very early in frontend's initialization. + // To be more optimal the frontend should provide this code with a valid Thread object earlier. + auto CodeInvalidationlk = GuardSignalDeferringSectionWithFallback(CTX->GetCodeInvalidationMutex(), CallingThread); + + for (auto &Thread : Threads) { + CTX->InvalidateGuestCodeRange(Thread->Thread, Start, Length, callback); + } + } + + fextl::vector const *GetThreads() const { + return &Threads; + } + + private: + FEXCore::Context::Context *CTX; + FEX::HLE::SignalDelegator *SignalDelegation; + + FEXCore::ForkableUniqueMutex ThreadCreationMutex; + fextl::vector Threads; + + // Thread idling support. + bool Running{}; + std::mutex IdleWaitMutex; + std::condition_variable IdleWaitCV; + std::atomic IdleWaitRefCount{}; + + void HandleThreadDeletion(FEX::HLE::ThreadStateObject *Thread); + void NotifyPause(); +}; + +} diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Thread.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Thread.cpp index e41fbbe0d1..f724c4794e 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Thread.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/x32/Thread.cpp @@ -16,7 +16,6 @@ tags: LinuxSyscalls|syscalls-x86-32 #include #include -#include #include #include @@ -174,17 +173,21 @@ namespace FEX::HLE::x32 { } auto Thread = Frame->Thread; + auto ThreadObject = static_cast(Thread->FrontendPtr); + // Retain the robust list head but don't give it to the kernel // The kernel would break if it tried parsing a 32bit robust list from a 64bit process - Thread->ThreadManager.robust_list_head = reinterpret_cast(head); + ThreadObject->ThreadManager.robust_list_head = reinterpret_cast(head); return 0; }); REGISTER_SYSCALL_IMPL_X32(get_robust_list, [](FEXCore::Core::CpuStateFrame *Frame, int pid, struct robust_list_head **head, uint32_t *len_ptr) -> uint64_t { auto Thread = Frame->Thread; + auto ThreadObject = static_cast(Thread->FrontendPtr); + // Give the robust list back to the application // Steam specifically checks to make sure the robust list is set - *(uint32_t*)head = (uint32_t)Thread->ThreadManager.robust_list_head; + *(uint32_t*)head = (uint32_t)ThreadObject->ThreadManager.robust_list_head; *len_ptr = 12; return 0; }); diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Thread.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Thread.cpp index a650a36cc8..75413f2269 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Thread.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Thread.cpp @@ -12,7 +12,6 @@ tags: LinuxSyscalls|syscalls-x86-64 #include #include -#include #include #include diff --git a/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp b/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp index 230341dab7..2c47f0411b 100644 --- a/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp +++ b/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp @@ -308,7 +308,7 @@ int main(int argc, char **argv, char **const envp) { if (!CTX->InitCore()) { return 1; } - auto ParentThread = CTX->CreateThread(Loader.DefaultRIP(), Loader.GetStackPointer()); + auto ParentThread = SyscallHandler->TM.CreateThread(Loader.DefaultRIP(), Loader.GetStackPointer()); SignalDelegation->RegisterTLSState(ParentThread); if (!ParentThread) { @@ -317,22 +317,25 @@ int main(int argc, char **argv, char **const envp) { int LongJumpVal = setjmp(LongJumpHandler::LongJump); if (!LongJumpVal) { - CTX->RunUntilExit(ParentThread); + CTX->RunUntilExit(ParentThread->Thread); } // Just re-use compare state. It also checks against the expected values in config. - memcpy(&State, &ParentThread->CurrentFrame->State, sizeof(State)); + memcpy(&State, &ParentThread->Thread->CurrentFrame->State, sizeof(State)); SyscallHandler.reset(); SignalDelegation->UninstallTLSState(ParentThread); - CTX->DestroyThread(ParentThread, true); + CTX->DestroyThread(ParentThread->Thread, true); } #ifndef _WIN32 else { // Run as host SupportsAVX = true; - SignalDelegation->RegisterTLSState((FEXCore::Core::InternalThreadState*)UINTPTR_MAX); + + FEX::HLE::ThreadStateObject ThreadStateObject{}; + SignalDelegation->RegisterTLSState(&ThreadStateObject); + if (!Loader.MapMemory()) { // failed to map LogMan::Msg::EFmt("Failed to map {}-bit elf file.", Loader.Is64BitMode() ? 64 : 32); @@ -340,6 +343,7 @@ int main(int argc, char **argv, char **const envp) { } RunAsHost(SignalDelegation, Loader.DefaultRIP(), Loader.GetStackPointer(), &State); + SignalDelegation->UninstallTLSState(&ThreadStateObject); } #endif