From 3131ee4de1f2ea01023db94a7be62dc54c0098d8 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Sun, 24 Nov 2024 15:41:02 -0800 Subject: [PATCH 01/11] GdbServer: Moves information fetching to independent files NFC --- Source/Tools/LinuxEmulation/CMakeLists.txt | 1 + .../Tools/LinuxEmulation/GdbServer/Info.cpp | 201 ++++++++++++++++++ Source/Tools/LinuxEmulation/GdbServer/Info.h | 51 +++++ .../LinuxSyscalls/GdbServer.cpp | 186 +--------------- 4 files changed, 258 insertions(+), 181 deletions(-) create mode 100644 Source/Tools/LinuxEmulation/GdbServer/Info.cpp create mode 100644 Source/Tools/LinuxEmulation/GdbServer/Info.h diff --git a/Source/Tools/LinuxEmulation/CMakeLists.txt b/Source/Tools/LinuxEmulation/CMakeLists.txt index bdcade84cb..8db371eecb 100644 --- a/Source/Tools/LinuxEmulation/CMakeLists.txt +++ b/Source/Tools/LinuxEmulation/CMakeLists.txt @@ -3,6 +3,7 @@ add_compile_options(-fno-operator-names) set (SRCS VDSO_Emulation.cpp Thunks.cpp + GdbServer/Info.cpp LinuxSyscalls/GdbServer.cpp LinuxSyscalls/EmulatedFiles/EmulatedFiles.cpp LinuxSyscalls/FaultSafeUserMemAccess.cpp diff --git a/Source/Tools/LinuxEmulation/GdbServer/Info.cpp b/Source/Tools/LinuxEmulation/GdbServer/Info.cpp new file mode 100644 index 0000000000..31b1403960 --- /dev/null +++ b/Source/Tools/LinuxEmulation/GdbServer/Info.cpp @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: MIT +/* +$info$ +tags: glue|gdbserver +desc: Provides a gdb interface to the guest state +$end_info$ +*/ + +#include "GdbServer/Info.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace FEX::GDB::Info { +constexpr std::array FlagNames = { + "CF", "", "PF", "", "AF", "", "ZF", "SF", "TF", "IF", "DF", "OF", "IOPL", "", "NT", "", "RF", "VM", "AC", "VIF", "VIP", "ID", +}; + +const std::string_view& GetFlagName(unsigned Bit) { + LOGMAN_THROW_A_FMT(Bit < 22, "Bit position too large"); + return FlagNames[Bit]; +} + +std::string_view GetGRegName(unsigned Reg) { + switch (Reg) { + case FEXCore::X86State::REG_RAX: return "rax"; + case FEXCore::X86State::REG_RBX: return "rbx"; + case FEXCore::X86State::REG_RCX: return "rcx"; + case FEXCore::X86State::REG_RDX: return "rdx"; + case FEXCore::X86State::REG_RSP: return "rsp"; + case FEXCore::X86State::REG_RBP: return "rbp"; + case FEXCore::X86State::REG_RSI: return "rsi"; + case FEXCore::X86State::REG_RDI: return "rdi"; + case FEXCore::X86State::REG_R8: return "r8"; + case FEXCore::X86State::REG_R9: return "r9"; + case FEXCore::X86State::REG_R10: return "r10"; + case FEXCore::X86State::REG_R11: return "r11"; + case FEXCore::X86State::REG_R12: return "r12"; + case FEXCore::X86State::REG_R13: return "r13"; + case FEXCore::X86State::REG_R14: return "r14"; + case FEXCore::X86State::REG_R15: return "r15"; + default: FEX_UNREACHABLE; + } +} + +fextl::string GetThreadName(uint32_t PID, uint32_t ThreadID) { + const auto ThreadFile = fextl::fmt::format("/proc/{}/task/{}/comm", PID, ThreadID); + fextl::string ThreadName; + FEXCore::FileLoading::LoadFile(ThreadName, ThreadFile); + return ThreadName; +} + +fextl::string BuildOSXML() { + fextl::ostringstream xml; + + xml << "\n"; + + xml << "\n"; + xml << ""; + // XXX + xml << ""; + + xml << std::flush; + + return xml.str(); +} + +fextl::string BuildTargetXML() { + fextl::ostringstream xml; + + xml << "\n"; + xml << "\n"; + xml << "\n"; + xml << "i386:x86-64\n"; + xml << "GNU/Linux\n"; + xml << "\n"; + + xml << "\n"; + // flags register + for (int i = 0; i < 22; i++) { + auto name = GDB::Info::GetFlagName(i); + if (name.empty()) { + continue; + } + xml << "\t\n"; + } + xml << "\n"; + + int32_t TargetSize {}; + auto reg = [&](std::string_view name, std::string_view type, int size) { + TargetSize += size; + xml << "" << std::endl; + }; + + // Register ordering. + // We want to just memcpy our x86 state to gdb, so we tell it the ordering. + + // GPRs + for (uint32_t i = 0; i < FEXCore::Core::CPUState::NUM_GPRS; i++) { + reg(GDB::Info::GetGRegName(i), "int64", 64); + } + + reg("rip", "code_ptr", 64); + + reg("eflags", "fex_eflags", 32); + + // Fake registers which GDB requires, but we don't support; + // We stick them past the end of our cpu state. + + // non-userspace segment registers + reg("cs", "int32", 32); + reg("ss", "int32", 32); + reg("ds", "int32", 32); + reg("es", "int32", 32); + + reg("fs", "int32", 32); + reg("gs", "int32", 32); + + // x87 stack + for (int i = 0; i < 8; i++) { + reg(fextl::fmt::format("st{}", i), "i387_ext", 80); + } + + // x87 control + reg("fctrl", "int32", 32); + reg("fstat", "int32", 32); + reg("ftag", "int32", 32); + reg("fiseg", "int32", 32); + reg("fioff", "int32", 32); + reg("foseg", "int32", 32); + reg("fooff", "int32", 32); + reg("fop", "int32", 32); + + + xml << "\n"; + xml << "\n"; + xml << + R"( + + + + + + + + + + + + + + + )"; + + // SSE regs + for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; i++) { + reg(fextl::fmt::format("xmm{}", i), "vec128", 128); + } + + reg("mxcsr", "int", 32); + + xml << "\n"; + + xml << ""; + xml << + R"( + + + + + + + + + + + + + + + )"; + for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; i++) { + reg(fmt::format("ymm{}h", i), "vec128", 128); + } + xml << "\n"; + + xml << ""; + xml << std::flush; + + return xml.str(); +} + +} // namespace FEX::GDB::Info diff --git a/Source/Tools/LinuxEmulation/GdbServer/Info.h b/Source/Tools/LinuxEmulation/GdbServer/Info.h new file mode 100644 index 0000000000..d3a7ff45c6 --- /dev/null +++ b/Source/Tools/LinuxEmulation/GdbServer/Info.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +/* +$info$ +tags: glue|gdbserver +desc: Provides a gdb interface to the guest state +$end_info$ +*/ +#pragma once + +#include + +#include +#include + +namespace FEXCore::X86State { +enum X86Reg : uint32_t; +} + +namespace FEX::GDB::Info { +/** + * @brief Returns textual name of bit location from EFLAGs register. + * + * @param Bit Which bit of EFLAG to query + */ +const std::string_view& GetFlagName(unsigned Bit); + +/** + * @brief Returns the textual name of a GPR register + * + * @param Reg Index of the register to fetch + */ +std::string_view GetGRegName(unsigned Reg); + +/** + * @brief Fetches the thread's name + * + * @param PID The program id of the application + * @param ThreadID The thread id of the program + */ +fextl::string GetThreadName(uint32_t PID, uint32_t ThreadID); + +/** + * @brief Returns the GDB specific construct of OS describing XML. + */ +fextl::string BuildOSXML(); + +/** + * @brief Returns the GDB specific construct of target describing XML. + */ +fextl::string BuildTargetXML(); +} // namespace FEX::GDB::Info diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp index 18945947e8..2d9756e1ba 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp @@ -7,6 +7,7 @@ desc: Provides a gdb interface to the guest state */ #include "CodeLoader.h" +#include "GdbServer/Info.h" #include "LinuxSyscalls/NetStream.h" @@ -59,36 +60,6 @@ desc: Provides a gdb interface to the guest state namespace FEX { -constexpr std::array FlagNames = { - "CF", "", "PF", "", "AF", "", "ZF", "SF", "TF", "IF", "DF", "OF", "IOPL", "", "NT", "", "RF", "VM", "AC", "VIF", "VIP", "ID", -}; - -static const std::string_view& GetFlagName(unsigned Flag) { - return FlagNames[Flag]; -} - -static std::string_view const GetGRegName(unsigned Reg) { - switch (Reg) { - case FEXCore::X86State::REG_RAX: return "rax"; - case FEXCore::X86State::REG_RBX: return "rbx"; - case FEXCore::X86State::REG_RCX: return "rcx"; - case FEXCore::X86State::REG_RDX: return "rdx"; - case FEXCore::X86State::REG_RSP: return "rsp"; - case FEXCore::X86State::REG_RBP: return "rbp"; - case FEXCore::X86State::REG_RSI: return "rsi"; - case FEXCore::X86State::REG_RDI: return "rdi"; - case FEXCore::X86State::REG_R8: return "r8"; - case FEXCore::X86State::REG_R9: return "r9"; - case FEXCore::X86State::REG_R10: return "r10"; - case FEXCore::X86State::REG_R11: return "r11"; - case FEXCore::X86State::REG_R12: return "r12"; - case FEXCore::X86State::REG_R13: return "r13"; - case FEXCore::X86State::REG_R14: return "r14"; - case FEXCore::X86State::REG_R15: return "r15"; - default: FEX_UNREACHABLE; - } -} - #ifndef _WIN32 void GdbServer::Break(int signal) { std::lock_guard lk(sendMutex); @@ -192,13 +163,6 @@ static fextl::string encodeHex(std::string_view str) { return encodeHex(reinterpret_cast(str.data()), str.size()); } -static fextl::string getThreadName(uint32_t ThreadID) { - const auto ThreadFile = fextl::fmt::format("/proc/{}/task/{}/comm", getpid(), ThreadID); - fextl::string ThreadName; - FEXCore::FileLoading::LoadFile(ThreadName, ThreadFile); - return ThreadName; -} - // Packet parser // Takes a serial stream and reads a single packet // Un-escapes chars, checks the checksum and request a retransmit if it fails. @@ -431,146 +395,6 @@ GdbServer::HandledPacketType GdbServer::readReg(const fextl::string& packet) { return {"E00", HandledPacketType::TYPE_ACK}; } -fextl::string buildTargetXML() { - fextl::ostringstream xml; - - xml << "\n"; - xml << "\n"; - xml << "\n"; - xml << "i386:x86-64\n"; - xml << "GNU/Linux\n"; - xml << "\n"; - - xml << "\n"; - // flags register - for (int i = 0; i < 22; i++) { - auto name = GetFlagName(i); - if (name.empty()) { - continue; - } - xml << "\t\n"; - } - xml << "\n"; - - int32_t TargetSize {}; - auto reg = [&](std::string_view name, std::string_view type, int size) { - TargetSize += size; - xml << "" << std::endl; - }; - - // Register ordering. - // We want to just memcpy our x86 state to gdb, so we tell it the ordering. - - // GPRs - for (uint32_t i = 0; i < FEXCore::Core::CPUState::NUM_GPRS; i++) { - reg(GetGRegName(i), "int64", 64); - } - - reg("rip", "code_ptr", 64); - - reg("eflags", "fex_eflags", 32); - - // Fake registers which GDB requires, but we don't support; - // We stick them past the end of our cpu state. - - // non-userspace segment registers - reg("cs", "int32", 32); - reg("ss", "int32", 32); - reg("ds", "int32", 32); - reg("es", "int32", 32); - - reg("fs", "int32", 32); - reg("gs", "int32", 32); - - // x87 stack - for (int i = 0; i < 8; i++) { - reg(fextl::fmt::format("st{}", i), "i387_ext", 80); - } - - // x87 control - reg("fctrl", "int32", 32); - reg("fstat", "int32", 32); - reg("ftag", "int32", 32); - reg("fiseg", "int32", 32); - reg("fioff", "int32", 32); - reg("foseg", "int32", 32); - reg("fooff", "int32", 32); - reg("fop", "int32", 32); - - - xml << "\n"; - xml << "\n"; - xml << - R"( - - - - - - - - - - - - - - - )"; - - // SSE regs - for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; i++) { - reg(fextl::fmt::format("xmm{}", i), "vec128", 128); - } - - reg("mxcsr", "int", 32); - - xml << "\n"; - - xml << ""; - xml << - R"( - - - - - - - - - - - - - - - )"; - for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; i++) { - reg(fmt::format("ymm{}h", i), "vec128", 128); - } - xml << "\n"; - - xml << ""; - xml << std::flush; - - return xml.str(); -} - -fextl::string buildOSData() { - fextl::ostringstream xml; - - xml << "\n"; - - xml << "\n"; - xml << ""; - // XXX - xml << ""; - - xml << std::flush; - - return xml.str(); -} - void GdbServer::buildLibraryMap() { if (!LibraryMapChanged) { // No need to update @@ -706,7 +530,7 @@ GdbServer::HandledPacketType GdbServer::handleXfer(const fextl::string& packet) if (object == "features") { if (annex == "target.xml") { - return {encode(buildTargetXML()), HandledPacketType::TYPE_ACK}; + return {encode(GDB::Info::BuildTargetXML()), HandledPacketType::TYPE_ACK}; } return {"E00", HandledPacketType::TYPE_ACK}; @@ -721,7 +545,7 @@ GdbServer::HandledPacketType GdbServer::handleXfer(const fextl::string& packet) ss << "\n"; for (auto& Thread : *Threads) { // Thread id is in hex without 0x prefix - const auto ThreadName = getThreadName(Thread->ThreadInfo.TID); + const auto ThreadName = GDB::Info::GetThreadName(::getpid(), Thread->ThreadInfo.TID); ss << "ThreadInfo.TID << "\""; if (!ThreadName.empty()) { ss << " name=\"" << ThreadName << "\""; @@ -739,7 +563,7 @@ GdbServer::HandledPacketType GdbServer::handleXfer(const fextl::string& packet) if (object == "osdata") { if (offset == 0) { - OSDataString = buildOSData(); + OSDataString = GDB::Info::BuildOSXML(); } return {encode(OSDataString), HandledPacketType::TYPE_ACK}; } @@ -977,7 +801,7 @@ GdbServer::HandledPacketType GdbServer::handleQuery(const fextl::string& packet) ss.get(); // discard comma uint32_t ThreadID; ss >> std::hex >> ThreadID; - auto ThreadName = getThreadName(ThreadID); + auto ThreadName = GDB::Info::GetThreadName(::getpid(), ThreadID); return {encodeHex((unsigned char*)ThreadName.data(), ThreadName.size()), HandledPacketType::TYPE_ACK}; } if (match("qC")) { From a4565ce783477c2b5eff63ab68b72d566897e630 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Sun, 24 Nov 2024 15:42:53 -0800 Subject: [PATCH 02/11] GdbServer: Pass through FCW We have supported this for a while, just wasn't passed through gdbserver since it usually doesn't matter. --- Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp index 2d9756e1ba..00845992c9 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp @@ -368,8 +368,7 @@ GdbServer::HandledPacketType GdbServer::readReg(const fextl::string& packet) { 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 - uint32_t FCW = 0x37F; + uint32_t FCW = state.FCW; return {encodeHex((unsigned char*)(&FCW), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; } else if (addr == offsetof(GDBContextDefinition, fstat)) { uint32_t FSW {}; From ee69b9f650dd7690e3d00678ab3b60f1e569880e Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Sun, 24 Nov 2024 15:56:50 -0800 Subject: [PATCH 03/11] GdbServer: Reconstruct XMM/YMM registers using FEXCore helpers Previously this would have corrupted data in the upper 128-bits of the YMM register. --- .../LinuxSyscalls/GdbServer.cpp | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp index 00845992c9..5f1fa786bb 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp @@ -311,8 +311,7 @@ fextl::string GdbServer::readRegs() { memcpy(&GDB.mm[i], &state.mm[i], sizeof(GDB.mm[i])); } - // Currently unsupported - GDB.fctrl = 0x37F; + GDB.fctrl = state.FCW; GDB.fstat = static_cast(state.flags[FEXCore::X86State::X87FLAG_TOP_LOC]) << 11; GDB.fstat |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C0_LOC]) << 8; @@ -320,7 +319,14 @@ fextl::string GdbServer::readRegs() { GDB.fstat |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C2_LOC]) << 10; GDB.fstat |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C3_LOC]) << 14; - memcpy(&GDB.xmm, &state.xmm.avx.data, sizeof(GDB.xmm)); + __uint128_t XMM_Low[FEXCore::Core::CPUState::NUM_XMMS]; + __uint128_t YMM_High[FEXCore::Core::CPUState::NUM_XMMS]; + + CTX->ReconstructXMMRegisters(CurrentThread->Thread, XMM_Low, YMM_High); + for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; ++i) { + memcpy(&GDB.xmm[0], &XMM_Low[i], sizeof(__uint128_t)); + memcpy(&GDB.xmm[2], &YMM_High[i], sizeof(__uint128_t)); + } return encodeHex((unsigned char*)&GDB, sizeof(GDBContextDefinition)); } @@ -383,8 +389,16 @@ GdbServer::HandledPacketType GdbServer::readReg(const fextl::string& packet) { return {encodeHex((unsigned char*)(&Empty), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; } else if (addr >= offsetof(GDBContextDefinition, xmm[0][0]) && addr < offsetof(GDBContextDefinition, xmm[16][0])) { const auto XmmIndex = (addr - offsetof(GDBContextDefinition, xmm[0][0])) / FEXCore::Core::CPUState::XMM_AVX_REG_SIZE; - const auto* Data = (unsigned char*)&state.xmm.avx.data[XmmIndex][0]; - return {encodeHex(Data, FEXCore::Core::CPUState::XMM_AVX_REG_SIZE), HandledPacketType::TYPE_ACK}; + + __uint128_t XMM_Low[FEXCore::Core::CPUState::NUM_XMMS]; + __uint128_t YMM_High[FEXCore::Core::CPUState::NUM_XMMS]; + + CTX->ReconstructXMMRegisters(CurrentThread->Thread, XMM_Low, YMM_High); + uint64_t xmm[4]; + memcpy(&xmm[0], &XMM_Low[XmmIndex], sizeof(__uint128_t)); + memcpy(&xmm[2], &YMM_High[XmmIndex], sizeof(__uint128_t)); + + return {encodeHex(reinterpret_cast(&xmm), FEXCore::Core::CPUState::XMM_AVX_REG_SIZE), HandledPacketType::TYPE_ACK}; } else if (addr == offsetof(GDBContextDefinition, mxcsr)) { uint32_t Empty {}; return {encodeHex((unsigned char*)(&Empty), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; From 740ff60a71d9c5a12cc6993a13c524e4b0483b36 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Sun, 24 Nov 2024 17:39:20 -0800 Subject: [PATCH 04/11] GdbServer: Reorganize packet command handlers Makes these consistent in the handling and documents the commands in a way that is easier to parse while working on this. NFC --- .../LinuxSyscalls/GdbServer.cpp | 496 +++++++++++------- .../LinuxEmulation/LinuxSyscalls/GdbServer.h | 23 +- 2 files changed, 334 insertions(+), 185 deletions(-) diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp index 5f1fa786bb..07032705f1 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp @@ -331,83 +331,6 @@ fextl::string GdbServer::readRegs() { return encodeHex((unsigned char*)&GDB, sizeof(GDBContextDefinition)); } -GdbServer::HandledPacketType GdbServer::readReg(const fextl::string& packet) { - size_t addr; - auto ss = fextl::istringstream(packet); - ss.get(); // Drop first letter - ss >> std::hex >> addr; - - FEXCore::Core::CPUState state {}; - - auto Threads = SyscallHandler->TM.GetThreads(); - FEX::HLE::ThreadStateObject* CurrentThread {Threads->at(0)}; - bool Found = false; - - for (auto& Thread : *Threads) { - if (Thread->ThreadInfo.TID != CurrentDebuggingThread) { - continue; - } - memcpy(&state, Thread->Thread->CurrentFrame, sizeof(state)); - CurrentThread = Thread; - Found = true; - break; - } - - if (!Found) { - // If set to an invalid thread then just get the parent thread ID - memcpy(&state, CurrentThread->Thread->CurrentFrame, sizeof(state)); - } - - - if (addr >= offsetof(GDBContextDefinition, gregs[0]) && addr < offsetof(GDBContextDefinition, gregs[16])) { - return {encodeHex((unsigned char*)(&state.gregs[addr / sizeof(uint64_t)]), sizeof(uint64_t)), HandledPacketType::TYPE_ACK}; - } else if (addr == offsetof(GDBContextDefinition, rip)) { - return {encodeHex((unsigned char*)(&state.rip), sizeof(uint64_t)), HandledPacketType::TYPE_ACK}; - } else if (addr == offsetof(GDBContextDefinition, eflags)) { - uint32_t eflags = CTX->ReconstructCompactedEFLAGS(CurrentThread->Thread, false, nullptr, 0); - - return {encodeHex((unsigned char*)(&eflags), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; - } else if (addr >= offsetof(GDBContextDefinition, cs) && addr < offsetof(GDBContextDefinition, mm[0])) { - uint32_t Empty {}; - return {encodeHex((unsigned char*)(&Empty), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; - } else if (addr >= offsetof(GDBContextDefinition, mm[0]) && addr < offsetof(GDBContextDefinition, mm[8])) { - return {encodeHex((unsigned char*)(&state.mm[(addr - offsetof(GDBContextDefinition, mm[0])) / sizeof(X80Float)]), sizeof(X80Float)), - HandledPacketType::TYPE_ACK}; - } else if (addr == offsetof(GDBContextDefinition, fctrl)) { - uint32_t FCW = state.FCW; - return {encodeHex((unsigned char*)(&FCW), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; - } else if (addr == offsetof(GDBContextDefinition, fstat)) { - uint32_t FSW {}; - FSW = static_cast(state.flags[FEXCore::X86State::X87FLAG_TOP_LOC]) << 11; - FSW |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C0_LOC]) << 8; - FSW |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C1_LOC]) << 9; - FSW |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C2_LOC]) << 10; - FSW |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C3_LOC]) << 14; - return {encodeHex((unsigned char*)(&FSW), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; - } else if (addr >= offsetof(GDBContextDefinition, dummies[0]) && addr < offsetof(GDBContextDefinition, dummies[6])) { - uint32_t Empty {}; - return {encodeHex((unsigned char*)(&Empty), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; - } else if (addr >= offsetof(GDBContextDefinition, xmm[0][0]) && addr < offsetof(GDBContextDefinition, xmm[16][0])) { - const auto XmmIndex = (addr - offsetof(GDBContextDefinition, xmm[0][0])) / FEXCore::Core::CPUState::XMM_AVX_REG_SIZE; - - __uint128_t XMM_Low[FEXCore::Core::CPUState::NUM_XMMS]; - __uint128_t YMM_High[FEXCore::Core::CPUState::NUM_XMMS]; - - CTX->ReconstructXMMRegisters(CurrentThread->Thread, XMM_Low, YMM_High); - uint64_t xmm[4]; - memcpy(&xmm[0], &XMM_Low[XmmIndex], sizeof(__uint128_t)); - memcpy(&xmm[2], &YMM_High[XmmIndex], sizeof(__uint128_t)); - - return {encodeHex(reinterpret_cast(&xmm), FEXCore::Core::CPUState::XMM_AVX_REG_SIZE), HandledPacketType::TYPE_ACK}; - } else if (addr == offsetof(GDBContextDefinition, mxcsr)) { - uint32_t Empty {}; - return {encodeHex((unsigned char*)(&Empty), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; - } - - LogMan::Msg::EFmt("Unknown GDB register 0x{:x}", addr); - return {"E00", HandledPacketType::TYPE_ACK}; -} - void GdbServer::buildLibraryMap() { if (!LibraryMapChanged) { // No need to update @@ -651,7 +574,104 @@ GdbServer::HandledPacketType GdbServer::handleProgramOffsets() { return {std::move(str), HandledPacketType::TYPE_ACK}; } -GdbServer::HandledPacketType GdbServer::handleMemory(const fextl::string& packet) { +GdbServer::HandledPacketType GdbServer::ThreadAction(char action, uint32_t tid) { + switch (action) { + case 'c': { + SyscallHandler->TM.Run(); + ThreadBreakEvent.NotifyAll(); + SyscallHandler->TM.WaitForThreadsToRun(); + return {"", HandledPacketType::TYPE_ONLYACK}; + } + case 's': { + SyscallHandler->TM.Step(); + SendPacketPair({"OK", HandledPacketType::TYPE_ACK}); + fextl::string str = fextl::fmt::format("T05thread:{:02x};", getpid()); + if (LibraryMapChanged) { + // If libraries have changed then let gdb know + str += "library:1;"; + } + + SendPacketPair({std::move(str), HandledPacketType::TYPE_ACK}); + return {"OK", HandledPacketType::TYPE_ACK}; + } + case 't': + // This thread isn't part of the thread pool + SyscallHandler->TM.Stop(); + return {"OK", HandledPacketType::TYPE_ACK}; + default: return {"E00", HandledPacketType::TYPE_ACK}; + } +} + +// Command handlers +GdbServer::HandledPacketType GdbServer::CommandEnableExtendedMode(const fextl::string& packet) { + return {"OK", HandledPacketType::TYPE_ACK}; +} + +GdbServer::HandledPacketType GdbServer::CommandQueryHalted(const fextl::string& packet) { + // Indicates the reason that the thread has stopped + // Behaviour changes if the target is in non-stop mode + // Binja doesn't support S response here + fextl::string str = fextl::fmt::format("T00thread:{:x};", getpid()); + return {std::move(str), HandledPacketType::TYPE_ACK}; +} + +GdbServer::HandledPacketType GdbServer::CommandContinue(const fextl::string& packet) { + // Continue + return ThreadAction('c', 0); +} + +GdbServer::HandledPacketType GdbServer::CommandDetach(const fextl::string& packet) { + // Detach + // Ensure the threads are back in running state on detach + SyscallHandler->TM.Run(); + SyscallHandler->TM.WaitForThreadsToRun(); + return {"OK", HandledPacketType::TYPE_ACK}; +} + +GdbServer::HandledPacketType GdbServer::CommandReadRegisters(const fextl::string& packet) { + // We might be running while we try reading + // Pause up front + SyscallHandler->TM.Pause(); + return {readRegs(), HandledPacketType::TYPE_ACK}; +} + +GdbServer::HandledPacketType GdbServer::CommandThreadOp(const fextl::string& packet) { + const auto match = [&](const char* str) -> bool { + return packet.rfind(str, 0) == 0; + }; + + if (match("Hc")) { + // Sets thread to this ID for stepping + // This is deprecated and vCont should be used instead + auto ss = fextl::istringstream(packet); + ss.seekg(strlen("Hc")); + ss >> std::hex >> CurrentDebuggingThread; + + SyscallHandler->TM.Pause(); + return {"OK", HandledPacketType::TYPE_ACK}; + } + + if (match("Hg")) { + // Sets thread for "other" operations + auto ss = fextl::istringstream(packet); + ss.seekg(strlen("Hg")); + ss >> std::hex >> CurrentDebuggingThread; + + // This must return quick otherwise IDA complains + SyscallHandler->TM.Pause(); + return {"OK", HandledPacketType::TYPE_ACK}; + } + + return {"", HandledPacketType::TYPE_UNKNOWN}; +} + +GdbServer::HandledPacketType GdbServer::CommandKill(const fextl::string& packet) { + SyscallHandler->TM.Stop(); + SyscallHandler->TM.WaitForIdle(); // Block until exit + return {"", HandledPacketType::TYPE_NONE}; +} + +GdbServer::HandledPacketType GdbServer::CommandMemory(const fextl::string& packet) { bool write; size_t addr; size_t length; @@ -691,7 +711,84 @@ GdbServer::HandledPacketType GdbServer::handleMemory(const fextl::string& packet } } -GdbServer::HandledPacketType GdbServer::handleQuery(const fextl::string& packet) { +GdbServer::HandledPacketType GdbServer::CommandReadReg(const fextl::string& packet) { + size_t addr; + auto ss = fextl::istringstream(packet); + ss.get(); // Drop first letter + ss >> std::hex >> addr; + + FEXCore::Core::CPUState state {}; + + auto Threads = SyscallHandler->TM.GetThreads(); + FEX::HLE::ThreadStateObject* CurrentThread {Threads->at(0)}; + bool Found = false; + + for (auto& Thread : *Threads) { + if (Thread->ThreadInfo.TID != CurrentDebuggingThread) { + continue; + } + memcpy(&state, Thread->Thread->CurrentFrame, sizeof(state)); + CurrentThread = Thread; + Found = true; + break; + } + + if (!Found) { + // If set to an invalid thread then just get the parent thread ID + memcpy(&state, CurrentThread->Thread->CurrentFrame, sizeof(state)); + } + + + if (addr >= offsetof(GDBContextDefinition, gregs[0]) && addr < offsetof(GDBContextDefinition, gregs[16])) { + return {encodeHex((unsigned char*)(&state.gregs[addr / sizeof(uint64_t)]), sizeof(uint64_t)), HandledPacketType::TYPE_ACK}; + } else if (addr == offsetof(GDBContextDefinition, rip)) { + return {encodeHex((unsigned char*)(&state.rip), sizeof(uint64_t)), HandledPacketType::TYPE_ACK}; + } else if (addr == offsetof(GDBContextDefinition, eflags)) { + uint32_t eflags = CTX->ReconstructCompactedEFLAGS(CurrentThread->Thread, false, nullptr, 0); + + return {encodeHex((unsigned char*)(&eflags), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; + } else if (addr >= offsetof(GDBContextDefinition, cs) && addr < offsetof(GDBContextDefinition, mm[0])) { + uint32_t Empty {}; + return {encodeHex((unsigned char*)(&Empty), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; + } else if (addr >= offsetof(GDBContextDefinition, mm[0]) && addr < offsetof(GDBContextDefinition, mm[8])) { + return {encodeHex((unsigned char*)(&state.mm[(addr - offsetof(GDBContextDefinition, mm[0])) / sizeof(X80Float)]), sizeof(X80Float)), + HandledPacketType::TYPE_ACK}; + } else if (addr == offsetof(GDBContextDefinition, fctrl)) { + uint32_t FCW = state.FCW; + return {encodeHex((unsigned char*)(&FCW), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; + } else if (addr == offsetof(GDBContextDefinition, fstat)) { + uint32_t FSW {}; + FSW = static_cast(state.flags[FEXCore::X86State::X87FLAG_TOP_LOC]) << 11; + FSW |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C0_LOC]) << 8; + FSW |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C1_LOC]) << 9; + FSW |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C2_LOC]) << 10; + FSW |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C3_LOC]) << 14; + return {encodeHex((unsigned char*)(&FSW), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; + } else if (addr >= offsetof(GDBContextDefinition, dummies[0]) && addr < offsetof(GDBContextDefinition, dummies[6])) { + uint32_t Empty {}; + return {encodeHex((unsigned char*)(&Empty), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; + } else if (addr >= offsetof(GDBContextDefinition, xmm[0][0]) && addr < offsetof(GDBContextDefinition, xmm[16][0])) { + const auto XmmIndex = (addr - offsetof(GDBContextDefinition, xmm[0][0])) / FEXCore::Core::CPUState::XMM_AVX_REG_SIZE; + + __uint128_t XMM_Low[FEXCore::Core::CPUState::NUM_XMMS]; + __uint128_t YMM_High[FEXCore::Core::CPUState::NUM_XMMS]; + + CTX->ReconstructXMMRegisters(CurrentThread->Thread, XMM_Low, YMM_High); + uint64_t xmm[4]; + memcpy(&xmm[0], &XMM_Low[XmmIndex], sizeof(__uint128_t)); + memcpy(&xmm[2], &YMM_High[XmmIndex], sizeof(__uint128_t)); + + return {encodeHex(reinterpret_cast(&xmm), FEXCore::Core::CPUState::XMM_AVX_REG_SIZE), HandledPacketType::TYPE_ACK}; + } else if (addr == offsetof(GDBContextDefinition, mxcsr)) { + uint32_t Empty {}; + return {encodeHex((unsigned char*)(&Empty), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; + } + + LogMan::Msg::EFmt("Unknown GDB register 0x{:x}", addr); + return {"E00", HandledPacketType::TYPE_ACK}; +} + +GdbServer::HandledPacketType GdbServer::CommandQuery(const fextl::string& packet) { const auto match = [&](const char* str) -> bool { return packet.rfind(str, 0) == 0; }; @@ -920,36 +1017,15 @@ GdbServer::HandledPacketType GdbServer::handleQuery(const fextl::string& packet) return {"", HandledPacketType::TYPE_UNKNOWN}; } +GdbServer::HandledPacketType GdbServer::CommandSingleStep(const fextl::string& packet) { + return ThreadAction('s', 0); +} -GdbServer::HandledPacketType GdbServer::ThreadAction(char action, uint32_t tid) { - switch (action) { - case 'c': { - SyscallHandler->TM.Run(); - ThreadBreakEvent.NotifyAll(); - SyscallHandler->TM.WaitForThreadsToRun(); - return {"", HandledPacketType::TYPE_ONLYACK}; - } - case 's': { - SyscallHandler->TM.Step(); - SendPacketPair({"OK", HandledPacketType::TYPE_ACK}); - fextl::string str = fextl::fmt::format("T05thread:{:02x};", getpid()); - if (LibraryMapChanged) { - // If libraries have changed then let gdb know - str += "library:1;"; - } - - SendPacketPair({std::move(str), HandledPacketType::TYPE_ACK}); - return {"OK", HandledPacketType::TYPE_ACK}; - } - case 't': - // This thread isn't part of the thread pool - SyscallHandler->TM.Stop(); - return {"OK", HandledPacketType::TYPE_ACK}; - default: return {"E00", HandledPacketType::TYPE_ACK}; - } +GdbServer::HandledPacketType GdbServer::CommandQueryThreadAlive(const fextl::string& packet) { + return {"OK", HandledPacketType::TYPE_ACK}; } -GdbServer::HandledPacketType GdbServer::handleV(const fextl::string& packet) { +GdbServer::HandledPacketType GdbServer::CommandMultiLetterV(const fextl::string& packet) { const auto match = [&](const fextl::string& str) -> std::optional { if (packet.rfind(str, 0) == 0) { auto ss = fextl::istringstream(packet); @@ -1044,37 +1120,7 @@ GdbServer::HandledPacketType GdbServer::handleV(const fextl::string& packet) { return {"", HandledPacketType::TYPE_ACK}; } -GdbServer::HandledPacketType GdbServer::handleThreadOp(const fextl::string& packet) { - const auto match = [&](const char* str) -> bool { - return packet.rfind(str, 0) == 0; - }; - - if (match("Hc")) { - // Sets thread to this ID for stepping - // This is deprecated and vCont should be used instead - auto ss = fextl::istringstream(packet); - ss.seekg(fextl::string("Hc").size()); - ss >> std::hex >> CurrentDebuggingThread; - - SyscallHandler->TM.Pause(); - return {"OK", HandledPacketType::TYPE_ACK}; - } - - if (match("Hg")) { - // Sets thread for "other" operations - auto ss = fextl::istringstream(packet); - ss.seekg(std::string_view("Hg").size()); - ss >> std::hex >> CurrentDebuggingThread; - - // This must return quick otherwise IDA complains - SyscallHandler->TM.Pause(); - return {"OK", HandledPacketType::TYPE_ACK}; - } - - return {"", HandledPacketType::TYPE_UNKNOWN}; -} - -GdbServer::HandledPacketType GdbServer::handleBreakpoint(const fextl::string& packet) { +GdbServer::HandledPacketType GdbServer::CommandBreakpoint(const fextl::string& packet) { auto ss = fextl::istringstream(packet); // Don't do anything with set breakpoints yet @@ -1091,50 +1137,142 @@ GdbServer::HandledPacketType GdbServer::handleBreakpoint(const fextl::string& pa return {"OK", HandledPacketType::TYPE_ACK}; } +GdbServer::HandledPacketType GdbServer::CommandUnknown(const fextl::string& packet) { + return {"", HandledPacketType::TYPE_UNKNOWN}; +} + GdbServer::HandledPacketType GdbServer::ProcessPacket(const fextl::string& packet) { + // Packet commands list: https://sourceware.org/gdb/current/onlinedocs/gdb.html/Packets.html#Packets + switch (packet[0]) { - case '?': { - // Indicates the reason that the thread has stopped - // Behaviour changes if the target is in non-stop mode - // Binja doesn't support S response here - fextl::string str = fextl::fmt::format("T00thread:{:x};", getpid()); - return {std::move(str), HandledPacketType::TYPE_ACK}; - } - case 'c': - // Continue - return ThreadAction('c', 0); - case 'D': - // Detach - // Ensure the threads are back in running state on detach - SyscallHandler->TM.Run(); - SyscallHandler->TM.WaitForThreadsToRun(); - return {"OK", HandledPacketType::TYPE_ACK}; - case 'g': - // We might be running while we try reading - // Pause up front - SyscallHandler->TM.Pause(); - return {readRegs(), HandledPacketType::TYPE_ACK}; - case 'p': return readReg(packet); + // Command: $! + // - Desc: Enable extended mode + // - Args: + case '!': return CommandEnableExtendedMode(packet); + // Command: $? + // - Desc: Sent on connection first established to query the reason the target halted. + case '?': return CommandQueryHalted(packet); + // Command: $A + // - Desc: Initialized argv[] array passed in to the program. + // - Args: arglen,argnum,arg,... + case 'A': return CommandUnknown(packet); + // Command: $b + // - Desc: Change the serial line speed to baud + // - Args: baud + // - Deprecated: Behaviour isn't well-defined. + case 'b': return CommandUnknown(packet); + // Command: $B + // - Desc: Set or clear a breadpoint at address + // - Args: addr,mode + // - Deprecated: Use $Z and $z instead. + case 'B': return CommandUnknown(packet); + // Command: $c + // - Desc: Continue execution of process + // - Args: [addr] + // - Deprecated: See $vCont for multi-threaded support. + case 'c': return CommandContinue(packet); + // Command: $C + // - Desc: Continue execution of process with signal + // - Args: sig[;addr] + // - Deprecated: See $vCont for multi-threaded support. + case 'C': return CommandUnknown(packet); + // Command: $d + // - Desc: Toggle debug flag + // - Args: + // - Deprecated: Use $q or $Q instead. + case 'd': return CommandUnknown(packet); + // Command: $D + // - Desc: Detach GDB from the remote system + // - Args: [;pid] + case 'D': return CommandDetach(packet); + // Command: $F + // - Desc: A reply from GDB to the `F` packet sent by the target. Part of the File-I/O protocol. + // - Args: RC,EE,CF;XX + case 'F': return CommandUnknown(packet); + // Command: $g + // - Desc: Read general registers + // - Args: + case 'g': return CommandReadRegisters(packet); + // Command: $G + // - Desc: Write general registers + // - Args: XX... + case 'G': return CommandUnknown(packet); + // Command: $H + // - Desc: Sets thread for subsequent operations + // - Args: op thread-id + case 'H': return CommandThreadOp(packet); + // Command: $i + // - Desc: Step the remote target by a single clock cycle + // - Args: [addr[,nnn]] + case 'i': return CommandUnknown(packet); + // Command: $I + // - Desc: Signal, then cycle step + // - Args: + case 'I': return CommandUnknown(packet); + // Command: $k + // - Desc: kill process + case 'k': return CommandKill(packet); + // Command: $m + // - Desc: Read addressable memory + // - Args: addr length + case 'm': + // Command: $M + // - Desc: Write addressable memory + // - Args: addr length + case 'M': return CommandMemory(packet); + // Command: $p + // - Desc: Read the value of a register + // - Args: index + case 'p': return CommandReadReg(packet); + // Command: $q + // - Desc: General query fetching + // - Args: Name params... case 'q': - case 'Q': return handleQuery(packet); - case 'v': return handleV(packet); - case 'm': // Memory read - case 'M': // Memory write - return handleMemory(packet); - case 'H': // Sets thread for subsequent operations - return handleThreadOp(packet); - case '!': // Enable extended mode - case 'T': // Is a thread alive? - return {"OK", HandledPacketType::TYPE_ACK}; - case 's': // Step - return ThreadAction('s', 0); - case 'z': // Remove breakpoint or watchpoint - case 'Z': // Inserts breakpoint or watchpoint - return handleBreakpoint(packet); - case 'k': // Kill the process - SyscallHandler->TM.Stop(); - SyscallHandler->TM.WaitForIdle(); // Block until exit - return {"", HandledPacketType::TYPE_NONE}; + // Command: $Q + // - Desc: General query setting + // - Args: Name params... + case 'Q': return CommandQuery(packet); + // Command: $r + // - Desc: Reset the entire system + // - Args: + // - Deprecated: Use $R instead. + case 'r': return CommandUnknown(packet); + // Command: $R + // - Desc: Restart the program beging debugged + // - Args: XX + case 'R': return CommandUnknown(packet); + // Command: $s + // - Desc: Single step + // - Args: [addr] + case 's': return CommandSingleStep(packet); + // Command: $S + // - Desc: Step with Signal + // - Args: sig[;addr] + // - Deprecated: See $vCont for multi-threaded support. + case 'S': return CommandUnknown(packet); + // Command: $t + // - Desc: Search backwards started at address with pattern and mask. + // - Args: addr:PP,MM + case 't': return CommandUnknown(packet); + // Command: $T + // - Desc: Find out if the thread is alive + // - Args: thread-id + case 'T': return CommandQueryThreadAlive(packet); + // Command: $v + // - Desc: Multi-letter command + case 'v': return CommandMultiLetterV(packet); + // Command: $X + // - Desc: Write data to memory + // - Args: addr,length:XX... + case 'X': return CommandUnknown(packet); + // Command: $z + // - Desc: Insert a type of breakpoint or watchpoint + // - Args: type,addr,kind + case 'z': + // Command: $Z + // - Desc: Remove a type of breakpoint or watchpoint + // - Args: type,addr,kind + case 'Z': return CommandBreakpoint(packet); default: return {"", HandledPacketType::TYPE_UNKNOWN}; } } @@ -1294,7 +1432,7 @@ void GdbServer::OpenListenSocket() { listen(ListenSocket, 1); LogMan::Msg::IFmt("[GdbServer] Waiting for connection on {}", GdbUnixSocketPath); - LogMan::Msg::IFmt("[GdbServer] gdb-multiarch -ex \"target extended-remote {}\"", GdbUnixSocketPath); + LogMan::Msg::IFmt("[GdbServer] gdb-multiarch -ex \"set debug remote 1\" -ex \"target extended-remote {}\"", GdbUnixSocketPath); } void GdbServer::CloseListenSocket() { diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h index 0fb3ac0e72..7749e34cf1 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h @@ -70,18 +70,29 @@ class GdbServer { void SendPacketPair(const HandledPacketType& packetPair); HandledPacketType ProcessPacket(const fextl::string& packet); - HandledPacketType handleQuery(const fextl::string& packet); HandledPacketType handleXfer(const fextl::string& packet); - HandledPacketType handleMemory(const fextl::string& packet); - HandledPacketType handleV(const fextl::string& packet); - HandledPacketType handleThreadOp(const fextl::string& packet); - HandledPacketType handleBreakpoint(const fextl::string& packet); HandledPacketType handleProgramOffsets(); HandledPacketType ThreadAction(char action, uint32_t tid); + // Command handlers + HandledPacketType CommandEnableExtendedMode(const fextl::string& packet); + HandledPacketType CommandQueryHalted(const fextl::string& packet); + HandledPacketType CommandContinue(const fextl::string& packet); + HandledPacketType CommandDetach(const fextl::string& packet); + HandledPacketType CommandReadRegisters(const fextl::string& packet); + HandledPacketType CommandThreadOp(const fextl::string& packet); + HandledPacketType CommandKill(const fextl::string& packet); + HandledPacketType CommandMemory(const fextl::string& packet); + HandledPacketType CommandReadReg(const fextl::string& packet); + HandledPacketType CommandQuery(const fextl::string& packet); + HandledPacketType CommandSingleStep(const fextl::string& packet); + HandledPacketType CommandQueryThreadAlive(const fextl::string& packet); + HandledPacketType CommandMultiLetterV(const fextl::string& packet); + HandledPacketType CommandBreakpoint(const fextl::string& packet); + HandledPacketType CommandUnknown(const fextl::string& packet); + fextl::string readRegs(); - HandledPacketType readReg(const fextl::string& packet); FEXCore::Context::Context* CTX; FEX::HLE::SyscallHandler* const SyscallHandler; From 38c834e7315fb114ff14702291c971c3175a8100 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Sun, 24 Nov 2024 22:27:31 -0800 Subject: [PATCH 05/11] FEXCore: Removes global StartPaused check for gdb This doesn't behave properly anymore now that thread management was moved to the frontend. --- FEXCore/Source/Interface/Context/Context.h | 1 - FEXCore/Source/Interface/Core/Core.cpp | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/FEXCore/Source/Interface/Context/Context.h b/FEXCore/Source/Interface/Context/Context.h index b2b154d04c..234af2ac3c 100644 --- a/FEXCore/Source/Interface/Context/Context.h +++ b/FEXCore/Source/Interface/Context/Context.h @@ -386,7 +386,6 @@ class ContextImpl final : public FEXCore::Context::Context { IR::AOTIRCaptureCache IRCaptureCache; fextl::unique_ptr CodeObjectCacheService; - bool StartPaused = false; bool IsMemoryShared = false; bool SupportsHardwareTSO = false; bool AtomicTSOEmulationEnabled = true; diff --git a/FEXCore/Source/Interface/Core/Core.cpp b/FEXCore/Source/Interface/Core/Core.cpp index 34f9c79098..1027ca02b2 100644 --- a/FEXCore/Source/Interface/Core/Core.cpp +++ b/FEXCore/Source/Interface/Core/Core.cpp @@ -354,8 +354,6 @@ bool ContextImpl::InitCore() { if (Config.GdbServer) { // If gdbserver is enabled then this needs to be enabled. Config.NeedsPendingInterruptFaultCheck = true; - // FEX needs to start paused when gdb is enabled. - StartPaused = true; } return true; @@ -894,7 +892,7 @@ void ContextImpl::ExecutionThread(FEXCore::Core::InternalThreadState* Thread) { // Now notify the thread that we are initialized Thread->ThreadWaiting.NotifyAll(); - if (StartPaused || Thread->StartPaused) { + if (Thread->StartPaused) { // Parent thread doesn't need to wait to run Thread->StartRunning.Wait(); } From 71fe9aee21ef696bfb59a31c2b910f67b0c61dbb Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Sun, 24 Nov 2024 22:29:25 -0800 Subject: [PATCH 06/11] GdbServer: Fixes thread name setting When parsing `comm`, by default it will have a newline which breaks gdb in some cases. Strip out the whitespace to fix that issue. --- Source/Tools/LinuxEmulation/GdbServer/Info.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Tools/LinuxEmulation/GdbServer/Info.cpp b/Source/Tools/LinuxEmulation/GdbServer/Info.cpp index 31b1403960..3352ee8154 100644 --- a/Source/Tools/LinuxEmulation/GdbServer/Info.cpp +++ b/Source/Tools/LinuxEmulation/GdbServer/Info.cpp @@ -8,6 +8,8 @@ desc: Provides a gdb interface to the guest state #include "GdbServer/Info.h" +#include + #include #include #include @@ -55,6 +57,8 @@ fextl::string GetThreadName(uint32_t PID, uint32_t ThreadID) { const auto ThreadFile = fextl::fmt::format("/proc/{}/task/{}/comm", PID, ThreadID); fextl::string ThreadName; FEXCore::FileLoading::LoadFile(ThreadName, ThreadFile); + // Trim out the potential newline, breaks GDB if it exists. + FEX::StringUtil::trim(ThreadName); return ThreadName; } From 811ea093b5edb52f9212180cbb9060f7a9ea475c Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Sun, 24 Nov 2024 23:01:24 -0800 Subject: [PATCH 07/11] GdbServer: Split out qXfer handlers NFC, just making this easier to track for me. --- .../LinuxSyscalls/GdbServer.cpp | 200 ++++++++++-------- .../LinuxEmulation/LinuxSyscalls/GdbServer.h | 24 ++- 2 files changed, 137 insertions(+), 87 deletions(-) diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp index 07032705f1..e2e4dbfb55 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp @@ -408,11 +408,100 @@ void GdbServer::buildLibraryMap() { LibraryMapChanged = false; } +// Binary data transfer handlers + +GdbServer::HandledPacketType GdbServer::XferCommandExecFile(const fextl::string& annex, int offset, int length) { + int annex_pid; + if (annex.empty()) { + annex_pid = getpid(); + } else { + auto ss_pid = fextl::istringstream(annex); + ss_pid >> std::hex >> annex_pid; + } + + if (annex_pid == getpid()) { + return {EncodeXferString(Filename(), offset, length), HandledPacketType::TYPE_ACK}; + } + + return {"E00", HandledPacketType::TYPE_ACK}; +} + +GdbServer::HandledPacketType GdbServer::XferCommandFeatures(const fextl::string& annex, int offset, int length) { + if (annex == "target.xml") { + return {EncodeXferString(GDB::Info::BuildTargetXML(), offset, length), HandledPacketType::TYPE_ACK}; + } + + return {"E00", HandledPacketType::TYPE_ACK}; +} + +GdbServer::HandledPacketType GdbServer::XferCommandThreads(const fextl::string& annex, int offset, int length) { + if (offset == 0) { + auto Threads = SyscallHandler->TM.GetThreads(); + + ThreadString.clear(); + fextl::ostringstream ss; + ss << "\n"; + for (auto& Thread : *Threads) { + // Thread id is in hex without 0x prefix + const auto ThreadName = GDB::Info::GetThreadName(::getpid(), Thread->ThreadInfo.TID); + ss << "ThreadInfo.TID << "\""; + if (!ThreadName.empty()) { + ss << " name=\"" << ThreadName << "\""; + } + ss << "/>\n"; + } + + ss << "\n"; + ss << std::flush; + ThreadString = ss.str(); + } + + return {EncodeXferString(ThreadString, offset, length), HandledPacketType::TYPE_ACK}; +} + +GdbServer::HandledPacketType GdbServer::XferCommandOSData(const fextl::string& annex, int offset, int length) { + if (offset == 0) { + OSDataString = GDB::Info::BuildOSXML(); + } + return {EncodeXferString(OSDataString, offset, length), HandledPacketType::TYPE_ACK}; +} + +GdbServer::HandledPacketType GdbServer::XferCommandLibraries(const fextl::string& annex, int offset, int length) { + if (offset == 0) { + // Attempt to rebuild when reading from zero + buildLibraryMap(); + } + return {EncodeXferString(LibraryMapString, offset, length), HandledPacketType::TYPE_ACK}; +} + +GdbServer::HandledPacketType GdbServer::XferCommandAuxv(const fextl::string& annex, int offset, int length) { + auto CodeLoader = SyscallHandler->GetCodeLoader(); + uint64_t auxv_ptr, auxv_size; + CodeLoader->GetAuxv(auxv_ptr, auxv_size); + fextl::string data; + if (Is64BitMode()) { + data.resize(auxv_size); + memcpy(data.data(), reinterpret_cast(auxv_ptr), data.size()); + } else { + // We need to transcode from 32-bit auxv_t to 64-bit + data.resize(auxv_size / sizeof(Elf32_auxv_t) * sizeof(Elf64_auxv_t)); + size_t NumAuxv = auxv_size / sizeof(Elf32_auxv_t); + for (size_t i = 0; i < NumAuxv; ++i) { + Elf32_auxv_t* auxv = reinterpret_cast(auxv_ptr + i * sizeof(Elf32_auxv_t)); + Elf64_auxv_t tmp; + tmp.a_type = auxv->a_type; + tmp.a_un.a_val = auxv->a_un.a_val; + memcpy(data.data() + i * sizeof(Elf64_auxv_t), &tmp, sizeof(Elf64_auxv_t)); + } + } + + return {EncodeXferString(data, offset, length), HandledPacketType::TYPE_ACK}; +} + GdbServer::HandledPacketType GdbServer::handleXfer(const fextl::string& packet) { fextl::string object; fextl::string rw; fextl::string annex; - int annex_pid; int offset; int length; @@ -426,12 +515,7 @@ GdbServer::HandledPacketType GdbServer::handleXfer(const fextl::string& packet) std::getline(ss, object, ':'); std::getline(ss, rw, ':'); std::getline(ss, annex, ':'); - if (annex == "") { - annex_pid = getpid(); - } else { - auto ss_pid = fextl::istringstream(annex); - ss_pid >> std::hex >> annex_pid; - } + ss >> std::hex >> offset; ss.get(expectComma); ss >> std::hex >> length; @@ -442,98 +526,42 @@ GdbServer::HandledPacketType GdbServer::handleXfer(const fextl::string& packet) } } - // Lambda to correctly encode any reply - auto encode = [&](fextl::string data) -> fextl::string { - if (offset == data.size()) { - return "l"; - } - if (offset >= data.size()) { - return "E34"; // ERANGE - } - if ((data.size() - offset) > length) { - return "m" + data.substr(offset, length); - } - return "l" + data.substr(offset); - }; + // Specific object documentation: https://sourceware.org/gdb/current/onlinedocs/gdb.html/General-Query-Packets.html#qXfer-read + if (object == "auxv") { + return XferCommandAuxv(annex, offset, length); + } - if (object == "exec-file") { - if (annex_pid == getpid()) { - return {encode(Filename()), HandledPacketType::TYPE_ACK}; - } + // btrace + // btrace-conf - return {"E00", HandledPacketType::TYPE_ACK}; + if (object == "exec-file") { + return XferCommandExecFile(annex, offset, length); } if (object == "features") { - if (annex == "target.xml") { - return {encode(GDB::Info::BuildTargetXML()), HandledPacketType::TYPE_ACK}; - } - - return {"E00", HandledPacketType::TYPE_ACK}; + return XferCommandFeatures(annex, offset, length); } - if (object == "threads") { - if (offset == 0) { - auto Threads = SyscallHandler->TM.GetThreads(); - - ThreadString.clear(); - fextl::ostringstream ss; - ss << "\n"; - for (auto& Thread : *Threads) { - // Thread id is in hex without 0x prefix - const auto ThreadName = GDB::Info::GetThreadName(::getpid(), Thread->ThreadInfo.TID); - ss << "ThreadInfo.TID << "\""; - if (!ThreadName.empty()) { - ss << " name=\"" << ThreadName << "\""; - } - ss << "/>\n"; - } - - ss << "\n"; - ss << std::flush; - ThreadString = ss.str(); - } - - return {encode(ThreadString), HandledPacketType::TYPE_ACK}; + if (object == "libraries") { + return XferCommandLibraries(annex, offset, length); } - if (object == "osdata") { - if (offset == 0) { - OSDataString = GDB::Info::BuildOSXML(); - } - return {encode(OSDataString), HandledPacketType::TYPE_ACK}; - } + // libraries-svr4 + // memory-map + // sdata + // siginfo:read + // siginfo:write - if (object == "libraries") { - if (offset == 0) { - // Attempt to rebuild when reading from zero - buildLibraryMap(); - } - return {encode(LibraryMapString), HandledPacketType::TYPE_ACK}; + if (object == "threads") { + return XferCommandThreads(annex, offset, length); } - if (object == "auxv") { - auto CodeLoader = SyscallHandler->GetCodeLoader(); - uint64_t auxv_ptr, auxv_size; - CodeLoader->GetAuxv(auxv_ptr, auxv_size); - fextl::string data; - if (Is64BitMode()) { - data.resize(auxv_size); - memcpy(data.data(), reinterpret_cast(auxv_ptr), data.size()); - } else { - // We need to transcode from 32-bit auxv_t to 64-bit - data.resize(auxv_size / sizeof(Elf32_auxv_t) * sizeof(Elf64_auxv_t)); - size_t NumAuxv = auxv_size / sizeof(Elf32_auxv_t); - for (size_t i = 0; i < NumAuxv; ++i) { - Elf32_auxv_t* auxv = reinterpret_cast(auxv_ptr + i * sizeof(Elf32_auxv_t)); - Elf64_auxv_t tmp; - tmp.a_type = auxv->a_type; - tmp.a_un.a_val = auxv->a_un.a_val; - memcpy(data.data() + i * sizeof(Elf64_auxv_t), &tmp, sizeof(Elf64_auxv_t)); - } - } + // traceframe-info + // uib + // fdpic - return {encode(std::move(data)), HandledPacketType::TYPE_ACK}; + if (object == "osdata") { + return XferCommandOSData(annex, offset, length); } return {"", HandledPacketType::TYPE_UNKNOWN}; diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h index 7749e34cf1..237f1f9a16 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h @@ -70,11 +70,33 @@ class GdbServer { void SendPacketPair(const HandledPacketType& packetPair); HandledPacketType ProcessPacket(const fextl::string& packet); - HandledPacketType handleXfer(const fextl::string& packet); HandledPacketType handleProgramOffsets(); HandledPacketType ThreadAction(char action, uint32_t tid); + // Binary data transfer handlers + // XFer function to correctly encode any reply + static fextl::string EncodeXferString(const fextl::string& data, int offset, int length) { + if (offset == data.size()) { + return "l"; + } + if (offset >= data.size()) { + return "E34"; // ERANGE + } + if ((data.size() - offset) > length) { + return "m" + data.substr(offset, length); + } + return "l" + data.substr(offset); + }; + + HandledPacketType XferCommandExecFile(const fextl::string& annex, int offset, int length); + HandledPacketType XferCommandFeatures(const fextl::string& annex, int offset, int length); + HandledPacketType XferCommandThreads(const fextl::string& annex, int offset, int length); + HandledPacketType XferCommandOSData(const fextl::string& annex, int offset, int length); + HandledPacketType XferCommandLibraries(const fextl::string& annex, int offset, int length); + HandledPacketType XferCommandAuxv(const fextl::string& annex, int offset, int length); + HandledPacketType handleXfer(const fextl::string& packet); + // Command handlers HandledPacketType CommandEnableExtendedMode(const fextl::string& packet); HandledPacketType CommandQueryHalted(const fextl::string& packet); From 1fb20710e66eba41d3dd7d29fc513f8637e56762 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Mon, 25 Nov 2024 21:02:48 -0800 Subject: [PATCH 08/11] GdbServer: Switch to thread specific stopping break logic Previous `S AA` logic is legacy for non-multithreaded applications. This newer command gives more information about what occured and in what thread id. --- .../Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp | 12 ++++++++---- .../Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp index e2e4dbfb55..3f5299bd8b 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp @@ -61,13 +61,17 @@ desc: Provides a gdb interface to the guest state namespace FEX { #ifndef _WIN32 -void GdbServer::Break(int signal) { +void GdbServer::Break(FEXCore::Core::InternalThreadState* Thread, int signal) { std::lock_guard lk(sendMutex); if (!CommsStream) { return; } - const fextl::string str = fextl::fmt::format("S{:02x}", signal); + auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromFEXCoreThread(Thread); + // Current debugging thread switches to the thread that is breaking. + CurrentDebuggingThread = ThreadObject->ThreadInfo.TID.load(); + + const auto str = fextl::fmt::format("T{:02x}thread:{:x};", signal, CurrentDebuggingThread); SendPacket(*CommsStream, str); } @@ -93,7 +97,7 @@ GdbServer::GdbServer(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* ctx->SetExitHandler([this](FEXCore::Core::InternalThreadState* Thread, FEXCore::Context::ExitReason ExitReason) { if (ExitReason == FEXCore::Context::ExitReason::EXIT_DEBUG) { - this->Break(SIGTRAP); + this->Break(Thread, SIGTRAP); } if (ExitReason == FEXCore::Context::ExitReason::EXIT_SHUTDOWN) { @@ -113,7 +117,7 @@ GdbServer::GdbServer(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* } // Let GDB know that we have a signal - this->Break(Signal); + this->Break(Thread, Signal); WaitForThreadWakeup(); diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h index 237f1f9a16..2a4eb613fa 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h @@ -36,7 +36,7 @@ class GdbServer { } private: - void Break(int signal); + void Break(FEXCore::Core::InternalThreadState* Thread, int signal); void OpenListenSocket(); void CloseListenSocket(); From 54a7317312311e9345cb4a2a1840787bbeb8484e Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Mon, 25 Nov 2024 21:08:49 -0800 Subject: [PATCH 09/11] GdbServer: Split out function searching for thread by TID This currently happens in two locations, so split it out. There's some behaviour here where if the TID isn't found, then it returns the ParentThread of the process. This is working around a bug in either FEX's gdbserver or binaryninja. Leave it currently before we figure out what's wrong. NFC --- .../LinuxSyscalls/GdbServer.cpp | 52 +++++++------------ .../LinuxEmulation/LinuxSyscalls/GdbServer.h | 6 +++ 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp index 3f5299bd8b..762b9927b3 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp @@ -282,29 +282,30 @@ struct FEX_PACKED GDBContextDefinition { uint32_t mxcsr; }; -fextl::string GdbServer::readRegs() { - GDBContextDefinition GDB {}; - FEXCore::Core::CPUState state {}; - +const FEX::HLE::ThreadStateObject* GdbServer::FindThreadByTID(uint32_t TID) { auto Threads = SyscallHandler->TM.GetThreads(); - FEX::HLE::ThreadStateObject* CurrentThread {Threads->at(0)}; - bool Found = false; for (auto& Thread : *Threads) { - if (Thread->ThreadInfo.TID != CurrentDebuggingThread) { + if (Thread->ThreadInfo.TID != TID) { continue; } - memcpy(&state, Thread->Thread->CurrentFrame, sizeof(state)); - CurrentThread = Thread; - Found = true; - break; - } - if (!Found) { - // If set to an invalid thread then just get the parent thread ID - memcpy(&state, CurrentThread->Thread->CurrentFrame, sizeof(state)); + return Thread; } + // Return parent thread if TID isn't found. + return Threads->at(0); +} + +fextl::string GdbServer::readRegs() { + GDBContextDefinition GDB {}; + FEXCore::Core::CPUState state {}; + + const FEX::HLE::ThreadStateObject* CurrentThread = FindThreadByTID(CurrentDebuggingThread); + + // Copy the thread 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)); @@ -751,25 +752,10 @@ GdbServer::HandledPacketType GdbServer::CommandReadReg(const fextl::string& pack FEXCore::Core::CPUState state {}; - auto Threads = SyscallHandler->TM.GetThreads(); - FEX::HLE::ThreadStateObject* CurrentThread {Threads->at(0)}; - bool Found = false; - - for (auto& Thread : *Threads) { - if (Thread->ThreadInfo.TID != CurrentDebuggingThread) { - continue; - } - memcpy(&state, Thread->Thread->CurrentFrame, sizeof(state)); - CurrentThread = Thread; - Found = true; - break; - } - - if (!Found) { - // If set to an invalid thread then just get the parent thread ID - memcpy(&state, CurrentThread->Thread->CurrentFrame, sizeof(state)); - } + const FEX::HLE::ThreadStateObject* CurrentThread = FindThreadByTID(CurrentDebuggingThread); + // Copy the thread state. + memcpy(&state, CurrentThread->Thread->CurrentFrame, sizeof(state)); if (addr >= offsetof(GDBContextDefinition, gregs[0]) && addr < offsetof(GDBContextDefinition, gregs[16])) { return {encodeHex((unsigned char*)(&state.gregs[addr / sizeof(uint64_t)]), sizeof(uint64_t)), HandledPacketType::TYPE_ACK}; diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h index 2a4eb613fa..563673cd04 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h @@ -114,6 +114,12 @@ class GdbServer { HandledPacketType CommandBreakpoint(const fextl::string& packet); HandledPacketType CommandUnknown(const fextl::string& packet); + /** + * @brief Returns the ThreadStateObject for the matching TID, or parent thread if TID isn't found + * + * @param TID Which TID to search for + */ + const FEX::HLE::ThreadStateObject* FindThreadByTID(uint32_t TID); fextl::string readRegs(); FEXCore::Context::Context* CTX; From 7c6e83686552fd1f50a223fcc066da3a6e7444aa Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Mon, 25 Nov 2024 21:27:56 -0800 Subject: [PATCH 10/11] GdbServer: Split out GDB context definition generation to its own function GDB has two ways to read the registers. One way is reading the full GDBContextDefinition, which matches the layout in `BuildTargetXML`. The other way is to read the individual elements out of GDBContextDefinition. These two code paths were independently implemented. Instead generate in one location and use in either location. NFC --- .../LinuxSyscalls/GdbServer.cpp | 78 +++++-------------- .../LinuxEmulation/LinuxSyscalls/GdbServer.h | 20 ++++- 2 files changed, 38 insertions(+), 60 deletions(-) diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp index 762b9927b3..54587d300a 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp @@ -265,23 +265,6 @@ void GdbServer::SendACK(std::ostream& stream, bool NACK) { } } -struct X80Float { - uint8_t Data[10]; -}; - -struct FEX_PACKED GDBContextDefinition { - uint64_t gregs[FEXCore::Core::CPUState::NUM_GPRS]; - uint64_t rip; - uint32_t eflags; - uint32_t cs, ss, ds, es, fs, gs; - X80Float mm[FEXCore::Core::CPUState::NUM_MMS]; - uint32_t fctrl; - uint32_t fstat; - uint32_t dummies[6]; - uint64_t xmm[FEXCore::Core::CPUState::NUM_XMMS][4]; - uint32_t mxcsr; -}; - const FEX::HLE::ThreadStateObject* GdbServer::FindThreadByTID(uint32_t TID) { auto Threads = SyscallHandler->TM.GetThreads(); @@ -297,20 +280,19 @@ const FEX::HLE::ThreadStateObject* GdbServer::FindThreadByTID(uint32_t TID) { return Threads->at(0); } -fextl::string GdbServer::readRegs() { + +GdbServer::GDBContextDefinition GdbServer::GenerateContextDefinition(const FEX::HLE::ThreadStateObject* ThreadObject) { GDBContextDefinition GDB {}; FEXCore::Core::CPUState state {}; - const FEX::HLE::ThreadStateObject* CurrentThread = FindThreadByTID(CurrentDebuggingThread); - // Copy the thread state. - memcpy(&state, CurrentThread->Thread->CurrentFrame, sizeof(state)); + memcpy(&state, ThreadObject->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->Thread, false, nullptr, 0); + GDB.eflags = CTX->ReconstructCompactedEFLAGS(ThreadObject->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[i])); @@ -327,13 +309,13 @@ fextl::string GdbServer::readRegs() { __uint128_t XMM_Low[FEXCore::Core::CPUState::NUM_XMMS]; __uint128_t YMM_High[FEXCore::Core::CPUState::NUM_XMMS]; - CTX->ReconstructXMMRegisters(CurrentThread->Thread, XMM_Low, YMM_High); + CTX->ReconstructXMMRegisters(ThreadObject->Thread, XMM_Low, YMM_High); for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; ++i) { memcpy(&GDB.xmm[0], &XMM_Low[i], sizeof(__uint128_t)); memcpy(&GDB.xmm[2], &YMM_High[i], sizeof(__uint128_t)); } - return encodeHex((unsigned char*)&GDB, sizeof(GDBContextDefinition)); + return GDB; } void GdbServer::buildLibraryMap() { @@ -665,7 +647,9 @@ GdbServer::HandledPacketType GdbServer::CommandReadRegisters(const fextl::string // We might be running while we try reading // Pause up front SyscallHandler->TM.Pause(); - return {readRegs(), HandledPacketType::TYPE_ACK}; + const FEX::HLE::ThreadStateObject* CurrentThread = FindThreadByTID(CurrentDebuggingThread); + auto GDB = GenerateContextDefinition(CurrentThread); + return {encodeHex((unsigned char*)&GDB, sizeof(GDBContextDefinition)), HandledPacketType::TYPE_ACK}; } GdbServer::HandledPacketType GdbServer::CommandThreadOp(const fextl::string& packet) { @@ -750,56 +734,32 @@ GdbServer::HandledPacketType GdbServer::CommandReadReg(const fextl::string& pack ss.get(); // Drop first letter ss >> std::hex >> addr; - FEXCore::Core::CPUState state {}; - const FEX::HLE::ThreadStateObject* CurrentThread = FindThreadByTID(CurrentDebuggingThread); - - // Copy the thread state. - memcpy(&state, CurrentThread->Thread->CurrentFrame, sizeof(state)); + auto GDB = GenerateContextDefinition(CurrentThread); if (addr >= offsetof(GDBContextDefinition, gregs[0]) && addr < offsetof(GDBContextDefinition, gregs[16])) { - return {encodeHex((unsigned char*)(&state.gregs[addr / sizeof(uint64_t)]), sizeof(uint64_t)), HandledPacketType::TYPE_ACK}; + return {encodeHex((unsigned char*)(&GDB.gregs[addr / sizeof(uint64_t)]), sizeof(uint64_t)), HandledPacketType::TYPE_ACK}; } else if (addr == offsetof(GDBContextDefinition, rip)) { - return {encodeHex((unsigned char*)(&state.rip), sizeof(uint64_t)), HandledPacketType::TYPE_ACK}; + return {encodeHex((unsigned char*)(&GDB.rip), sizeof(uint64_t)), HandledPacketType::TYPE_ACK}; } else if (addr == offsetof(GDBContextDefinition, eflags)) { - uint32_t eflags = CTX->ReconstructCompactedEFLAGS(CurrentThread->Thread, false, nullptr, 0); - - return {encodeHex((unsigned char*)(&eflags), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; + return {encodeHex((unsigned char*)(&GDB.eflags), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; } else if (addr >= offsetof(GDBContextDefinition, cs) && addr < offsetof(GDBContextDefinition, mm[0])) { uint32_t Empty {}; return {encodeHex((unsigned char*)(&Empty), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; } else if (addr >= offsetof(GDBContextDefinition, mm[0]) && addr < offsetof(GDBContextDefinition, mm[8])) { - return {encodeHex((unsigned char*)(&state.mm[(addr - offsetof(GDBContextDefinition, mm[0])) / sizeof(X80Float)]), sizeof(X80Float)), + return {encodeHex((unsigned char*)(&GDB.mm[(addr - offsetof(GDBContextDefinition, mm[0])) / sizeof(X80Float)]), sizeof(X80Float)), HandledPacketType::TYPE_ACK}; } else if (addr == offsetof(GDBContextDefinition, fctrl)) { - uint32_t FCW = state.FCW; - return {encodeHex((unsigned char*)(&FCW), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; + return {encodeHex((unsigned char*)(&GDB.fctrl), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; } else if (addr == offsetof(GDBContextDefinition, fstat)) { - uint32_t FSW {}; - FSW = static_cast(state.flags[FEXCore::X86State::X87FLAG_TOP_LOC]) << 11; - FSW |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C0_LOC]) << 8; - FSW |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C1_LOC]) << 9; - FSW |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C2_LOC]) << 10; - FSW |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C3_LOC]) << 14; - return {encodeHex((unsigned char*)(&FSW), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; + return {encodeHex((unsigned char*)(&GDB.fstat), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; } else if (addr >= offsetof(GDBContextDefinition, dummies[0]) && addr < offsetof(GDBContextDefinition, dummies[6])) { - uint32_t Empty {}; - return {encodeHex((unsigned char*)(&Empty), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; + return {encodeHex((unsigned char*)(&GDB.dummies[0]), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; } else if (addr >= offsetof(GDBContextDefinition, xmm[0][0]) && addr < offsetof(GDBContextDefinition, xmm[16][0])) { const auto XmmIndex = (addr - offsetof(GDBContextDefinition, xmm[0][0])) / FEXCore::Core::CPUState::XMM_AVX_REG_SIZE; - - __uint128_t XMM_Low[FEXCore::Core::CPUState::NUM_XMMS]; - __uint128_t YMM_High[FEXCore::Core::CPUState::NUM_XMMS]; - - CTX->ReconstructXMMRegisters(CurrentThread->Thread, XMM_Low, YMM_High); - uint64_t xmm[4]; - memcpy(&xmm[0], &XMM_Low[XmmIndex], sizeof(__uint128_t)); - memcpy(&xmm[2], &YMM_High[XmmIndex], sizeof(__uint128_t)); - - return {encodeHex(reinterpret_cast(&xmm), FEXCore::Core::CPUState::XMM_AVX_REG_SIZE), HandledPacketType::TYPE_ACK}; + return {encodeHex(reinterpret_cast(&GDB.xmm[XmmIndex]), FEXCore::Core::CPUState::XMM_AVX_REG_SIZE), HandledPacketType::TYPE_ACK}; } else if (addr == offsetof(GDBContextDefinition, mxcsr)) { - uint32_t Empty {}; - return {encodeHex((unsigned char*)(&Empty), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; + return {encodeHex((unsigned char*)(&GDB.mxcsr), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; } LogMan::Msg::EFmt("Unknown GDB register 0x{:x}", addr); diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h index 563673cd04..5423990b5f 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h @@ -120,7 +120,25 @@ class GdbServer { * @param TID Which TID to search for */ const FEX::HLE::ThreadStateObject* FindThreadByTID(uint32_t TID); - fextl::string readRegs(); + + struct X80Float { + uint8_t Data[10]; + }; + + struct FEX_PACKED GDBContextDefinition { + uint64_t gregs[FEXCore::Core::CPUState::NUM_GPRS]; + uint64_t rip; + uint32_t eflags; + uint32_t cs, ss, ds, es, fs, gs; + X80Float mm[FEXCore::Core::CPUState::NUM_MMS]; + uint32_t fctrl; + uint32_t fstat; + uint32_t dummies[6]; + uint64_t xmm[FEXCore::Core::CPUState::NUM_XMMS][4]; + uint32_t mxcsr; + }; + + GDBContextDefinition GenerateContextDefinition(const FEX::HLE::ThreadStateObject* ThreadObject); FEXCore::Context::Context* CTX; FEX::HLE::SyscallHandler* const SyscallHandler; From 357cc0494023ff42a339eb4880cfaafe26be848c Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Tue, 26 Nov 2024 00:40:16 -0800 Subject: [PATCH 11/11] GdbServer: Splits Multi-letter v command handler Just breaks out the two commands we support and leaves TODOs for implementing the remaining commands. NFC --- .../LinuxSyscalls/GdbServer.cpp | 39 ++++++++++++++++++- .../LinuxEmulation/LinuxSyscalls/GdbServer.h | 3 ++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp index 54587d300a..001b946623 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp @@ -16,6 +16,7 @@ desc: Provides a gdb interface to the guest state #include #include #include +#include #include #include @@ -1003,7 +1004,7 @@ GdbServer::HandledPacketType GdbServer::CommandQueryThreadAlive(const fextl::str return {"OK", HandledPacketType::TYPE_ACK}; } -GdbServer::HandledPacketType GdbServer::CommandMultiLetterV(const fextl::string& packet) { +GdbServer::HandledPacketType GdbServer::HandlevFile(const fextl::string& packet) { const auto match = [&](const fextl::string& str) -> std::optional { if (packet.rfind(str, 0) == 0) { auto ss = fextl::istringstream(packet); @@ -1074,10 +1075,26 @@ GdbServer::HandledPacketType GdbServer::CommandMultiLetterV(const fextl::string& data.resize(ret); return {F_data(ret, data), HandledPacketType::TYPE_ACK}; } + + return {"", HandledPacketType::TYPE_ACK}; +} + +GdbServer::HandledPacketType GdbServer::HandlevCont(const fextl::string& packet) { + const auto match = [&](const fextl::string& str) -> std::optional { + if (packet.rfind(str, 0) == 0) { + auto ss = fextl::istringstream(packet); + ss.seekg(str.size()); + return ss; + } + return std::nullopt; + }; + + std::optional ss; if ((ss = match("vCont?"))) { return {"vCont;c;t;s;r", HandledPacketType::TYPE_ACK}; // We support continue, step and terminate // FIXME: We also claim to support continue with signal... because it's compulsory } + if ((ss = match("vCont;"))) { char action {}; int thread {}; @@ -1095,6 +1112,26 @@ GdbServer::HandledPacketType GdbServer::CommandMultiLetterV(const fextl::string& return ThreadAction(action, thread); } + + return {"", HandledPacketType::TYPE_ACK}; +} + +GdbServer::HandledPacketType GdbServer::CommandMultiLetterV(const fextl::string& packet) { + // TODO: vAttach + if (packet.starts_with("vCont")) { + return HandlevCont(packet); + } + + // TODO: vCtrlC + + if (packet.starts_with("vFile")) { + return HandlevFile(packet); + } + + // TODO: vKill + // TODO: vRun + // TODO: vStopped + return {"", HandledPacketType::TYPE_ACK}; } diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h index 5423990b5f..4a749a53f0 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h @@ -97,6 +97,9 @@ class GdbServer { HandledPacketType XferCommandAuxv(const fextl::string& annex, int offset, int length); HandledPacketType handleXfer(const fextl::string& packet); + HandledPacketType HandlevFile(const fextl::string& packet); + HandledPacketType HandlevCont(const fextl::string& packet); + // Command handlers HandledPacketType CommandEnableExtendedMode(const fextl::string& packet); HandledPacketType CommandQueryHalted(const fextl::string& packet);