From e2c65189ff0eccce1ab0098af87b1a779ce938ec Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Thu, 2 Nov 2023 20:11:01 -0700 Subject: [PATCH] GDBServer: Preparation work to get this moved to the frontend GDBServer is inherently OS specific which is why all this code is removed when compiling for mingw/win32. This should get moved to the frontend before we start landing more work to clean this interface up. Not really any functional change. Changes: FEXCore/Context: Adds new public interfaces, these were previously private. - WaitForIdle - If `Pause` was called or the process is shutting down then this will wait until all threads have paused or exited. - WaitForThreadsToRun - If `Pause` was previously called and then `Run` was called to get them running again, this waits until all the threads have come out of idle to avoid races. - GetThreads - Returns the `InternalThreadData` for all the current threads. - GDBServer needs to know all the internal thread data state when the threads are paused which is what this gives it. GDBServer: - Removes usages of internal data structures where possible. - This gets it clean enough that moving it out of FEXCore is now possible. --- FEXCore/Source/Interface/Context/Context.h | 13 +++-- FEXCore/Source/Interface/Core/Core.cpp | 2 +- FEXCore/Source/Interface/Core/GdbServer.cpp | 56 ++++++++++++--------- FEXCore/Source/Interface/Core/GdbServer.h | 12 ++--- FEXCore/include/FEXCore/Core/Context.h | 25 +++++++++ 5 files changed, 72 insertions(+), 36 deletions(-) diff --git a/FEXCore/Source/Interface/Context/Context.h b/FEXCore/Source/Interface/Context/Context.h index 7574cb098..e5e0146ab 100644 --- a/FEXCore/Source/Interface/Context/Context.h +++ b/FEXCore/Source/Interface/Context/Context.h @@ -285,9 +285,9 @@ namespace FEXCore::Context { ~ContextImpl(); bool IsPaused() const { return !Running; } - void WaitForThreadsToRun(); + void WaitForThreadsToRun() override; void Stop(bool IgnoreCurrentThread); - void WaitForIdle(); + void WaitForIdle() override; void SignalThread(FEXCore::Core::InternalThreadState *Thread, FEXCore::Core::SignalEvent Event); bool GetGdbServerStatus() const { return DebugServer != nullptr; } @@ -353,8 +353,6 @@ namespace FEXCore::Context { void CopyMemoryMapping(FEXCore::Core::InternalThreadState *ParentThread, FEXCore::Core::InternalThreadState *ChildThread); - fextl::vector* GetThreads() { return &Threads; } - uint8_t GetGPRSize() const { return Config.Is64BitMode ? 8 : 4; } FEXCore::JITSymbols Symbols; @@ -402,6 +400,13 @@ namespace FEXCore::Context { bool ExitOnHLTEnabled() const { return ExitOnHLT; } + ThreadsState GetThreads() override { + return ThreadsState { + .ParentThread = ParentThread, + .Threads = &Threads, + }; + } + FEXCore::CPU::CPUBackendFeatures BackendFeatures; protected: diff --git a/FEXCore/Source/Interface/Core/Core.cpp b/FEXCore/Source/Interface/Core/Core.cpp index aac04da0f..95ac088a4 100644 --- a/FEXCore/Source/Interface/Core/Core.cpp +++ b/FEXCore/Source/Interface/Core/Core.cpp @@ -391,7 +391,7 @@ namespace FEXCore::Context { void ContextImpl::StartGdbServer() { #ifndef _WIN32 if (!DebugServer) { - DebugServer = fextl::make_unique(this); + DebugServer = fextl::make_unique(this, SignalDelegation, SyscallHandler); StartPaused = true; } #endif diff --git a/FEXCore/Source/Interface/Core/GdbServer.cpp b/FEXCore/Source/Interface/Core/GdbServer.cpp index bcd7a76ec..187658350 100644 --- a/FEXCore/Source/Interface/Core/GdbServer.cpp +++ b/FEXCore/Source/Interface/Core/GdbServer.cpp @@ -11,8 +11,6 @@ $end_info$ #include #include #include -#include "Common/SoftFloat.h" -#include "Interface/Context/Context.h" #include #include @@ -72,7 +70,9 @@ void GdbServer::WaitForThreadWakeup() { ThreadBreakEvent.Wait(); } -GdbServer::GdbServer(FEXCore::Context::ContextImpl *ctx) : CTX(ctx) { +GdbServer::GdbServer(FEXCore::Context::Context *ctx, SignalDelegator *SignalDelegation, FEXCore::HLE::SyscallHandler *const SyscallHandler) + : CTX(ctx) + , SyscallHandler {SyscallHandler} { // Pass all signals by default std::fill(PassSignals.begin(), PassSignals.end(), true); @@ -80,19 +80,21 @@ GdbServer::GdbServer(FEXCore::Context::ContextImpl *ctx) : CTX(ctx) { if (ExitReason == FEXCore::Context::ExitReason::EXIT_DEBUG) { this->Break(SIGTRAP); } + + if (ExitReason == FEXCore::Context::ExitReason::EXIT_SHUTDOWN) { + CoreShuttingDown = true; + } }); // This is a total hack as there is currently no way to resume once hitting a segfault // But it's semi-useful for debugging. for (uint32_t Signal = 0; Signal <= SignalDelegator::MAX_SIGNALS; ++Signal) { - ctx->SignalDelegation->RegisterHostSignalHandler(Signal, [this] (FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext) { + SignalDelegation->RegisterHostSignalHandler(Signal, [this] (FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext) { if (PassSignals[Signal]) { // Pass signal to the guest return false; } - this->CTX->Config.RunningMode = FEXCore::Context::CoreRunningMode::MODE_SINGLESTEP; - // Let GDB know that we have a signal this->Break(Signal); @@ -247,12 +249,16 @@ void GdbServer::SendACK(std::ostream &stream, bool NACK) { } } +struct X80Float { + uint8_t Data[10]; +}; + struct FEX_PACKED GDBContextDefinition { uint64_t gregs[Core::CPUState::NUM_GPRS]; uint64_t rip; uint32_t eflags; uint32_t cs, ss, ds, es, fs, gs; - X80SoftFloat mm[Core::CPUState::NUM_MMS]; + X80Float mm[Core::CPUState::NUM_MMS]; uint32_t fctrl; uint32_t fstat; uint32_t dummies[6]; @@ -265,10 +271,10 @@ fextl::string GdbServer::readRegs() { FEXCore::Core::CPUState state{}; auto Threads = CTX->GetThreads(); - FEXCore::Core::InternalThreadState *CurrentThread { CTX->ParentThread }; + FEXCore::Core::InternalThreadState *CurrentThread { Threads.ParentThread }; bool Found = false; - for (auto &Thread : *Threads) { + for (auto &Thread : *Threads.Threads) { if (Thread->ThreadManager.GetTID() != CurrentDebuggingThread) { continue; } @@ -280,7 +286,7 @@ fextl::string GdbServer::readRegs() { if (!Found) { // If set to an invalid thread then just get the parent thread ID - memcpy(&state, CTX->ParentThread->CurrentFrame, sizeof(state)); + memcpy(&state, Threads.ParentThread->CurrentFrame, sizeof(state)); } // Encode the GDB context definition @@ -316,10 +322,10 @@ GdbServer::HandledPacketType GdbServer::readReg(const fextl::string& packet) { FEXCore::Core::CPUState state{}; auto Threads = CTX->GetThreads(); - FEXCore::Core::InternalThreadState *CurrentThread { CTX->ParentThread }; + FEXCore::Core::InternalThreadState *CurrentThread { Threads.ParentThread }; bool Found = false; - for (auto &Thread : *Threads) { + for (auto &Thread : *Threads.Threads) { if (Thread->ThreadManager.GetTID() != CurrentDebuggingThread) { continue; } @@ -331,7 +337,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, CTX->ParentThread->CurrentFrame, sizeof(state)); + memcpy(&state, Threads.ParentThread->CurrentFrame, sizeof(state)); } @@ -354,7 +360,7 @@ GdbServer::HandledPacketType GdbServer::readReg(const fextl::string& packet) { } else if (addr >= offsetof(GDBContextDefinition, mm[0]) && addr < offsetof(GDBContextDefinition, mm[8])) { - return {encodeHex((unsigned char *)(&state.mm[(addr - offsetof(GDBContextDefinition, mm[0])) / sizeof(X80SoftFloat)]), sizeof(X80SoftFloat)), HandledPacketType::TYPE_ACK}; + return {encodeHex((unsigned char *)(&state.mm[(addr - offsetof(GDBContextDefinition, mm[0])) / sizeof(X80Float)]), sizeof(X80Float)), HandledPacketType::TYPE_ACK}; } else if (addr == offsetof(GDBContextDefinition, fctrl)) { // XXX: We don't support this yet @@ -674,7 +680,7 @@ GdbServer::HandledPacketType GdbServer::handleXfer(const fextl::string &packet) ThreadString.clear(); fextl::ostringstream ss; ss << "\n"; - for (auto &Thread : *Threads) { + for (auto &Thread : *Threads.Threads) { // Thread id is in hex without 0x prefix const auto ThreadName = getThreadName(Thread->ThreadManager.GetTID()); ss << "ThreadManager.GetTID() << "\""; @@ -708,11 +714,11 @@ GdbServer::HandledPacketType GdbServer::handleXfer(const fextl::string &packet) } if (object == "auxv") { - auto CodeLoader = CTX->SyscallHandler->GetCodeLoader(); + auto CodeLoader = SyscallHandler->GetCodeLoader(); uint64_t auxv_ptr, auxv_size; CodeLoader->GetAuxv(auxv_ptr, auxv_size); fextl::string data; - if (CTX->Config.Is64BitMode) { + if (Is64BitMode()) { data.resize(auxv_size); memcpy(data.data(), reinterpret_cast(auxv_ptr), data.size()); } @@ -763,7 +769,7 @@ static size_t CheckMemMapping(uint64_t Address, size_t Size) { } GdbServer::HandledPacketType GdbServer::handleProgramOffsets() { - auto CodeLoader = CTX->SyscallHandler->GetCodeLoader(); + auto CodeLoader = SyscallHandler->GetCodeLoader(); uint64_t BaseOffset = CodeLoader->GetBaseOffset(); fextl::string str = fextl::fmt::format("Text={:x};Data={:x};Bss={:x}", BaseOffset, BaseOffset, BaseOffset); return {std::move(str), HandledPacketType::TYPE_ACK}; @@ -907,10 +913,10 @@ GdbServer::HandledPacketType GdbServer::handleQuery(const fextl::string &packet) fextl::ostringstream ss; ss << "m"; - for (size_t i = 0; i < Threads->size(); ++i) { - auto Thread = Threads->at(i); + for (size_t i = 0; i < Threads.Threads->size(); ++i) { + auto Thread = Threads.Threads->at(i); ss << std::hex << Thread->ThreadManager.TID; - if (i != (Threads->size() - 1)) { + if (i != (Threads.Threads->size() - 1)) { ss << ","; } } @@ -931,7 +937,7 @@ GdbServer::HandledPacketType GdbServer::handleQuery(const fextl::string &packet) if (match("qC")) { // Returns the current Thread ID fextl::ostringstream ss; - ss << "m" << std::hex << CTX->ParentThread->ThreadManager.TID; + ss << "m" << std::hex << CTX->GetThreads().ParentThread->ThreadManager.TID; return {ss.str(), HandledPacketType::TYPE_ACK}; } if (match("QStartNoAckMode")) { @@ -999,7 +1005,7 @@ GdbServer::HandledPacketType GdbServer::ThreadAction(char action, uint32_t tid) } case 't': // This thread isn't part of the thread pool - CTX->Stop(false /* Ignore current thread */); + CTX->Stop(); return {"OK", HandledPacketType::TYPE_ACK}; default: return {"E00", HandledPacketType::TYPE_ACK}; @@ -1186,7 +1192,7 @@ GdbServer::HandledPacketType GdbServer::ProcessPacket(const fextl::string &packe case 'Z': // Inserts breakpoint or watchpoint return handleBreakpoint(packet); case 'k': // Kill the process - CTX->Stop(false /* Ignore current thread */); + CTX->Stop(); CTX->WaitForIdle(); // Block until exit return {"", HandledPacketType::TYPE_NONE}; default: @@ -1218,7 +1224,7 @@ void GdbServer::SendPacketPair(const HandledPacketType& response) { void GdbServer::GdbServerLoop() { OpenListenSocket(); - while (!CTX->CoreShuttingDown.load()) { + while (!CoreShuttingDown.load()) { CommsStream = OpenSocket(); HandledPacketType response{}; diff --git a/FEXCore/Source/Interface/Core/GdbServer.h b/FEXCore/Source/Interface/Core/GdbServer.h index e37f5cb94..e4eefc0af 100644 --- a/FEXCore/Source/Interface/Core/GdbServer.h +++ b/FEXCore/Source/Interface/Core/GdbServer.h @@ -7,6 +7,7 @@ $end_info$ #pragma once #include +#include #include #include #include @@ -20,13 +21,9 @@ $end_info$ namespace FEXCore { -namespace Context { - class ContextImpl; -} - class GdbServer { public: - GdbServer(FEXCore::Context::ContextImpl *ctx); + GdbServer(FEXCore::Context::Context *ctx, SignalDelegator *SignalDelegation, FEXCore::HLE::SyscallHandler *const SyscallHandler); // Public for threading void GdbServerLoop(); @@ -77,7 +74,8 @@ private: fextl::string readRegs(); HandledPacketType readReg(const fextl::string& packet); - FEXCore::Context::ContextImpl *CTX; + FEXCore::Context::Context *CTX; + FEXCore::HLE::SyscallHandler *const SyscallHandler; fextl::unique_ptr gdbServerThread; fextl::unique_ptr CommsStream; std::mutex sendMutex; @@ -88,6 +86,7 @@ private: fextl::string OSDataString{}; void buildLibraryMap(); std::atomic LibraryMapChanged = true; + std::atomic CoreShuttingDown{}; fextl::string LibraryMapString{}; // Used to keep track of which signals to pass to the guest @@ -95,6 +94,7 @@ private: uint32_t CurrentDebuggingThread{}; int ListenSocket{}; FEX_CONFIG_OPT(Filename, APP_FILENAME); + FEX_CONFIG_OPT(Is64BitMode, IS64BIT_MODE); }; } diff --git a/FEXCore/include/FEXCore/Core/Context.h b/FEXCore/include/FEXCore/Core/Context.h index b70a40923..38c4ba9b9 100644 --- a/FEXCore/include/FEXCore/Core/Context.h +++ b/FEXCore/include/FEXCore/Core/Context.h @@ -87,6 +87,11 @@ namespace FEXCore::Context { void *VDSO_kernel_rt_sigreturn; }; + struct ThreadsState { + FEXCore::Core::InternalThreadState* ParentThread; + fextl::vector* Threads; + }; + using CodeRangeInvalidationFn = std::function; using CustomCPUFactoryType = std::function(Context*, Core::InternalThreadState *Thread)>; @@ -141,6 +146,18 @@ namespace FEXCore::Context { */ FEX_DEFAULT_VISIBILITY virtual void Pause() = 0; + /** + * @brief Waits for all threads to be idle. + * + * Idling can happen when the process is shutting down or the debugger has asked for all threads to pause. + */ + FEX_DEFAULT_VISIBILITY virtual void WaitForIdle() = 0; + + /** + * @brief When resuming from a paused state, waits for all threads to start executing before returning. + */ + FEX_DEFAULT_VISIBILITY virtual void WaitForThreadsToRun() = 0; + /** * @brief Starts (or continues) the CPU core * @@ -318,6 +335,14 @@ namespace FEXCore::Context { * */ FEX_DEFAULT_VISIBILITY virtual void EnableExitOnHLT() = 0; + + /** + * @brief Gets the thread data for FEX's internal tracked threads. + * + * @return struct containing all the thread information. + */ + FEX_DEFAULT_VISIBILITY virtual ThreadsState GetThreads() = 0; + private: };