diff --git a/Source/Interface/Config/Config.cpp b/Source/Interface/Config/Config.cpp index b026b63e6..3bf59ed32 100644 --- a/Source/Interface/Config/Config.cpp +++ b/Source/Interface/Config/Config.cpp @@ -21,6 +21,9 @@ namespace FEXCore::Config { case FEXCore::Config::CONFIG_SINGLESTEP: CTX->RunningMode = Config != 0 ? FEXCore::Context::CoreRunningMode::MODE_SINGLESTEP : FEXCore::Context::CoreRunningMode::MODE_RUN; break; + case FEXCore::Config::CONFIG_GDBSERVER: + Config != 0 ? CTX->StartGdbServer() : CTX->StopGdbServer(); + break; default: LogMan::Msg::A("Unknown configuration option"); } } @@ -41,6 +44,8 @@ namespace FEXCore::Config { break; case FEXCore::Config::CONFIG_SINGLESTEP: return CTX->RunningMode == FEXCore::Context::CoreRunningMode::MODE_SINGLESTEP ? 1 : 0; + case FEXCore::Config::CONFIG_GDBSERVER: + return CTX->GetGdbServerStatus(); break; default: LogMan::Msg::A("Unknown configuration option"); } diff --git a/Source/Interface/Context/Context.h b/Source/Interface/Context/Context.h index 0eeae0068..7dda4f494 100644 --- a/Source/Interface/Context/Context.h +++ b/Source/Interface/Context/Context.h @@ -11,11 +11,13 @@ #include #include +#include #include namespace FEXCore { class SyscallHandler; class BlockSamplingData; +class GdbServer; namespace CPU { class JITCore; @@ -42,6 +44,7 @@ namespace FEXCore::Context { int64_t MaxInstPerBlock {-1LL}; uint64_t VirtualMemSize {1ULL << 36}; FEXCore::Config::ConfigCore Core {FEXCore::Config::CONFIG_INTERPRETER}; + bool GdbServer {false}; // LLVM JIT options bool LLVM_MemoryValidation {false}; @@ -68,6 +71,8 @@ namespace FEXCore::Context { CustomCPUFactoryType FallbackCPUFactory; std::function CustomExitHandler; + + #ifdef BLOCKSTATS std::unique_ptr BlockData; #endif @@ -82,6 +87,10 @@ namespace FEXCore::Context { void Run(); void Step(); + bool GetGdbServerStatus() { return (bool)DebugServer; } + void StartGdbServer(); + void StopGdbServer(); + // Debugger interface void CompileRIP(FEXCore::Core::InternalThreadState *Thread, uint64_t RIP); uint64_t GetThreadCount() const; @@ -128,6 +137,7 @@ namespace FEXCore::Context { uint64_t StartingRIP; IR::RegisterAllocationPass *RAPass {}; std::mutex ExitMutex; + std::unique_ptr DebugServer; bool StartPaused = false; #if ENABLE_JITSYMBOLS diff --git a/Source/Interface/Core/Core.cpp b/Source/Interface/Core/Core.cpp index 1183e41cd..d7f2e9714 100644 --- a/Source/Interface/Core/Core.cpp +++ b/Source/Interface/Core/Core.cpp @@ -284,17 +284,22 @@ namespace FEXCore::Context { uint64_t SlotSize = Loader->InitializeThreadSlot(TLSSlotWriter); Thread->State.State.rip = StartingRIP = Loader->DefaultRIP(); - if (false) { - auto gdb = new GdbServer(this, Loader); - gdb->StartAndBlock(); - StartPaused = true; - } - InitializeThread(Thread); return true; } + void Context::StartGdbServer() { + if (!DebugServer) { + DebugServer = std::make_unique(this); + StartPaused = true; + } + } + + void Context::StopGdbServer() { + DebugServer.reset(); + } + void Context::WaitForIdle() { do { bool AllPaused = true; diff --git a/Source/Interface/Core/GdbServer.cpp b/Source/Interface/Core/GdbServer.cpp index d34f57d85..4bbee14e6 100644 --- a/Source/Interface/Core/GdbServer.cpp +++ b/Source/Interface/Core/GdbServer.cpp @@ -22,8 +22,22 @@ namespace FEXCore { -GdbServer::GdbServer(FEXCore::Context::Context *ctx, FEXCore::CodeLoader *Loader) : CTX(ctx) { - std::tie(std::ignore, std::ignore, data_offset) = Loader->GetLayout(); +static const std::string NO_REPLY = "}{\x7f}{"; // An "unlikely" packet + +void GdbServer::Break() { + std::lock_guard lk(sendMutex); + if (CommsStream) + SendPacket(*CommsStream, "S05"); +} + +GdbServer::GdbServer(FEXCore::Context::Context *ctx) : CTX(ctx) { + ctx->CustomExitHandler = [&](uint64_t ThreadId, FEXCore::Context::ExitReason ExitReason) { + if (ExitReason == FEXCore::Context::ExitReason::EXIT_DEBUG) { + this->Break(); + } + }; + + StartThread(); } static int calculateChecksum(std::string &packet) { @@ -100,7 +114,7 @@ std::string GdbServer::ReadPacket(std::iostream &stream) { int expected_checksum = std::strtoul(hexString, nullptr, 16); if (calculateChecksum(packet) == expected_checksum) { - LogMan::Msg::E("Received Packet: \"%s\"", packet.c_str()); + LogMan::Msg::I("Received Packet: \"%s\"", packet.c_str()); stream << "+" << std::flush; return packet; } else { @@ -141,8 +155,12 @@ static std::string escapePacket(std::string packet) { } void GdbServer::SendPacket(std::ostream &stream, std::string packet) { + // In-band signaling, not a great design + if (packet == NO_REPLY) + return; + auto escaped = escapePacket(packet); - LogMan::Msg::E("GdbServer Reply: %s", escaped.c_str()); + //LogMan::Msg::E("GdbServer Reply: %s", escaped.c_str()); stream << '$' << escaped << '#'; stream << std::setfill('0') << std::setw(2) << std::hex << (int)calculateChecksum(escaped); stream << std::flush; @@ -150,7 +168,7 @@ void GdbServer::SendPacket(std::ostream &stream, std::string packet) { std::string GdbServer::readRegs() { auto state = CTX->GetCPUState(); - state.rip = 0x47c990; + // state.rip = 0x47c990; return encodeHex((unsigned char *)&state, sizeof(state)).substr(0, 572*2); } @@ -432,14 +450,14 @@ std::string GdbServer::handleV(std::string& packet) { switch (action) { case 'c': CTX->Run(); - return ""; // fixme + return NO_REPLY; case 's': CTX->Step(); - return ""; // fixme + return NO_REPLY; case 't': CTX->ShouldStop = true; - return ""; + return NO_REPLY; default: return "E00"; } @@ -466,19 +484,26 @@ std::string GdbServer::ProcessPacket(std::string &packet) { } } -void GdbServer::GdbServerLoop(std::unique_ptr stream) { +void GdbServer::GdbServerLoop() { + CommsStream = OpenSocket(); + std::string responce; // Outer server loop. Handles packet start, ACK/NAK and break int c; - while ((c = stream->get()) >= 0 ) { + while ((c = CommsStream->get()) >= 0 ) { switch (c) { case '$': { - std::string packet = ReadPacket(*stream); + std::string packet = ReadPacket(*CommsStream); responce = ProcessPacket(packet); - std::cout << responce; - SendPacket(*stream, responce); + { + std::lock_guard lk(sendMutex); + SendPacket(*CommsStream, responce); + } + if (responce == "") { + LogMan::Msg::D("Unknown packet %s", packet.c_str()); + } break; } case '+': @@ -486,19 +511,29 @@ void GdbServer::GdbServerLoop(std::unique_ptr stream) { break; case '-': // NAK, Resend requested - SendPacket(*stream, responce); + { + std::lock_guard lk(sendMutex); + SendPacket(*CommsStream, responce); + } break; case '\x03': // ASCII EOT - LogMan::Msg::E("GdbServer: Break"); + LogMan::Msg::D("GdbServer: Break"); + CTX->Pause(); break; default: - LogMan::Msg::E("GdbServer: Unexpected byte %c (%02x)", c, c); + LogMan::Msg::D("GdbServer: Unexpected byte %c (%02x)", c, c); } } + + { + std::lock_guard lk(sendMutex); + CommsStream.release(); + } + } -void GdbServer::StartThread(std::unique_ptr stream) { - gdbServerThread = std::thread(&GdbServer::GdbServerLoop, this, std::move(stream)); +void GdbServer::StartThread() { + gdbServerThread = std::thread(&GdbServer::GdbServerLoop, this); } std::unique_ptr GdbServer::OpenSocket() { @@ -535,7 +570,7 @@ std::unique_ptr GdbServer::OpenSocket() { // Block until a connection arrives - LogMan::Msg::E("GdbServer, waiting for connection"); + LogMan::Msg::E("GdbServer, waiting for connection on localhost:8086"); listen(sockfd, 1); new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size); diff --git a/Source/Interface/Core/GdbServer.h b/Source/Interface/Core/GdbServer.h index b4da77a55..9ad64255f 100644 --- a/Source/Interface/Core/GdbServer.h +++ b/Source/Interface/Core/GdbServer.h @@ -1,6 +1,6 @@ +#include #include -#include #include "Interface/Context/Context.h" @@ -10,16 +10,14 @@ namespace FEXCore { class GdbServer { public: - GdbServer(FEXCore::Context::Context *ctx, FEXCore::CodeLoader *Loader); - void StartAndBlock() { StartThread(OpenSocket()); Wait(); } + GdbServer(FEXCore::Context::Context *ctx); private: - std::unique_ptr OpenSocket(); - void StartThread(std::unique_ptr stream); - void GdbServerLoop(std::unique_ptr stream); - void Wait() { + void Break(); - } + std::unique_ptr OpenSocket(); + void StartThread(); + void GdbServerLoop(); std::string ReadPacket(std::iostream &stream); void SendPacket(std::ostream &stream, std::string packet); @@ -34,9 +32,8 @@ private: FEXCore::Context::Context *CTX; std::thread gdbServerThread; uint64_t data_offset; - - std::condition_variable waiting; - + std::unique_ptr CommsStream; + std::mutex sendMutex; }; } diff --git a/Source/Interface/Core/JIT/x86_64/JIT.cpp b/Source/Interface/Core/JIT/x86_64/JIT.cpp index a517c9dd0..1bfa511de 100644 --- a/Source/Interface/Core/JIT/x86_64/JIT.cpp +++ b/Source/Interface/Core/JIT/x86_64/JIT.cpp @@ -378,8 +378,14 @@ void *JITCore::CompileCode([[maybe_unused]] FEXCore::IR::IRListView const ud2(); break; case 4: // HLT + case 6: // INT3 + { mov(al, 1); - xchg(byte [STATE + offsetof(FEXCore::Core::ThreadState, RunningEvents.ShouldStop)], al); + + auto offset = Op->Reason == 4 ? + offsetof(FEXCore::Core::ThreadState, RunningEvents.ShouldStop) // HLT + : offsetof(FEXCore::Core::ThreadState, RunningEvents.ShouldPause); // INT3 + xchg(byte [STATE + offset], al); // This code matches what is in EXITFUNCTION if (SpillSlots) { @@ -398,6 +404,7 @@ void *JITCore::CompileCode([[maybe_unused]] FEXCore::IR::IRListView const #endif ret(); break; + } default: LogMan::Msg::A("Unknown Break reason: %d", Op->Reason); } break; diff --git a/Source/Interface/Core/OpcodeDispatcher.cpp b/Source/Interface/Core/OpcodeDispatcher.cpp index 2e0559fac..7b0d56a8c 100644 --- a/Source/Interface/Core/OpcodeDispatcher.cpp +++ b/Source/Interface/Core/OpcodeDispatcher.cpp @@ -3060,12 +3060,9 @@ void OpDispatchBuilder::ALUOp(OpcodeArgs) { void OpDispatchBuilder::INTOp(OpcodeArgs) { uint8_t Reason{}; uint8_t Literal{}; - BlockSetRIP = true; + bool setRIP = false; switch (Op->OP) { - case 0xCC: - Reason = 0; - break; case 0xCD: Reason = 1; Literal = Op->Src1.TypeLiteral.Literal; @@ -3078,15 +3075,24 @@ void OpDispatchBuilder::INTOp(OpcodeArgs) { break; case 0xF4: { Reason = 4; - - // We want to set RIP to the next instruction after HLT - auto NewRIP = _Constant(Op->PC + Op->InstSize); - _StoreContext(8, offsetof(FEXCore::Core::CPUState, rip), NewRIP); + setRIP = true; break; } case 0x0B: Reason = 5; + case 0xCC: + Reason = 6; + setRIP = true; break; + break; + } + + if (setRIP) { + BlockSetRIP = setRIP; + + // We want to set RIP to the next instruction after HLT/INT3 + auto NewRIP = _Constant(Op->PC + Op->InstSize); + _StoreContext(8, offsetof(FEXCore::Core::CPUState, rip), NewRIP); } if (Op->OP == 0xCE) { // Conditional to only break if Overflow == 1 diff --git a/include/FEXCore/Config/Config.h b/include/FEXCore/Config/Config.h index 0a20a53e6..5c87fba33 100644 --- a/include/FEXCore/Config/Config.h +++ b/include/FEXCore/Config/Config.h @@ -10,6 +10,7 @@ namespace FEXCore::Config { CONFIG_DEFAULTCORE, CONFIG_VIRTUALMEMSIZE, CONFIG_SINGLESTEP, + CONFIG_GDBSERVER, }; enum ConfigCore {