diff --git a/External/FEXCore/Source/Interface/Core/GdbServer.cpp b/External/FEXCore/Source/Interface/Core/GdbServer.cpp index c7e302bdd..733efa87a 100644 --- a/External/FEXCore/Source/Interface/Core/GdbServer.cpp +++ b/External/FEXCore/Source/Interface/Core/GdbServer.cpp @@ -7,7 +7,8 @@ #include #include #include "Common/NetStream.h" - #include "LogManager.h" +#include "Common/SoftFloat.h" +#include "LogManager.h" #include #include @@ -15,9 +16,11 @@ #include #include #include +#include #include "GdbServer.h" #include +#include namespace FEXCore { @@ -73,13 +76,28 @@ static std::string encodeHex(unsigned char *data, size_t length) { return ss.str(); } +static std::string getThreadName(uint32_t ThreadID) { + std::fstream fs; + std::ostringstream ThreadFile; + ThreadFile << "/proc/" << getpid() << "/task/" << ThreadID << "/comm"; + + fs.open(ThreadFile.str(), std::fstream::in | std::fstream::binary); + if (fs.is_open()) { + std::string ThreadName; + fs >> ThreadName; + fs.close(); + return ThreadName; + } + + return ""; +} // Packet parser // Takes a serial stream and reads a single packet // Un-escapes chars, checks the checksum and request a retransmit if it fails. // Once the checksum is validated, it acknowledges and returns the packet in a string std::string GdbServer::ReadPacket(std::iostream &stream) { - std::string packet; + std::string packet{}; // The GDB "Remote Serial Protocal" was originally 7bit clean for use on serial ports. // Binary data is useally hex encoded. However some later extentions just put @@ -114,12 +132,9 @@ std::string GdbServer::ReadPacket(std::iostream &stream) { int expected_checksum = std::strtoul(hexString, nullptr, 16); if (calculateChecksum(packet) == expected_checksum) { - LogMan::Msg::I("Received Packet: \"%s\"", packet.c_str()); - stream << "+" << std::flush; return packet; } else { LogMan::Msg::E("Received Invalid Packet: $%s#%02x %c%c", packet.c_str(), expected_checksum); - stream << "-" << std::flush; } break; } @@ -155,54 +170,204 @@ 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; + // 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()); - stream << '$' << escaped << '#'; - stream << std::setfill('0') << std::setw(2) << std::hex << (int)calculateChecksum(escaped); - stream << std::flush; + auto escaped = escapePacket(packet); + std::ostringstream ss; + + ss << '$' << escaped << '#'; + ss << std::setfill('0') << std::setw(2) << std::hex << (int)calculateChecksum(escaped); + stream << ss.str() << std::flush; } -std::string GdbServer::readRegs() { - auto state = CTX->GetCPUState(); - // state.rip = 0x47c990; +void GdbServer::SendACK(std::ostream &stream, bool NACK) { + if (NoAckMode) { + return; + } - return encodeHex((unsigned char *)&state, sizeof(state)).substr(0, 572*2); + if (NACK) { + stream << "-" << std::flush; + } + else { + stream << "+" << std::flush; + } + + if (SettingNoAckMode) { + NoAckMode = true; + SettingNoAckMode = false; + } +} + +struct __attribute__((packed)) GDBContextDefinition { + uint64_t gregs[16]; + uint64_t rip; + uint32_t eflags; + uint32_t cs, ss, ds, es, fs, gs; + X80SoftFloat mm[8]; + uint32_t fctrl; + uint32_t fstat; + uint32_t dummies[6]; + uint64_t xmm[16][2]; + uint32_t mxcsr; +}; + +std::string GdbServer::readRegs() { + GDBContextDefinition GDB{}; + FEXCore::Core::CPUState state{}; + + auto Threads = CTX->GetThreads(); + bool Found = false; + + for (auto &Thread : *Threads) { + if (Thread->State.ThreadManager.GetTID() != CurrentDebuggingThread) { + continue; + } + state = Thread->State.State; + Found = true; + break; + } + + if (!Found) { + // If set to an invalid thread then just get the parent thread ID + state = CTX->GetCPUState(); + } + + // Encode the GDB context definition + memcpy(&GDB.gregs[0], &state.gregs[0], sizeof(GDB.gregs)); + memcpy(&GDB.rip, &state.rip, sizeof(GDB.rip)); + + for (size_t i = 0; i < 32; ++i) { + uint64_t Flag = state.flags[i]; + GDB.eflags |= (Flag << i); + } + + for (size_t i = 0; i < 8; ++i) { + memcpy(&GDB.mm[i], &state.mm[i], sizeof(GDB.mm)); + } + + // Currently unsupported + GDB.fctrl = 0x37F; + + GDB.fstat = static_cast(state.flags[FEXCore::X86State::X87FLAG_TOP_LOC]) << 11; + GDB.fstat |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C0_LOC]) << 8; + GDB.fstat |= static_cast(state.flags[FEXCore::X86State::X87FLAG_C1_LOC]) << 9; + 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[0], &state.xmm[0], sizeof(GDB.xmm)); + + return encodeHex((unsigned char *)&GDB, sizeof(GDBContextDefinition)); +} + +GdbServer::HandledPacketType GdbServer::readReg(std::string& packet) { + size_t addr; + auto ss = std::istringstream(packet); + ss.get(); // Drop first letter + ss >> std::hex >> addr; + + FEXCore::Core::CPUState state{}; + + auto Threads = CTX->GetThreads(); + bool Found = false; + + for (auto &Thread : *Threads) { + if (Thread->State.ThreadManager.GetTID() != CurrentDebuggingThread) { + continue; + } + state = Thread->State.State; + Found = true; + break; + } + + if (!Found) { + // If set to an invalid thread then just get the parent thread ID + state = CTX->GetCPUState(); + } + + + 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{}; + for (size_t i = 0; i < 32; ++i) { + uint64_t Flag = state.flags[i]; + eflags |= (Flag << i); + } + 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(X80SoftFloat)]), sizeof(X80SoftFloat)), HandledPacketType::TYPE_ACK}; + } + else if (addr == offsetof(GDBContextDefinition, fctrl)) { + // XXX: We don't support this yet + uint32_t FCW = 0x37F; + 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])) { + return {encodeHex((unsigned char *)(&state.xmm[(addr - offsetof(GDBContextDefinition, xmm[0][0])) / 16][0]), 16), 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::E("Unknown GDB register 0x%lx", addr); + return {"E00", HandledPacketType::TYPE_ACK}; } std::string buildTargetXML() { - using FEXCore::Core::CPUState; - std::ostringstream xml; - int dummy_offset = sizeof(CPUState); - int dummy_num = sizeof(CPUState) / 8; - auto dummy = [&](int size) { dummy_offset+= size; return dummy_num++; }; - xml << "\n"; xml << "\n";\ - xml << "\n"; + xml << "\n"; xml << "i386:x86-64\n"; xml << "GNU/Linux\n"; xml << "\n"; - xml << "\n"; + xml << "\n"; // flags register - for(int i=0; i <= 32; i++) { + for(int i = 0; i < 22; i++) { auto name = FEXCore::Core::GetFlagName(i); - xml << "\n"; + if (name.empty()) { + continue; + } + xml << "\t\n"; } - xml << ""; + xml << "\n"; - auto reg_numbered = [&](std::string_view name, int num, std::string_view type, int size) { - xml << "" << std::endl; - }; - auto reg = [&](std::string_view name, int offset, std::string_view type, int size) { - reg_numbered(name, offset / 8, type, size); + int32_t TargetSize{}; + auto reg = [&](std::string_view name, std::string_view type, int size) { + TargetSize += size; + xml << "" << std::endl; }; // Register ordering. @@ -210,62 +375,80 @@ std::string buildTargetXML() { // GPRs for (int i=0; i < 16; i++) { - reg(FEXCore::Core::GetGRegName(i), offsetof(CPUState, gregs[i]), "int64", 64); + reg(FEXCore::Core::GetGRegName(i), "int64", 64); } - reg("rip", offsetof(CPUState, rip), "code_ptr", 64); - reg("eflags", offsetof(CPUState, flags), "fex_eflags", 8*32); - reg("gs", offsetof(CPUState, gs), "int64", 64); - reg("fs", offsetof(CPUState, fs), "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_numbered("cs", dummy(4), "int32", 32); - reg_numbered("ss", dummy(4), "int32", 32); - reg_numbered("ds", dummy(4), "int32", 32); - reg_numbered("es", dummy(4), "int32", 32); + 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_numbered("st" + std::to_string(i), dummy(10), "i387_ext", 80); + reg("st" + std::to_string(i), "i387_ext", 80); } // x87 control - reg_numbered("fctrl", dummy(4), "int32", 32); - reg_numbered("fstat", dummy(4), "int32", 32); - reg_numbered("ftag", dummy(4), "int32", 32); - reg_numbered("fiseg", dummy(4), "int32", 32); - reg_numbered("fioff", dummy(4), "int32", 32); - reg_numbered("foseg", dummy(4), "int32", 32); - reg_numbered("fooff", dummy(4), "int32", 32); - reg_numbered("fop", dummy(4), "int32", 32); + 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 << ""; + xml << + R"( + + + + + + + + + + + + + + + )"; // SSE regs for (int i=0; i < 16; i++) { - reg("xmm" + std::to_string(i), offsetof(CPUState, xmm[i]), "sse", 128); + reg("xmm" + std::to_string(i), "vec128", 128); } - reg("mxcsr", dummy(4), "int", 32); + reg("mxcsr", "int", 32); xml << "\n"; - xml << "\n"; + xml << ""; xml << std::flush; return xml.str(); } -std::string GdbServer::handleXfer(std::string &packet) { - +GdbServer::HandledPacketType GdbServer::handleXfer(std::string &packet) { std::string object; std::string rw; std::string annex; + int annex_pid; int offset; int length; @@ -279,13 +462,20 @@ std::string GdbServer::handleXfer(std::string &packet) { std::getline(ss, object, ':'); std::getline(ss, rw, ':'); std::getline(ss, annex, ':'); + if (annex == "") { + annex_pid = getpid(); + } + else { + auto ss_pid = std::istringstream(annex); + ss_pid >> std::hex >> annex_pid; + } ss >> std::hex >> offset; ss.get(expectComma); ss >> std::hex >> length; // Bail on any errors if (ss.fail() || !ss.eof() || expectXfer != "qXfer" || rw != "read" || expectComma != ',') - return "E00"; + return {"E00", HandledPacketType::TYPE_ACK}; } // Lambda to correctly encode any reply @@ -300,19 +490,91 @@ std::string GdbServer::handleXfer(std::string &packet) { }; if (object == "exec-file") { - if (annex == "") - return encode(CTX->SyscallHandler->GetFilename()); - return "E00"; + if (annex_pid == getpid()) + return {encode(CTX->SyscallHandler->GetFilename()), HandledPacketType::TYPE_ACK}; + + return {"E00", HandledPacketType::TYPE_ACK}; } + if (object == "features") { - if (annex == "target.xml") - return encode(buildTargetXML()); - return "E00"; + if (annex == "target.xml") + return {encode(buildTargetXML()), HandledPacketType::TYPE_ACK}; + + return {"E00", HandledPacketType::TYPE_ACK}; } - return ""; + + if (object == "threads") { + if (offset == 0) { + auto Threads = CTX->GetThreads(); + + ThreadString.clear(); + std::ostringstream ss; + ss << "\n"; + ss << "\n"; + for (size_t i = 0; i < Threads->size(); ++i) { + auto Thread = Threads->at(i); + ss << "\tState.ThreadManager.GetTID() << "\" core=\"" << i << "\" name=\"" << getThreadName(Thread->State.ThreadManager.GetTID()) << "\">\n"; + ss << "\t\n"; + } + + ss << ""; + ss << std::flush; + ThreadString = ss.str(); + } + + return {encode(ThreadString.substr(offset, length)), HandledPacketType::TYPE_ACK}; + } + return {"", HandledPacketType::TYPE_UNKNOWN}; } -std::string GdbServer::handleMemory(std::string &packet) { +static size_t CheckMemMapping(uint64_t Address, size_t Size) { + uint64_t AddressEnd = Address + Size; + + std::fstream fs; + fs.open("/proc/self/maps", std::fstream::in | std::fstream::binary); + std::string Line; + while (std::getline(fs, Line)) { + if (fs.eof()) break; + uint64_t Begin, End; + char r,w,x,p; + if (sscanf(Line.c_str(), "%lx-%lx %c%c%c%c", &Begin, &End, &r, &w, &x, &p) == 6) { + if (Begin <= Address && + End > Address) { + ssize_t Overrun{}; + if (AddressEnd > End) { + Overrun = AddressEnd - End; + } + return Size - Overrun; + } + } + } + + fs.close(); + return 0; +} + +GdbServer::HandledPacketType GdbServer::handleProgramOffsets() { + std::fstream fs; + fs.open("/proc/self/maps", std::fstream::in | std::fstream::binary); + std::string Line; + std::string const &RuntimeExecutable = CTX->SyscallHandler->GetFilename(); + while (std::getline(fs, Line)) { + uint64_t Begin, End; + char Filename[255]; + if (sscanf(Line.c_str(), "%lx-%lx %*c%*c%*c%*c %*x %*x:%*x %*d%s", &Begin, &End, Filename) == 3) { + if (RuntimeExecutable == Filename) { + std::ostringstream ss; + ss << "Text=" << std::hex << Begin << ";Data=" << std::hex << Begin << ";Bss=" << std::hex << Begin; + ss << std::flush; + return {ss.str(), HandledPacketType::TYPE_ACK}; + } + } + } + fs.close(); + return {"Text=0;Data=0;Bss=0", HandledPacketType::TYPE_ACK}; +} + +GdbServer::HandledPacketType GdbServer::handleMemory(std::string &packet) { bool write; size_t addr; size_t length; @@ -331,42 +593,91 @@ std::string GdbServer::handleMemory(std::string &packet) { // validate packet if (ss.fail() || !ss.eof() || (write && (data.length() != length))) { - return "E00"; + return {"E00", HandledPacketType::TYPE_ACK}; } + length = CheckMemMapping(addr, length); + if (length == 0) { + return {"E00", HandledPacketType::TYPE_ACK}; + } + // TODO: check we are in a valid memory range // Also, clamp length - void* ptr = CTX->MemoryMapper.GetPointer(addr); + void* ptr = reinterpret_cast(addr); + if (!CTX->Config.UnifiedMemory) { + ptr = CTX->MemoryMapper.GetPointer(addr); + } if (write) { std::memcpy(ptr, data.data(), data.length()); // TODO: invalidate any code - return "OK"; + return {"OK", HandledPacketType::TYPE_ACK}; } else { - return encodeHex((unsigned char*)ptr, length); + return {encodeHex((unsigned char*)ptr, length), HandledPacketType::TYPE_ACK}; } } -std::string GdbServer::handleQuery(std::string &packet) { - auto match = [&](const char *str) -> bool { return packet.rfind(str, 0) == 0; }; +GdbServer::HandledPacketType GdbServer::handleQuery(std::string &packet) { + auto match = [&](const char *str) -> bool { return packet.rfind(str, 0) == 0; }; - if (match("qSupported")) { - return "PacketSize=5000;xmlRegisters=i386;qXfer:exec-file:read+;qXfer:features:read+"; + if (match("qSupported")) { + return {"PacketSize=5000;xmlRegisters=i386;qXfer:exec-file:read+;qXfer:features:read+;", HandledPacketType::TYPE_ACK}; + } + if (match("qAttached")) { + return {"1", HandledPacketType::TYPE_ACK}; // We don't currently support launching executables from gdb. + } + if (match("qXfer")) { + return handleXfer(packet); + } + if (match("qOffsets")) { + return handleProgramOffsets(); + } + if (match("qTStatus")) { + // We don't support trace experiments + return {"", HandledPacketType::TYPE_ACK}; + } + if (match("qfThreadInfo")) { + auto Threads = CTX->GetThreads(); + + std::ostringstream ss; + ss << "m"; + for (size_t i = 0; i < Threads->size(); ++i) { + auto Thread = Threads->at(i); + ss << std::hex << Thread->State.ThreadManager.TID << ","; } - if (match("qAttached")) { - return "1"; // We don't currently support launching executables from gdb. - } - if (match("qXfer")) { - return handleXfer(packet); - } - if (match("qOffsets")) { - return "Text=0;Data=0;Bss=0"; - } - return ""; + return {ss.str(), HandledPacketType::TYPE_ACK}; + } + if (match("qsThreadInfo")) { + return {"l", HandledPacketType::TYPE_ACK}; + } + if (match("qThreadExtraInfo")) { + auto ss = std::istringstream(packet); + ss.seekg(std::string("qThreadExtraInfo").size()); + ss.get(); // discard comma + uint32_t ThreadID; + ss >> std::hex >> ThreadID; + auto ThreadName = getThreadName(ThreadID); + return {encodeHex((unsigned char*)ThreadName.data(), ThreadName.size()), HandledPacketType::TYPE_ACK}; + } + if (match("qC")) { + // Returns the current Thread ID + std::ostringstream ss; + ss << "m" << std::hex << CTX->ParentThread->State.ThreadManager.TID; + return {ss.str(), HandledPacketType::TYPE_ACK}; + } + if (match("QStartNoAckMode")) { + SettingNoAckMode = true; + return {"OK", HandledPacketType::TYPE_ACK}; + } + if (match("qSymbol")) { + return {"OK", HandledPacketType::TYPE_ACK}; + } + + return {"", HandledPacketType::TYPE_UNKNOWN}; } -std::string GdbServer::handleV(std::string& packet) { +GdbServer::HandledPacketType GdbServer::handleV(std::string& packet) { auto match = [&](std::string str) -> std::optional { if (packet.rfind(str, 0) == 0) { auto ss = std::istringstream(packet); @@ -400,14 +711,20 @@ std::string GdbServer::handleV(std::string& packet) { ss->get(); // discard comma *ss >> std::hex >> mode; - return F(open(filename.c_str(), flags, mode)); + return {F(open(filename.c_str(), flags, mode)), HandledPacketType::TYPE_ACK}; } if((ss = match("vFile:setfs:"))) { int pid; *ss >> pid; - F(pid == 0 ? 0 : -1); // Only support the common filesystem + return {F(pid == 0 ? 0 : -1), HandledPacketType::TYPE_ACK}; // Only support the common filesystem } + if((ss = match("vFile:close:"))) { + int fd; + *ss >> std::hex >> fd; + close(fd); + return {F(0), HandledPacketType::TYPE_ACK}; + } if((ss = match("vFile:pread:"))) { int fd, count, offset; @@ -419,17 +736,17 @@ std::string GdbServer::handleV(std::string& packet) { std::string data(count, '\0'); if (lseek(fd, offset, SEEK_SET) < 0) { - return F_error(); + return {F_error(), HandledPacketType::TYPE_ACK}; } int ret = read(fd, data.data(), count); if (ret < 0) { - return F_error(); + return {F_error(), HandledPacketType::TYPE_ACK}; } data.resize(ret); - return F_data(ret, data); + return {F_data(ret, data), HandledPacketType::TYPE_ACK}; } if ((ss = match("vCont?"))) { - return "vCont;c;C;s;t"; // We support continue, step and terminate + return {"vCont;c;C;t;s;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;"))) { @@ -443,51 +760,145 @@ std::string GdbServer::handleV(std::string& packet) { *ss >> std::hex >> thread; } - if (ss->fail() || !ss->eof()) { - return "E00"; + if (ss->fail()) { + return {"E00", HandledPacketType::TYPE_ACK}; } switch (action) { - case 'c': + case 'c': { CTX->Run(); - return NO_REPLY; - case 's': + return {"", HandledPacketType::TYPE_ONLYACK}; + } + case 's': { CTX->Step(); - return NO_REPLY; + SendPacketPair({"OK", HandledPacketType::TYPE_ACK}); + std::ostringstream ss; + ss << "T05thread:" << std::setfill('0') << std::setw(2) << std::hex << getpid() << ";core:2c;"; + SendPacketPair({ss.str(), HandledPacketType::TYPE_ACK}); + return {"OK", HandledPacketType::TYPE_ACK}; + } case 't': CTX->ShouldStop = true; - return NO_REPLY; + return {"OK", HandledPacketType::TYPE_ACK}; default: - return "E00"; + return {"E00", HandledPacketType::TYPE_ACK}; } } - return ""; + return {"", HandledPacketType::TYPE_ACK}; } -std::string GdbServer::ProcessPacket(std::string &packet) { - switch (packet[0]) { - case '?': - return "S00"; +GdbServer::HandledPacketType GdbServer::handleThreadOp(std::string &packet) { + 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 = std::istringstream(packet); + ss.seekg(std::string("Hc").size()); + ss >> std::hex >> CurrentDebuggingThread; + + CTX->Pause(); + return {"OK", HandledPacketType::TYPE_ACK}; + } + + if (match("Hg")) { + // Sets thread for "other" operations + auto ss = std::istringstream(packet); + ss.seekg(std::string("Hg").size()); + ss >> std::hex >> CurrentDebuggingThread; + + // This must return quick otherwise IDA complains + CTX->Pause(); + return {"OK", HandledPacketType::TYPE_ACK}; + } + + return {"", HandledPacketType::TYPE_UNKNOWN}; +} + +GdbServer::HandledPacketType GdbServer::handleBreakpoint(std::string &packet) { + auto ss = std::istringstream(packet); + + bool Set{}; + uint64_t Addr; + uint64_t Type; + Set = ss.get() == 'Z'; + + ss >> std::hex >> Addr; + ss.get(); // discard comma + ss >> std::hex >> Type; + + CTX->Pause(); + return {"OK", HandledPacketType::TYPE_ACK}; +} + +GdbServer::HandledPacketType GdbServer::ProcessPacket(std::string &packet) { + 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 + //return {"S00", HandledPacketType::TYPE_ACK}; + std::ostringstream ss; + ss << "T00thread:" << std::setfill('0') << std::setw(2) << std::hex << getpid() << ";core:2c;"; + + return {ss.str(), HandledPacketType::TYPE_ACK}; + } case 'g': - return readRegs(); + return {readRegs(), HandledPacketType::TYPE_ACK}; + case 'p': + return readReg(packet); case 'q': - return handleQuery(packet); + case 'Q': + return handleQuery(packet); case 'v': - return handleV(packet); + return handleV(packet); case 'm': // Memory read case 'M': // Memory write - return handleMemory(packet); + 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 'Z': // Inserts breakpoint or watchpoint + return handleBreakpoint(packet); + case 'k': // Kill the process + CTX->ShouldStop = true; + CTX->Pause(); // Block until exit + return {"", HandledPacketType::TYPE_NONE}; default: - return ""; - } + return {"", HandledPacketType::TYPE_UNKNOWN}; + } +} + +void GdbServer::SendPacketPair(HandledPacketType response) { + std::lock_guard lk(sendMutex); + if (response.TypeResponse == HandledPacketType::TYPE_ACK || + response.TypeResponse == HandledPacketType::TYPE_ONLYACK) { + SendACK(*CommsStream, false); + } + else if (response.TypeResponse == HandledPacketType::TYPE_NACK || + response.TypeResponse == HandledPacketType::TYPE_ONLYNACK) { + SendACK(*CommsStream, true); + } + + if (response.TypeResponse == HandledPacketType::TYPE_UNKNOWN) { + SendPacket(*CommsStream, ""); + } + else if (response.TypeResponse != HandledPacketType::TYPE_ONLYNACK && + response.TypeResponse != HandledPacketType::TYPE_ONLYACK && + response.TypeResponse != HandledPacketType::TYPE_NONE) { + SendPacket(*CommsStream, response.Response); + } } void GdbServer::GdbServerLoop() { + while (!CTX->ShouldStop) { CommsStream = OpenSocket(); - std::string responce; + HandledPacketType response{}; // Outer server loop. Handles packet start, ACK/NAK and break @@ -496,13 +907,10 @@ void GdbServer::GdbServerLoop() { switch (c) { case '$': { std::string packet = ReadPacket(*CommsStream); - responce = ProcessPacket(packet); - { - std::lock_guard lk(sendMutex); - SendPacket(*CommsStream, responce); - } - if (responce == "") { - LogMan::Msg::D("Unknown packet %s", packet.c_str()); + response = ProcessPacket(packet); + SendPacketPair(response); + if (response.TypeResponse == HandledPacketType::TYPE_UNKNOWN) { + LogMan::Msg::D("Unknown packet %s", packet.c_str()); } break; } @@ -512,14 +920,17 @@ void GdbServer::GdbServerLoop() { case '-': // NAK, Resend requested { - std::lock_guard lk(sendMutex); - SendPacket(*CommsStream, responce); + std::lock_guard lk(sendMutex); + SendPacket(*CommsStream, response.Response); } break; - case '\x03': // ASCII EOT - LogMan::Msg::D("GdbServer: Break"); + case '\x03': { // ASCII EOT CTX->Pause(); + std::ostringstream ss; + ss << "T02thread:" << std::setfill('0') << std::setw(2) << std::hex << getpid() << ";core:2c;"; + SendPacketPair({ss.str(), HandledPacketType::TYPE_ACK}); break; + } default: LogMan::Msg::D("GdbServer: Unexpected byte %c (%02x)", c, c); } @@ -529,7 +940,7 @@ void GdbServer::GdbServerLoop() { std::lock_guard lk(sendMutex); CommsStream.release(); } - + } } void GdbServer::StartThread() { @@ -570,11 +981,10 @@ std::unique_ptr GdbServer::OpenSocket() { // Block until a connection arrives - LogMan::Msg::E("GdbServer, waiting for connection on localhost:8086"); + LogMan::Msg::I("GdbServer, waiting for connection on localhost:8086"); listen(sockfd, 1); new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size); - LogMan::Msg::E("Connected"); return std::make_unique(new_fd); } diff --git a/External/FEXCore/Source/Interface/Core/GdbServer.h b/External/FEXCore/Source/Interface/Core/GdbServer.h index 9ad64255f..a829c9a65 100644 --- a/External/FEXCore/Source/Interface/Core/GdbServer.h +++ b/External/FEXCore/Source/Interface/Core/GdbServer.h @@ -21,19 +21,42 @@ private: std::string ReadPacket(std::iostream &stream); void SendPacket(std::ostream &stream, std::string packet); - std::string ProcessPacket(std::string &packet); - std::string handleQuery(std::string &packet); - std::string handleXfer(std::string &packet); - std::string handleMemory(std::string &packet); - std::string handleV(std::string& packet); + void SendACK(std::ostream &stream, bool NACK); + + struct HandledPacketType { + std::string Response{}; + enum ResponseType { + TYPE_NONE, + TYPE_UNKNOWN, + TYPE_ACK, + TYPE_NACK, + TYPE_ONLYACK, + TYPE_ONLYNACK, + }; + ResponseType TypeResponse{}; + }; + + void SendPacketPair(HandledPacketType packetPair); + HandledPacketType ProcessPacket(std::string &packet); + HandledPacketType handleQuery(std::string &packet); + HandledPacketType handleXfer(std::string &packet); + HandledPacketType handleMemory(std::string &packet); + HandledPacketType handleV(std::string& packet); + HandledPacketType handleThreadOp(std::string &packet); + HandledPacketType handleBreakpoint(std::string &packet); + HandledPacketType handleProgramOffsets(); std::string readRegs(); + HandledPacketType readReg(std::string& packet); FEXCore::Context::Context *CTX; std::thread gdbServerThread; - uint64_t data_offset; std::unique_ptr CommsStream; std::mutex sendMutex; + bool SettingNoAckMode{false}; + bool NoAckMode{false}; + std::string ThreadString{}; + uint32_t CurrentDebuggingThread{}; }; }