// SPDX-License-Identifier: MIT /* $info$ tags: glue|gdbserver desc: Provides a gdb interface to the guest state $end_info$ */ #include "CodeLoader.h" #include "GdbServer/Info.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include "LinuxSyscalls/GdbServer.h" namespace FEX { #ifndef _WIN32 void GdbServer::Break(FEXCore::Core::InternalThreadState* Thread, int signal) { std::lock_guard lk(sendMutex); if (!CommsSocket) { return; } 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(*CommsSocket, str); } void GdbServer::WaitForThreadWakeup() { // Wait for gdbserver to tell us to wake up ThreadBreakEvent.Wait(); } GdbServer::~GdbServer() { CloseListenSocket(); if (gdbServerThread->joinable()) { gdbServerThread->join(nullptr); } } GdbServer::GdbServer(FEXCore::Context::Context* ctx, FEX::HLE::SignalDelegator* SignalDelegation, FEX::HLE::SyscallHandler* const SyscallHandler) : CTX(ctx) , SyscallHandler {SyscallHandler} , SignalDelegation {SignalDelegation} { // Pass all signals by default std::fill(PassSignals.begin(), PassSignals.end(), true); // This is a total hack as there is currently no way to resume once hitting a segfault // But it's semi-useful for debugging. for (uint32_t Signal = 0; Signal <= FEX::HLE::SignalDelegator::MAX_SIGNALS; ++Signal) { SignalDelegation->RegisterHostSignalHandler( Signal, [this](FEXCore::Core::InternalThreadState* Thread, int Signal, void* info, void* ucontext) { if (PassSignals[Signal]) { // Pass signal to the guest return false; } auto ThreadObject = FEX::HLE::ThreadManager::GetStateObjectFromFEXCoreThread(Thread); ThreadObject->GdbInfo = {}; ThreadObject->GdbInfo->Signal = Signal; this->SignalDelegation->SpillSRA(Thread, ucontext, Thread->CurrentFrame->InSyscallInfo); // Let GDB know that we have a signal this->Break(Thread, Signal); WaitForThreadWakeup(); ThreadObject->GdbInfo.reset(); return true; }, true); } StartThread(); } static int calculateChecksum(const fextl::string& packet) { unsigned char checksum = 0; for (const char& c : packet) { checksum += c; } return checksum; } static fextl::string hexstring(fextl::istringstream& ss, int delm) { fextl::string ret; char hexString[3] = {0, 0, 0}; while (ss.peek() != delm) { ss.read(hexString, 2); int c = std::strtoul(hexString, nullptr, 16); ret.push_back((char)c); } if (delm != -1) { ss.get(); } return ret; } static fextl::string appendHex(const char* data, size_t length) { return fextl::fmt::format("{:02x}", fmt::join(data, data + length, "")); } static fextl::string encodeHex(const unsigned char* data, size_t length) { fextl::ostringstream ss; for (size_t i = 0; i < length; i++) { ss << std::setfill('0') << std::setw(2) << std::hex << int(data[i]); } return ss.str(); } static fextl::string encodeHex(std::string_view str) { return encodeHex(reinterpret_cast(str.data()), str.size()); } // 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 fextl::string GdbServer::ReadPacket(const std::span& RawMessage) { fextl::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 // raw 8bit binary data. // Packets are in the format // $# // where any $ or # in the packet body are escaped ('}' followed by the char XORed with 0x20) // The checksum is a single unsigned byte sum of the data, hex encoded. if (RawMessage.empty() || (char)RawMessage[0] != '$') { ERROR_AND_DIE_FMT("Expected GDB protocol messages to start with '$'"); } for (auto It = std::next(RawMessage.begin()); It != RawMessage.end(); ++It) { char c = (char)*It; switch (c) { case '$': // start of packet ERROR_AND_DIE_FMT("Unescaped control character"); break; case '}': // escape char { if (std::next(It) == RawMessage.end()) { ERROR_AND_DIE_FMT("Missing character after escape indicator"); } char escaped = (char)*++It; packet.push_back(escaped ^ 0x20); break; } case '#': // end of packet { if (RawMessage.end() - It <= 2) { ERROR_AND_DIE_FMT("Missing checksum at end of packet"); } char hexString[3] = {0, 0, 0}; hexString[0] = (char)*++It; hexString[1] = (char)*++It; int expected_checksum = std::strtoul(hexString, nullptr, 16); if (calculateChecksum(packet) == expected_checksum) { return packet; } else { LogMan::Msg::EFmt("Received Invalid Packet: ${}#{:02x}", packet, expected_checksum); } break; } default: packet.push_back(c); break; } } return ""; } static fextl::string escapePacket(const fextl::string& packet) { fextl::ostringstream ss; for (const auto& c : packet) { switch (c) { case '$': case '#': case '*': case '}': { char escaped = c ^ 0x20; ss << '}' << (escaped); break; } default: ss << c; break; } } return ss.str(); } void GdbServer::SendPacket(fasio::tcp_socket& Socket, const fextl::string& packet) { const auto escaped = escapePacket(packet); auto str = fextl::fmt::format("${}#{:02x}", escaped, calculateChecksum(escaped)); fasio::error ec; write(Socket, fasio::mutable_buffer {std::as_writable_bytes(std::span {str})}, ec); } void GdbServer::SendACK(fasio::tcp_socket& Socket, bool NACK) { if (NoAckMode) { return; } if (NACK) { std::string_view message = "-"; send(Socket.FD, message.data(), message.size(), 0); } else { std::string_view message = "+"; send(Socket.FD, message.data(), message.size(), 0); } if (SettingNoAckMode) { NoAckMode = true; SettingNoAckMode = false; } } const FEX::HLE::ThreadStateObject* GdbServer::FindThreadByTID(uint32_t TID) { auto Threads = SyscallHandler->TM.GetThreads(); for (auto& Thread : *Threads) { if (Thread->ThreadInfo.TID != TID) { continue; } return Thread; } // Return parent thread if TID isn't found. return Threads->at(0); } GdbServer::GDBContextDefinition GdbServer::GenerateContextDefinition(const FEX::HLE::ThreadStateObject* ThreadObject) { GDBContextDefinition GDB {}; FEXCore::Core::CPUState state {}; // Copy the thread state. memcpy(&state, ThreadObject->Thread->CurrentFrame, sizeof(state)); // Encode the GDB context definition memcpy(&GDB.gregs[0], &state.gregs[0], sizeof(GDB.gregs)); GDB.rip = ThreadObject->Thread->CurrentFrame->State.rip; 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])); } 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; 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; __uint128_t XMM_Low[FEXCore::Core::CPUState::NUM_XMMS]; __uint128_t YMM_High[FEXCore::Core::CPUState::NUM_XMMS]; CTX->ReconstructXMMRegisters(ThreadObject->Thread, XMM_Low, YMM_High); for (size_t i = 0; i < FEXCore::Core::CPUState::NUM_XMMS; ++i) { memcpy(&GDB.xmm[i][0], &XMM_Low[i], sizeof(__uint128_t)); memcpy(&GDB.xmm[i][2], &YMM_High[i], sizeof(__uint128_t)); } return GDB; } void GdbServer::buildLibraryMap() { if (!LibraryMapChanged) { // No need to update return; } fextl::ostringstream xml; fextl::string MapsFile; FEXCore::FileLoading::LoadFile(MapsFile, "/proc/self/maps"); fextl::istringstream MapsStream(MapsFile); fextl::string Line; struct FileData { uint64_t Begin; }; fextl::map> SegmentMaps; // 7ff5dd6d2000-7ff5dd6d3000 rw-p 0000a000 103:0b 1881447 /usr/lib/x86_64-linux-gnu/libnss_compat.so.2 const fextl::string& RuntimeExecutable = Filename(); while (std::getline(MapsStream, Line)) { auto ss = fextl::istringstream(Line); fextl::string Tmp; fextl::string Begin; fextl::string Name; std::getline(ss, Begin, '-'); std::getline(ss, Tmp, ' '); // End std::getline(ss, Tmp, ' '); // Perm std::getline(ss, Tmp, ' '); // Inode std::getline(ss, Tmp, ' '); // devid std::getline(ss, Tmp, ' '); // Some garbage std::getline(ss, Name, '\n'); if (strstr(Name.c_str(), "aarch64") != nullptr) { // If the library comes from aarch64, just skip it // Reduces the amount of memory gdb fetches continue; } Name = FEXCore::StringUtils::Trim(Name); struct stat sb {}; if (stat(Name.c_str(), &sb) != -1) { if (S_ISCHR(sb.st_mode)) { // Skip this special file type // Fixes GDB trying to read dri render nodes continue; } } // Skip empty entries, the entry from the process, and also anything like [heap] if (!Name.empty() && Name != RuntimeExecutable && Name[0] != '[') { FileData data { .Begin = std::strtoul(Begin.c_str(), nullptr, 16), }; SegmentMaps[Name].emplace_back(data); } } xml << "\n"; for (auto& Array : SegmentMaps) { xml << "\t\n"; for (auto& Data : Array.second) { xml << "\t\t\n"; } xml << "\t\n"; } xml << "\n"; LibraryMapString = xml.str(); 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(Is64BitMode()), 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 offset; int length; // Parse Xfer message { auto ss = fextl::istringstream(packet); fextl::string expectXfer; char expectComma; std::getline(ss, expectXfer, ':'); std::getline(ss, object, ':'); std::getline(ss, rw, ':'); std::getline(ss, annex, ':'); 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", HandledPacketType::TYPE_ACK}; } } // 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); } // btrace // btrace-conf if (object == "exec-file") { return XferCommandExecFile(annex, offset, length); } if (object == "features") { return XferCommandFeatures(annex, offset, length); } if (object == "libraries") { return XferCommandLibraries(annex, offset, length); } // libraries-svr4 // memory-map // sdata // siginfo:read // siginfo:write if (object == "threads") { return XferCommandThreads(annex, offset, length); } // traceframe-info // uib // fdpic if (object == "osdata") { return XferCommandOSData(annex, offset, length); } return {"", HandledPacketType::TYPE_UNKNOWN}; } static size_t CheckMemMapping(uint64_t Address, size_t Size) { uint64_t AddressEnd = Address + Size; fextl::string MapsFile; FEXCore::FileLoading::LoadFile(MapsFile, "/proc/self/maps"); fextl::istringstream MapsStream(MapsFile); fextl::string Line; while (std::getline(MapsStream, Line)) { if (MapsStream.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; } } } return 0; } GdbServer::HandledPacketType GdbServer::handleProgramOffsets() { auto CodeLoader = SyscallHandler->GetCodeLoader(); uint64_t BaseOffset = CodeLoader->GetBaseOffset(); fextl::string str = fextl::fmt::format("Text={:x};Data={:x};Bss={:x}", BaseOffset, BaseOffset, BaseOffset); return {std::move(str), HandledPacketType::TYPE_ACK}; } 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(); const FEX::HLE::ThreadStateObject* CurrentThread = FindThreadByTID(CurrentDebuggingThread); const size_t NumGPR = Is64BitMode() ? FEXCore::Core::CPUState::NUM_GPRS : FEXCore::Core::CPUState::NUM_GPRS / 2; const size_t GPRSize = Is64BitMode() ? sizeof(uint64_t) : sizeof(uint32_t); const size_t NumXMM = Is64BitMode() ? FEXCore::Core::CPUState::NUM_XMMS : FEXCore::Core::CPUState::NUM_XMMS / 2; const size_t XMMSize = Is64BitMode() ? sizeof(__uint128_t) * 2 : sizeof(__uint128_t); fextl::string str; auto GDB = GenerateContextDefinition(CurrentThread); for (size_t i = 0; i < NumGPR; ++i) { str += appendHex(reinterpret_cast(&GDB.gregs[i]), GPRSize); } str += appendHex(reinterpret_cast(&GDB.rip), GPRSize); str += appendHex(reinterpret_cast(&GDB.eflags), sizeof(uint32_t)); str += appendHex(reinterpret_cast(&GDB.cs), sizeof(uint32_t)); str += appendHex(reinterpret_cast(&GDB.ss), sizeof(uint32_t)); str += appendHex(reinterpret_cast(&GDB.ds), sizeof(uint32_t)); str += appendHex(reinterpret_cast(&GDB.es), sizeof(uint32_t)); str += appendHex(reinterpret_cast(&GDB.fs), sizeof(uint32_t)); str += appendHex(reinterpret_cast(&GDB.gs), sizeof(uint32_t)); for (auto& mm : GDB.mm) { str += appendHex(reinterpret_cast(&mm), sizeof(X80Float)); } str += appendHex(reinterpret_cast(&GDB.fctrl), sizeof(uint32_t)); str += appendHex(reinterpret_cast(&GDB.fstat), sizeof(uint32_t)); str += appendHex(reinterpret_cast(&GDB.dummies), sizeof(GDB.dummies)); for (size_t i = 0; i < NumXMM; ++i) { str += appendHex(reinterpret_cast(&GDB.xmm[i]), XMMSize); } str += appendHex(reinterpret_cast(&GDB.mxcsr), sizeof(uint32_t)); return {std::move(str), 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; fextl::string data; auto ss = fextl::istringstream(packet); write = ss.get() == 'M'; ss >> std::hex >> addr; ss.get(); // discard comma ss >> std::hex >> length; if (write) { ss.get(); // discard colon data = hexstring(ss, -1); // grab data until end of file. } // validate packet if (ss.fail() || !ss.eof() || (write && (data.length() != length))) { 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 = reinterpret_cast(addr); if (write) { std::memcpy(ptr, data.data(), data.length()); // TODO: invalidate any code return {"OK", HandledPacketType::TYPE_ACK}; } else { return {encodeHex((unsigned char*)ptr, length), HandledPacketType::TYPE_ACK}; } } 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; const FEX::HLE::ThreadStateObject* CurrentThread = FindThreadByTID(CurrentDebuggingThread); auto GDB = GenerateContextDefinition(CurrentThread); if (addr >= offsetof(GDBContextDefinition, gregs[0]) && addr < offsetof(GDBContextDefinition, gregs[16])) { 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*)(&GDB.rip), sizeof(uint64_t)), HandledPacketType::TYPE_ACK}; } else if (addr == offsetof(GDBContextDefinition, eflags)) { 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*)(&GDB.mm[(addr - offsetof(GDBContextDefinition, mm[0])) / sizeof(X80Float)]), sizeof(X80Float)), HandledPacketType::TYPE_ACK}; } else if (addr == offsetof(GDBContextDefinition, fctrl)) { return {encodeHex((unsigned char*)(&GDB.fctrl), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; } else if (addr == offsetof(GDBContextDefinition, fstat)) { return {encodeHex((unsigned char*)(&GDB.fstat), sizeof(uint32_t)), HandledPacketType::TYPE_ACK}; } else if (addr >= offsetof(GDBContextDefinition, dummies[0]) && addr < offsetof(GDBContextDefinition, dummies[6])) { 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; return {encodeHex(reinterpret_cast(&GDB.xmm[XmmIndex]), FEXCore::Core::CPUState::XMM_AVX_REG_SIZE), HandledPacketType::TYPE_ACK}; } else if (addr == offsetof(GDBContextDefinition, mxcsr)) { return {encodeHex((unsigned char*)(&GDB.mxcsr), 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; }; const auto MatchStr = [](const fextl::string& Str, const char* str) -> bool { return Str.rfind(str, 0) == 0; }; const auto split = [](const fextl::string& Str, char deliminator) -> fextl::vector { fextl::vector Elements; fextl::istringstream Input(Str); for (fextl::string line; std::getline(Input, line); Elements.emplace_back(line)) ; return Elements; }; if (match("QNonStop:")) { auto ss = fextl::istringstream(packet); ss.seekg(fextl::string("QNonStop:").size()); ss.get(); // discard colon ss >> NonStopMode; return {"OK", HandledPacketType::TYPE_ACK}; } if (match("qSupported:")) { // eg: qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;memory-tagging+;xmlRegisters=i386 auto Features = split(packet.substr(strlen("qSupported:")), ';'); // For feature documentation // https://sourceware.org/gdb/current/onlinedocs/gdb/General-Query-Packets.html#qSupported fextl::string SupportedFeatures {}; // Required features SupportedFeatures += "PacketSize=32768;"; SupportedFeatures += "xmlRegisters=i386;"; SupportedFeatures += "qXfer:auxv:read+;"; SupportedFeatures += "qXfer:exec-file:read+;"; SupportedFeatures += "qXfer:features:read+;"; SupportedFeatures += "qXfer:libraries:read+;"; // Don't enable this feature. If enabled then gdb doesn't query for // memory-map updates post-launch. Resulting in the inability to // disassemble code from loaded libraries. // gdbserver running on a true host also doesn't use this feature. // It is likely used for embedded environments where you have a fixed // memory map. // SupportedFeatures += "qXfer:memory-map:read+;"; SupportedFeatures += "qXfer:siginfo:read+;"; SupportedFeatures += "qXfer:siginfo:write+;"; SupportedFeatures += "qXfer:threads:read+;"; SupportedFeatures += "QCatchSignals+;"; SupportedFeatures += "QPassSignals+;"; SupportedFeatures += "QNonStop+;"; SupportedFeatures += "qXfer:osdata:read+;"; SupportedFeatures += "QStartNoAckMode+;"; // TODO: Support breakpoints // SupportedFeatures += "swbreak+;"; // SupportedFeatures += "hwbreak+;"; // SupportedFeatures += "BreakpointCommands+;"; // TODO: If we want to support conditional breakpoints then we need to support single stepping. // SupportedFeatures += "ConditionalBreakpoints+;"; for (auto& Feature : Features) { if (MatchStr(Feature, "swbreak+")) { SupportedFeatures += "swbreak+;"; } if (MatchStr(Feature, "hwbreak+")) { SupportedFeatures += "hwbreak+;"; } if (MatchStr(Feature, "vContSupported+")) { SupportedFeatures += "vContSupported+;"; } // Unsupported: // multiprocess // qRelocInsn // fork-events // vfork-events // exec-events // QThreadEvents // no-resumed // memory-tagging } return {std::move(SupportedFeatures), HandledPacketType::TYPE_ACK}; } if (match("qAttached")) { return {"tnotrun:0", 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 = SyscallHandler->TM.GetThreads(); fextl::ostringstream ss; ss << "m"; for (size_t i = 0; i < Threads->size(); ++i) { auto Thread = Threads->at(i); ss << std::hex << Thread->ThreadInfo.TID; if (i != (Threads->size() - 1)) { ss << ","; } } return {ss.str(), HandledPacketType::TYPE_ACK}; } if (match("qsThreadInfo")) { return {"l", HandledPacketType::TYPE_ACK}; } if (match("qThreadExtraInfo")) { auto ss = fextl::istringstream(packet); ss.seekg(fextl::string("qThreadExtraInfo").size()); ss.get(); // discard comma uint32_t ThreadID; ss >> std::hex >> ThreadID; auto ThreadName = GDB::Info::GetThreadName(::getpid(), ThreadID); return {encodeHex((unsigned char*)ThreadName.data(), ThreadName.size()), HandledPacketType::TYPE_ACK}; } if (match("qC")) { // Returns the current Thread ID auto Threads = SyscallHandler->TM.GetThreads(); fextl::ostringstream ss; ss << "m" << std::hex << Threads->at(0)->ThreadInfo.TID; return {ss.str(), HandledPacketType::TYPE_ACK}; } if (match("QStartNoAckMode")) { SettingNoAckMode = true; return {"OK", HandledPacketType::TYPE_ACK}; } if (match("qSymbol")) { auto ss = fextl::istringstream(packet); ss.seekg(fextl::string("qSymbol").size()); ss.get(); // discard colon fextl::string Symbol_Val, Symbol_name; std::getline(ss, Symbol_Val, ':'); std::getline(ss, Symbol_name, ':'); if (Symbol_Val.empty() && Symbol_name.empty()) { return {"OK", HandledPacketType::TYPE_ACK}; } else { return {"", HandledPacketType::TYPE_UNKNOWN}; } } if (match("QPassSignals")) { // First set all signals as unpassed std::fill(PassSignals.begin(), PassSignals.end(), false); // eg: QPassSignals:e;10;14;17;1a;1b;1c;21;24;25;2c;4c;97; auto ss = fextl::istringstream(packet); ss.seekg(fextl::string("QPassSignals").size()); ss.get(); // discard colon // We now have a semi-colon deliminated list of signals to pass to the guest process for (fextl::string tmp; std::getline(ss, tmp, ';');) { uint32_t Signal = std::stoi(tmp.c_str(), nullptr, 16); if (Signal < FEX::HLE::SignalDelegator::MAX_SIGNALS) { PassSignals[Signal] = true; } } return {"OK", HandledPacketType::TYPE_ACK}; } // lldb specific queries if (match("qHostInfo")) { // Returns Key:Value pairs separated by ; // eg: // triple:7838365f36342d70632d6c696e75782d676e75; // ptrsize:8; // distribution_id:7562756e7475; // watchpoint_exceptions_received:after; // endian:little; // os_version:6.3.3; // os_build:362e332e332d3036303330332d67656e65726963; // os_kernel:2332303233303531373133333620534d5020505245454d50545f44594e414d494320576564204d61792031372031333a34353a3139205554432032303233; // hostname:7279616e682d545235303030; fextl::string HostFeatures {}; // 64-bit always returned for the host environment. // qProcessInfo will return i386 or not. HostFeatures += fextl::fmt::format("triple:{};", encodeHex("x86_64-pc-linux-gnu")); HostFeatures += "ptrsize:8;"; // Always little-endian. HostFeatures += "endian:little;"; struct utsname buf {}; if (uname(&buf) != -1) { uint32_t Major {}; uint32_t Minor {}; uint32_t Patch {}; // Parse kernel version in the form of `..[Optional Data]` const auto End = buf.release + sizeof(buf.release); auto Results = std::from_chars(buf.release, End, Major, 10); Results = std::from_chars(Results.ptr + 1, End, Minor, 10); Results = std::from_chars(Results.ptr + 1, End, Patch, 10); HostFeatures += fextl::fmt::format("os_version:{}.{}.{};", Major, Minor, Patch); // os_build returns the release untouched. HostFeatures += fextl::fmt::format("os_build:{};", encodeHex(buf.release)); HostFeatures += fextl::fmt::format("os_kernel:{};", encodeHex(buf.version)); HostFeatures += fextl::fmt::format("hostname:{};", encodeHex(buf.nodename)); } // TODO: distribution_id should be fetched with `lsb_release -i` // TODO: watchpoint_exceptions_received is unsupported return {std::move(HostFeatures), HandledPacketType::TYPE_ACK}; } if (match("qGetWorkingDir")) { char Tmp[PATH_MAX]; if (getcwd(Tmp, PATH_MAX)) { return {encodeHex(Tmp), HandledPacketType::TYPE_ACK}; } return {"E00", HandledPacketType::TYPE_ACK}; } return {"", HandledPacketType::TYPE_UNKNOWN}; } GdbServer::HandledPacketType GdbServer::CommandSingleStep(const fextl::string& packet) { return ThreadAction('s', 0); } GdbServer::HandledPacketType GdbServer::CommandQueryThreadAlive(const fextl::string& packet) { return {"OK", HandledPacketType::TYPE_ACK}; } 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); ss.seekg(str.size()); return ss; } return std::nullopt; }; const auto F = [](int result) -> fextl::string { return fextl::fmt::format("F{:x}", result); }; const auto F_error = []() -> fextl::string { return fextl::fmt::format("F-1,{:x}", errno); }; const auto F_data = [](int result, const fextl::string& data) -> fextl::string { // Binary encoded data is raw appended to the end return fextl::fmt::format("F{:#x};", result) + data; }; std::optional ss; if ((ss = match("vFile:open:"))) { fextl::string filename; int flags; int mode; filename = hexstring(*ss, ','); *ss >> std::hex >> flags; ss->get(); // discard comma *ss >> std::hex >> mode; return {F(open(filename.c_str(), flags, mode)), HandledPacketType::TYPE_ACK}; } if ((ss = match("vFile:setfs:"))) { int pid; *ss >> pid; 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; *ss >> std::hex >> fd; ss->get(); // discard comma *ss >> std::hex >> count; ss->get(); // discard comma *ss >> std::hex >> offset; fextl::string data(count, '\0'); if (lseek(fd, offset, SEEK_SET) < 0) { return {F_error(), HandledPacketType::TYPE_ACK}; } int ret = read(fd, data.data(), count); if (ret < 0) { return {F_error(), HandledPacketType::TYPE_ACK}; } if (ret == 0) { return {F(0), HandledPacketType::TYPE_ACK}; } 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 {}; action = ss->get(); if (ss->peek() == ':') { ss->get(); *ss >> std::hex >> thread; } if (ss->fail()) { return {"E00", HandledPacketType::TYPE_ACK}; } 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); } if (packet.starts_with("vKill")) { tgkill(::getpid(), ::getpid(), SIGKILL); } // TODO: vRun // TODO: vStopped return {"", HandledPacketType::TYPE_ACK}; } GdbServer::HandledPacketType GdbServer::CommandBreakpoint(const fextl::string& packet) { auto ss = fextl::istringstream(packet); // Don't do anything with set breakpoints yet [[maybe_unused]] bool Set {}; uint64_t Addr; uint64_t Type; Set = ss.get() == 'Z'; ss >> std::hex >> Addr; ss.get(); // discard comma ss >> std::hex >> Type; SyscallHandler->TM.Pause(); 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]) { // 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': // 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}; } } void GdbServer::SendPacketPair(const HandledPacketType& response) { std::lock_guard lk(sendMutex); if (response.TypeResponse == HandledPacketType::TYPE_ACK || response.TypeResponse == HandledPacketType::TYPE_ONLYACK) { SendACK(*CommsSocket, false); } else if (response.TypeResponse == HandledPacketType::TYPE_NACK || response.TypeResponse == HandledPacketType::TYPE_ONLYNACK) { SendACK(*CommsSocket, true); } if (response.TypeResponse == HandledPacketType::TYPE_UNKNOWN) { SendPacket(*CommsSocket, ""); } else if (response.TypeResponse != HandledPacketType::TYPE_ONLYNACK && response.TypeResponse != HandledPacketType::TYPE_ONLYACK && response.TypeResponse != HandledPacketType::TYPE_NONE) { SendPacket(*CommsSocket, response.Response); } } std::pair::iterator, bool> GdbServer::MatchPacket(fextl::vector::iterator begin, fextl::vector::iterator end) { if (CommsBuffer.empty()) { return std::make_pair(begin, false); } switch ((char)CommsBuffer[0]) { case '+': case '-': case '\x03': // No further data return std::make_pair(std::next(begin), true); case '$': { // Message format: $packet-data#checksum, where checksum is a single byte. auto match = std::find(begin, end, (std::byte)'#'); if (match == end) { // No match; fetch more data return std::make_pair(end, false); } else if (end - match <= 2) { // Found '#' but missing the checksum bytes return std::make_pair(match, false); } else { return std::make_pair(std::next(match, 3), true); } break; } default: ERROR_AND_DIE_FMT("Unexpected character at beginning of GDB packet: {}", CommsBuffer[0]); } } void GdbServer::HandlePacket(fasio::error ec, size_t BytesInMessage) { if (ec != fasio::error::success || BytesInMessage == 0) { ERROR_AND_DIE_FMT("Failed"); } char c = (char)CommsBuffer[0]; switch (c) { case '$': { auto packet = ReadPacket(std::span {CommsBuffer}.subspan(0, BytesInMessage)); auto response = ProcessPacket(packet); SendPacketPair(response); if (response.TypeResponse == HandledPacketType::TYPE_UNKNOWN) { LogMan::Msg::DFmt("Unknown packet {}", packet); } break; } case '+': // ACK, do nothing. break; case '-': // NAK, Resend requested { std::lock_guard lk(sendMutex); SendPacket(*CommsSocket, {}); } break; case '\x03': { // ASCII EOT SyscallHandler->TM.Pause(); fextl::string str = fextl::fmt::format("T02thread:{:02x};", getpid()); if (LibraryMapChanged) { // If libraries have changed then let gdb know str += "library:1;"; } SendPacketPair({std::move(str), HandledPacketType::TYPE_ACK}); break; } default: LogMan::Msg::DFmt("GdbServer: Unexpected byte {} ({:02x})", c, c); } CommsBuffer.erase(CommsBuffer.begin(), CommsBuffer.begin() + BytesInMessage); async_read_until(*CommsSocket, fasio::dynamic_vector_buffer {CommsBuffer}, std::bind_front(&GdbServer::MatchPacket, this), std::bind_front(&GdbServer::HandlePacket, this)); } void GdbServer::GdbServerLoop() { OpenListenSocket(); if (!Acceptor) { // Couldn't open socket, just exit. return; } Acceptor->async_accept([this](fasio::error ec, std::optional Socket) { if (ec != fasio::error::success) { // Listen socket error or shutting down LogMan::Msg::EFmt("[GdbServer] gdbserver shutting down: {}"); close(CommsSocket->FD); CommsSocket.reset(); // Repeat to wait for another connection return fasio::post_callback::repeat; } CommsSocket.emplace(*std::move(Socket)); // Receive packet data async_read_until(*CommsSocket, fasio::dynamic_vector_buffer {CommsBuffer}, std::bind_front(&GdbServer::MatchPacket, this), std::bind_front(&GdbServer::HandlePacket, this)); // Repeat to catch disconnect events return fasio::post_callback::repeat; }); CommsBuffer.reserve(1000); // Enter event loop Reactor.run(); // Shut down std::lock_guard lk(sendMutex); if (CommsSocket) { close(CommsSocket->FD); CommsSocket.reset(); } CloseListenSocket(); } static void* ThreadHandler(void* Arg) { FEXCore::Threads::SetThreadName("FEX:gdbserver"); auto This = reinterpret_cast(Arg); This->GdbServerLoop(); return nullptr; } void GdbServer::StartThread() { uint64_t OldMask = FEXCore::Threads::SetSignalMask(~0ULL); gdbServerThread = FEXCore::Threads::Thread::Create(ThreadHandler, this); FEXCore::Threads::SetSignalMask(OldMask); } void GdbServer::OpenListenSocket() { const auto GdbUnixPath = fextl::fmt::format("{}/FEX_gdbserver/", FEXServerClient::GetTempFolder()); if (FHU::Filesystem::CreateDirectory(GdbUnixPath) == FHU::Filesystem::CreateDirectoryResult::ERROR) { LogMan::Msg::EFmt("[GdbServer] Couldn't create gdbserver folder {}", GdbUnixPath); return; } GdbUnixSocketPath = fextl::fmt::format("{}{}-gdb", GdbUnixPath, ::getpid()); for (int attempt = 0; attempt < 2; ++attempt) { Acceptor = fasio::tcp_acceptor::create(Reactor, false, GdbUnixSocketPath, 1); if (Acceptor) { break; } // This can happen periodically with execve. unlink the path and try again. // The PID is reused but FEX likely started a gdbserver thread for the PID before execve. unlink(GdbUnixSocketPath.c_str()); } if (!Acceptor) { LogMan::Msg::EFmt("[GdbServer] Couldn't bind AF_UNIX socket '{}': {} {}\n", GdbUnixSocketPath, errno, strerror(errno)); return; } LogMan::Msg::IFmt("[GdbServer] Waiting for connection on {}", GdbUnixSocketPath); LogMan::Msg::IFmt("[GdbServer] gdb-multiarch -ex \"set debug remote 1\" -ex \"target extended-remote {}\"", GdbUnixSocketPath); } void GdbServer::CloseListenSocket() { Acceptor.reset(); unlink(GdbUnixSocketPath.c_str()); } #endif } // namespace FEX