diff --git a/Source/Tools/FEXLoader/FEXLoader.cpp b/Source/Tools/FEXLoader/FEXLoader.cpp index 60a8c49936..bf621bf393 100644 --- a/Source/Tools/FEXLoader/FEXLoader.cpp +++ b/Source/Tools/FEXLoader/FEXLoader.cpp @@ -587,6 +587,7 @@ int main(int argc, char** argv, char** const envp) { fextl::unique_ptr DebugServer; if (GdbServer) { DebugServer = fextl::make_unique(CTX.get(), SignalDelegation.get(), SyscallHandler.get()); + SyscallHandler->SetGdbServer(DebugServer.get()); } if (!CTX->InitCore()) { @@ -594,9 +595,9 @@ 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); + SyscallHandler->TM.TrackThread(ParentThread); // Pass in our VDSO thunks ThunkHandler->AppendThunkDefinitions(FEX::VDSO::GetVDSOThunkDefinitions(Loader.Is64BitMode())); diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp index 4265a562b2..32d1cddcbf 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp @@ -10,6 +10,7 @@ desc: Provides a gdb interface to the guest state #include "GdbServer/Info.h" #include "LinuxSyscalls/NetStream.h" +#include "LinuxSyscalls/SignalDelegator.h" #include #include @@ -62,13 +63,12 @@ desc: Provides a gdb interface to the guest state namespace FEX { #ifndef _WIN32 -void GdbServer::Break(FEXCore::Core::InternalThreadState* Thread, int signal) { +void GdbServer::Break(FEX::HLE::ThreadStateObject* ThreadObject, int signal) { std::lock_guard lk(sendMutex); if (!CommsStream) { return; } - auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromFEXCoreThread(Thread); // Current debugging thread switches to the thread that is breaking. CurrentDebuggingThread = ThreadObject->ThreadInfo.TID.load(); @@ -76,11 +76,6 @@ void GdbServer::Break(FEXCore::Core::InternalThreadState* Thread, int signal) { SendPacket(*CommsStream, str); } -void GdbServer::WaitForThreadWakeup() { - // Wait for gdbserver to tell us to wake up - ThreadBreakEvent.Wait(); -} - GdbServer::~GdbServer() { CloseListenSocket(); CoreShuttingDown = true; @@ -119,9 +114,9 @@ GdbServer::GdbServer(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* ThreadObject->GdbInfo->PState = ArchHelpers::Context::GetArmPState(ucontext); // Let GDB know that we have a signal - this->Break(Thread, Signal); + this->Break(ThreadObject, Signal); - WaitForThreadWakeup(); + this->SyscallHandler->TM.SleepThread(this->CTX, ThreadObject); ThreadObject->GdbInfo.reset(); return true; @@ -605,8 +600,14 @@ GdbServer::HandledPacketType GdbServer::handleProgramOffsets() { GdbServer::HandledPacketType GdbServer::ThreadAction(char action, uint32_t tid) { switch (action) { case 'c': { + { + std::lock_guard lk(SyscallHandler->TM.GetThreadsCreationMutex()); + auto Threads = SyscallHandler->TM.GetThreads(); + for (auto& Thread : *Threads) { + Thread->ThreadSleeping.NotifyOne(); + } + } SyscallHandler->TM.Run(); - ThreadBreakEvent.NotifyAll(); SyscallHandler->TM.WaitForThreadsToRun(); return {"", HandledPacketType::TYPE_ONLYACK}; } @@ -1451,6 +1452,13 @@ void GdbServer::StartThread() { FEXCore::Threads::SetSignalMask(OldMask); } +void GdbServer::OnThreadCreated(FEX::HLE::ThreadStateObject* ThreadObject) { + if (SyscallHandler->TM.GetThreadCount() == 1) { + // Sleep the first thread created. This is because FEX only supports attaching at startup currently. + SyscallHandler->TM.SleepThread(CTX, ThreadObject); + } +} + void GdbServer::OpenListenSocket() { const auto GdbUnixPath = fextl::fmt::format("{}/FEX_gdbserver/", FEXServerClient::GetTempFolder()); if (FHU::Filesystem::CreateDirectory(GdbUnixPath) == FHU::Filesystem::CreateDirectoryResult::ERROR) { diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h index af749d8f89..425eabb7ee 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h @@ -35,8 +35,10 @@ class GdbServer { LibraryMapChanged = true; } + void OnThreadCreated(FEX::HLE::ThreadStateObject* ThreadObject); + private: - void Break(FEXCore::Core::InternalThreadState* Thread, int signal); + void Break(FEX::HLE::ThreadStateObject* ThreadObject, int signal); void OpenListenSocket(); void CloseListenSocket(); @@ -52,9 +54,6 @@ class GdbServer { void SendACK(std::ostream& stream, bool NACK); - Event ThreadBreakEvent {}; - void WaitForThreadWakeup(); - struct HandledPacketType { fextl::string Response {}; enum ResponseType { diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.cpp index 51abf11619..94920b4d45 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/SignalDelegator.cpp @@ -476,7 +476,7 @@ bool SignalDelegator::HandleSignalPause(FEXCore::Core::InternalThreadState* Thre // We need to be a little bit careful here // If we were already paused (due to GDB) and we are immediately stopping (due to gdb kill) // Then we need to ensure we don't double decrement our idle thread counter - if (ThreadObject->ThreadSleeping) { + if (ThreadObject->ThreadSleeping.HasWaiter()) { // If the thread was sleeping then its idle counter was decremented // Reincrement it here to not break logic FEX::HLE::_SyscallHandler->TM.IncrementIdleRefCount(); diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp index ac3c8fe781..d1161802a0 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp @@ -744,7 +744,7 @@ void SyscallHandler::DefaultProgramBreak(uint64_t Base, uint64_t Size) { } SyscallHandler::SyscallHandler(FEXCore::Context::Context* _CTX, FEX::HLE::SignalDelegator* _SignalDelegation, FEX::HLE::ThunkHandler* ThunkHandler) - : TM {_CTX, _SignalDelegation} + : TM {_CTX, this, _SignalDelegation} , SeccompEmulator {this, _SignalDelegation} , FM {_CTX} , CTX {_CTX} diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h index db8d5363c0..20ec01f75a 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h @@ -53,7 +53,8 @@ desc: Glue logic, STRACE magic namespace FEX { class CodeLoader; -} +class GdbServer; +} // namespace FEX namespace FEXCore { namespace Context { @@ -282,6 +283,14 @@ class SyscallHandler : public FEXCore::HLE::SyscallHandler, FEXCore::HLE::Source constexpr static uint64_t TASK_MAX_64BIT = (1ULL << 48); + void SetGdbServer(FEX::GdbServer* Server) { + GdbServer = Server; + } + + FEX::GdbServer* GetGdbServer() { + return GdbServer; + } + protected: SyscallHandler(FEXCore::Context::Context* _CTX, FEX::HLE::SignalDelegator* _SignalDelegation, FEX::HLE::ThunkHandler* ThunkHandler); @@ -308,6 +317,7 @@ class SyscallHandler : public FEXCore::HLE::SyscallHandler, FEXCore::HLE::Source FEX::HLE::SignalDelegator* SignalDelegation; FEX::HLE::ThunkHandler* ThunkHandler; + FEX::GdbServer* GdbServer {}; 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 3c93f76935..7eed5b5ec3 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls/Thread.cpp @@ -230,12 +230,12 @@ uint64_t HandleNewClone(FEX::HLE::ThreadStateObject* Thread, FEXCore::Context::C Thread->ThreadInfo.PID = ::getpid(); FEX::HLE::_SyscallHandler->FM.UpdatePID(Thread->ThreadInfo.PID); + FEX::HLE::_SyscallHandler->RegisterTLSState(Thread); + if (CreatedNewThreadObject) { FEX::HLE::_SyscallHandler->TM.TrackThread(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->ExecuteThread(Thread->Thread); diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.cpp index 106e5a4cb1..1532f5d501 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT +#include "LinuxSyscalls/GdbServer.h" #include "LinuxSyscalls/Syscalls.h" #include "LinuxSyscalls/SignalDelegator.h" @@ -29,6 +30,18 @@ FEX::HLE::ThreadStateObject* ThreadManager::CreateThread(uint64_t InitialRIP, ui return ThreadStateObject; } +void ThreadManager::TrackThread(FEX::HLE::ThreadStateObject* Thread) { + { + std::lock_guard lk(ThreadCreationMutex); + Threads.emplace_back(Thread); + } + + auto GdbServer = SyscallHandler->GetGdbServer(); + if (GdbServer) { + GdbServer->OnThreadCreated(Thread); + } +} + void ThreadManager::DestroyThread(FEX::HLE::ThreadStateObject* Thread, bool NeedsTLSUninstall) { { std::lock_guard lk(ThreadCreationMutex); @@ -79,7 +92,10 @@ void ThreadManager::NotifyPause() { // Tell all the threads that they should pause std::lock_guard lk(ThreadCreationMutex); for (auto& Thread : Threads) { - SignalDelegation->SignalThread(Thread->Thread, SignalEvent::Pause); + if (!Thread->ThreadSleeping.HasWaiter()) { + // Only signal if it isn't already sleeping. + SignalDelegation->SignalThread(Thread->Thread, SignalEvent::Pause); + } } } @@ -182,8 +198,7 @@ void ThreadManager::Stop(bool IgnoreCurrentThread) { } } -void ThreadManager::SleepThread(FEXCore::Context::Context* CTX, FEXCore::Core::CpuStateFrame* Frame) { - auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame); +void ThreadManager::SleepThread(FEXCore::Context::Context* CTX, FEX::HLE::ThreadStateObject* ThreadObject) { #if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED // Sanity check. This can only be called from the owning thread. { @@ -197,19 +212,16 @@ void ThreadManager::SleepThread(FEXCore::Context::Context* CTX, FEXCore::Core::C --IdleWaitRefCount; IdleWaitCV.notify_all(); - ThreadObject->ThreadSleeping = true; - - // Go to sleep - ThreadObject->ThreadPaused.Wait(); + // Go to sleep. + ThreadObject->ThreadSleeping.Wait(); ++IdleWaitRefCount; - ThreadObject->ThreadSleeping = false; IdleWaitCV.notify_all(); } void ThreadManager::UnpauseThread(FEX::HLE::ThreadStateObject* Thread) { - Thread->ThreadPaused.NotifyOne(); + Thread->ThreadSleeping.NotifyOne(); } void ThreadManager::UnlockAfterFork(FEXCore::Core::InternalThreadState* LiveThread, bool Child) { diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h index 2401a88357..466706d040 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/ThreadManager.h @@ -22,6 +22,82 @@ namespace FEX::HLE { class SyscallHandler; class SignalDelegator; +// A latch that can be inspected to see if there is a waiter currently active. +// In contrast to std::condition_variable, an InspectableLatch may be signaled *before* being waited on without change of effect. +class InspectableLatch final { +public: + bool Wait(struct timespec* Timeout = nullptr) { + LOGMAN_THROW_A_FMT(Mutex.load() != HAS_SIGNALED, "Stale signal mutex!"); + LOGMAN_THROW_A_FMT(Mutex.load() != HAS_WAITER, "Can't have multiple waiters on a single InspectableLatch!"); + while (true) { + uint32_t Expected = HAS_NO_WAITER; + const uint32_t Desired = HAS_WAITER; + + if (Mutex.compare_exchange_strong(Expected, Desired)) { + // We have latched, sleep using a futex syscall until signaled. + constexpr int Op = FUTEX_WAIT | FUTEX_PRIVATE_FLAG; + // WAIT will keep sleeping on the futex word while it is `val` + int Result = ::syscall(SYS_futex, &Mutex, Op, + Desired, // val + Timeout, // Timeout/val2 + nullptr, // Addr2 + 0); // val3 + + if (Timeout && Result == -1 && errno == ETIMEDOUT) { + return false; + } + } else if (Expected == HAS_SIGNALED) { + // Reset the latch once signaled + Mutex.store(HAS_NO_WAITER); + return true; + } + } + } + + template + bool WaitFor(const std::chrono::duration& time) { + struct timespec Timeout {}; + auto SecondsDuration = std::chrono::duration_cast(time); + Timeout.tv_sec = SecondsDuration.count(); + Timeout.tv_nsec = std::chrono::duration_cast(time - SecondsDuration).count(); + return Wait(&Timeout); + } + + void NotifyOne() { +#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED + LOGMAN_THROW_A_FMT(Mutex.load() != HAS_SIGNALED, "Trying to double signal!"); +#endif + DoNotify(1); + } + + bool HasWaiter() const { + return Mutex.load() == HAS_WAITER; + } + +private: + std::atomic Mutex {}; + constexpr static uint32_t HAS_NO_WAITER = 0; + constexpr static uint32_t HAS_WAITER = 1; + constexpr static uint32_t HAS_SIGNALED = 2; + + void DoNotify(int Waiters) { + uint32_t Expected = HAS_WAITER; + const uint32_t Desired = HAS_SIGNALED; + + // If the mutex is in a waiting state and we have CAS exchanged it to HAS_SIGNALED, then signal the thread to wake up with a futex + // syscall. otherwise just leave since nothing was waiting. + if (Mutex.compare_exchange_strong(Expected, Desired)) { + constexpr int Op = FUTEX_WAKE | FUTEX_PRIVATE_FLAG; + + ::syscall(SYS_futex, &Mutex, Op, + Waiters, // val - Number of waiters to wake + 0, // val2 + &Mutex, // Addr2 - Mutex to do the operation on + 0); // val3 + } + } +}; + enum class SignalEvent : uint32_t { Nothing, // If the guest uses our signal we need to know it was errant on our end Pause, @@ -81,8 +157,7 @@ struct ThreadStateObject : public FEXCore::Allocator::FEXAllocOperators { std::atomic SignalReason {SignalEvent::Nothing}; // Thread pause handling - std::atomic_bool ThreadSleeping {false}; - FEXCore::InterruptableConditionVariable ThreadPaused; + InspectableLatch ThreadSleeping; // GDB signal information struct GdbInfoStruct { @@ -99,8 +174,9 @@ struct ThreadStateObject : public FEXCore::Allocator::FEXAllocOperators { class ThreadManager final { public: - ThreadManager(FEXCore::Context::Context* CTX, FEX::HLE::SignalDelegator* SignalDelegation) + ThreadManager(FEXCore::Context::Context* CTX, FEX::HLE::SyscallHandler* SyscallHandler, FEX::HLE::SignalDelegator* SignalDelegation) : CTX {CTX} + , SyscallHandler {SyscallHandler} , SignalDelegation {SignalDelegation} {} ~ThreadManager(); @@ -116,10 +192,7 @@ class ThreadManager final { FEX::HLE::ThreadStateObject* CreateThread(uint64_t InitialRIP, uint64_t StackPointer, const FEXCore::Core::CPUState* NewThreadState = nullptr, uint64_t ParentTID = 0, FEX::HLE::ThreadStateObject* InheritThread = nullptr); - void TrackThread(FEX::HLE::ThreadStateObject* Thread) { - std::lock_guard lk(ThreadCreationMutex); - Threads.emplace_back(Thread); - } + void TrackThread(FEX::HLE::ThreadStateObject* Thread); void DestroyThread(FEX::HLE::ThreadStateObject* Thread, bool NeedsTLSUninstall = false); void StopThread(FEX::HLE::ThreadStateObject* Thread); @@ -134,7 +207,11 @@ class ThreadManager final { void WaitForIdleWithTimeout(); void WaitForThreadsToRun(); - void SleepThread(FEXCore::Context::Context* CTX, FEXCore::Core::CpuStateFrame* Frame); + void SleepThread(FEXCore::Context::Context* CTX, FEX::HLE::ThreadStateObject* ThreadObject); + void SleepThread(FEXCore::Context::Context* CTX, FEXCore::Core::CpuStateFrame* Frame) { + auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame); + SleepThread(CTX, ThreadObject); + } void UnlockAfterFork(FEXCore::Core::InternalThreadState* Thread, bool Child); @@ -169,12 +246,22 @@ class ThreadManager final { } } + FEXCore::ForkableUniqueMutex& GetThreadsCreationMutex() { + return ThreadCreationMutex; + } + const fextl::vector* GetThreads() const { return &Threads; } + size_t GetThreadCount() { + std::lock_guard lk(ThreadCreationMutex); + return Threads.size(); + } + private: FEXCore::Context::Context* CTX; + FEX::HLE::SyscallHandler* SyscallHandler; FEX::HLE::SignalDelegator* SignalDelegation; FEXCore::ForkableUniqueMutex ThreadCreationMutex; diff --git a/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp b/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp index 11edc6453f..7e6467e13e 100644 --- a/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp +++ b/Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp @@ -323,8 +323,8 @@ int main(int argc, char** argv, char** const envp) { return 1; } auto ParentThread = SyscallHandler->TM.CreateThread(Loader.DefaultRIP(), Loader.GetStackPointer()); - SyscallHandler->TM.TrackThread(ParentThread); SignalDelegation->RegisterTLSState(ParentThread); + SyscallHandler->TM.TrackThread(ParentThread); if (!ParentThread) { return 1;