diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp index 4265a562b2..37a81f748a 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp @@ -73,7 +73,7 @@ void GdbServer::Break(FEXCore::Core::InternalThreadState* Thread, int signal) { CurrentDebuggingThread = ThreadObject->ThreadInfo.TID.load(); const auto str = fextl::fmt::format("T{:02x}thread:{:x};", signal, CurrentDebuggingThread); - SendPacket(*CommsStream, str); + SendPacket(str); } void GdbServer::WaitForThreadWakeup() { @@ -178,7 +178,7 @@ static fextl::string encodeHex(std::string_view str) { // 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(std::iostream& stream) { +fextl::string GdbServer::ReadPacket() { fextl::string packet {}; // The GDB "Remote Serial Protocal" was originally 7bit clean for use on serial ports. @@ -190,9 +190,9 @@ fextl::string GdbServer::ReadPacket(std::iostream& stream) { // 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. - int c; - while ((c = stream.get()) > 0) { - switch (c) { + std::optional c; + while ((c = CommsStream->get()).has_value() && !c->Hangup) { + switch (c->data) { case '$': // start of packet if (packet.size() != 0) { LogMan::Msg::EFmt("Dropping unexpected data: \"{}\"", packet); @@ -203,15 +203,18 @@ fextl::string GdbServer::ReadPacket(std::iostream& stream) { break; case '}': // escape char { - char escaped; - stream >> escaped; - packet.push_back(escaped ^ 0x20); + auto escaped = CommsStream->get(); + if (escaped.has_value() && !escaped->Hangup) { + packet.push_back(escaped->data ^ 0x20); + } else { + LogMan::Msg::EFmt("Received Invalid escape char: ${}", packet); + } break; } case '#': // end of packet { char hexString[3] = {0, 0, 0}; - stream.read(hexString, 2); + CommsStream->read(hexString, 2); int expected_checksum = std::strtoul(hexString, nullptr, 16); if (calculateChecksum(packet) == expected_checksum) { @@ -221,7 +224,7 @@ fextl::string GdbServer::ReadPacket(std::iostream& stream) { } break; } - default: packet.push_back((char)c); break; + default: packet.push_back(c->data); break; } } @@ -248,22 +251,22 @@ static fextl::string escapePacket(const fextl::string& packet) { return ss.str(); } -void GdbServer::SendPacket(std::ostream& stream, const fextl::string& packet) { +void GdbServer::SendPacket(const fextl::string& packet) { const auto escaped = escapePacket(packet); const auto str = fextl::fmt::format("${}#{:02x}", escaped, calculateChecksum(escaped)); - stream << str << std::flush; + CommsStream->SendPacket(str); } -void GdbServer::SendACK(std::ostream& stream, bool NACK) { +void GdbServer::SendACK(bool NACK) { if (NoAckMode) { return; } if (NACK) { - stream << "-" << std::flush; + CommsStream->SendPacket("-"); } else { - stream << "+" << std::flush; + CommsStream->SendPacket("+"); } if (SettingNoAckMode) { @@ -1341,16 +1344,16 @@ GdbServer::HandledPacketType GdbServer::ProcessPacket(const fextl::string& packe void GdbServer::SendPacketPair(const HandledPacketType& response) { std::lock_guard lk(sendMutex); if (response.TypeResponse == HandledPacketType::TYPE_ACK || response.TypeResponse == HandledPacketType::TYPE_ONLYACK) { - SendACK(*CommsStream, false); + SendACK(false); } else if (response.TypeResponse == HandledPacketType::TYPE_NACK || response.TypeResponse == HandledPacketType::TYPE_ONLYNACK) { - SendACK(*CommsStream, true); + SendACK(true); } if (response.TypeResponse == HandledPacketType::TYPE_UNKNOWN) { - SendPacket(*CommsStream, ""); + SendPacket(""); } else if (response.TypeResponse != HandledPacketType::TYPE_ONLYNACK && response.TypeResponse != HandledPacketType::TYPE_ONLYACK && response.TypeResponse != HandledPacketType::TYPE_NONE) { - SendPacket(*CommsStream, response.Response); + SendPacket(response.Response); } } @@ -1394,41 +1397,47 @@ void GdbServer::GdbServerLoop() { // Outer server loop. Handles packet start, ACK/NAK and break - int c; - while ((c = CommsStream->get()) >= 0) { - switch (c) { - case '$': { - auto packet = ReadPacket(*CommsStream); - response = ProcessPacket(packet); - SendPacketPair(response); - if (response.TypeResponse == HandledPacketType::TYPE_UNKNOWN) { - LogMan::Msg::DFmt("Unknown packet {}", packet); + do { + std::optional c; + while ((c = CommsStream->get()).has_value() && !c->Hangup) { + switch (c->data) { + case '$': { + auto packet = ReadPacket(); + response = ProcessPacket(packet); + SendPacketPair(response); + if (response.TypeResponse == HandledPacketType::TYPE_UNKNOWN) { + LogMan::Msg::DFmt("Unknown packet {}", packet); + } + break; } - break; - } - case '+': - // ACK, do nothing. - break; - case '-': - // NAK, Resend requested - { - std::lock_guard lk(sendMutex); - SendPacket(*CommsStream, response.Response); + case '+': + // ACK, do nothing. + break; + case '-': + // NAK, Resend requested + { + std::lock_guard lk(sendMutex); + SendPacket(response.Response); + } + 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; } - 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;"; + default: LogMan::Msg::DFmt("GdbServer: Unexpected byte {} ({:02x})", c->data, c->data); } - SendPacketPair({std::move(str), HandledPacketType::TYPE_ACK}); - break; } - default: LogMan::Msg::DFmt("GdbServer: Unexpected byte {} ({:02x})", static_cast(c), c); + + if (c.has_value() && c->Hangup) { + break; } - } + } while (!CoreShuttingDown.load()); { std::lock_guard lk(sendMutex); @@ -1504,14 +1513,14 @@ void GdbServer::CloseListenSocket() { unlink(GdbUnixSocketPath.c_str()); } -fextl::unique_ptr GdbServer::OpenSocket() { +fextl::unique_ptr GdbServer::OpenSocket() { // Block until a connection arrives struct sockaddr_storage their_addr {}; socklen_t addr_size {}; int new_fd = accept(ListenSocket, (struct sockaddr*)&their_addr, &addr_size); - return fextl::make_unique(new_fd); + return fextl::make_unique(new_fd); } #endif diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h index af749d8f89..01421ce8a5 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h @@ -14,11 +14,11 @@ tags: glue|gdbserver #include #include -#include #include #include #include +#include "LinuxSyscalls/NetStream.h" #include "LinuxSyscalls/SignalDelegator.h" namespace FEX { @@ -45,12 +45,12 @@ class GdbServer { ERROR, }; WaitForConnectionResult WaitForConnection(); - fextl::unique_ptr OpenSocket(); + fextl::unique_ptr OpenSocket(); void StartThread(); - fextl::string ReadPacket(std::iostream& stream); - void SendPacket(std::ostream& stream, const fextl::string& packet); + fextl::string ReadPacket(); + void SendPacket(const fextl::string& packet); - void SendACK(std::ostream& stream, bool NACK); + void SendACK(bool NACK); Event ThreadBreakEvent {}; void WaitForThreadWakeup(); @@ -147,7 +147,7 @@ class GdbServer { FEX::HLE::SyscallHandler* const SyscallHandler; FEX::HLE::SignalDelegator* SignalDelegation; fextl::unique_ptr gdbServerThread; - fextl::unique_ptr CommsStream; + fextl::unique_ptr CommsStream; std::mutex sendMutex; bool SettingNoAckMode {false}; bool NoAckMode {false}; diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/NetStream.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/NetStream.cpp index 31a9f52070..79277b708d 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/NetStream.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/NetStream.cpp @@ -4,128 +4,84 @@ #include #include -#include #include -#include -#ifndef _WIN32 #include -#endif +#include #include -namespace FEXCore::Utils { -namespace { - class NetBuf final : public std::streambuf, public FEXCore::Allocator::FEXAllocOperators { - public: - explicit NetBuf(int socketfd) - : socket {socketfd} { - reset_output_buffer(); - } - ~NetBuf() override { - close(socket); - } - - private: - std::streamsize xsputn(const char* buffer, std::streamsize size) override; - - std::streambuf::int_type underflow() override; - std::streambuf::int_type overflow(std::streambuf::int_type ch) override; - int sync() override; +namespace FEX::Utils { +std::optional NetStream::get(std::optional timeout) { + if (read_offset != receive_buffer.size() && read_offset != receive_offset) { + auto Result = receive_buffer.at(read_offset); + ++read_offset; + return ReturnGet {Result, false}; + } - void reset_output_buffer() { - // we always leave room for one extra char - setp(std::begin(output_buffer), std::end(output_buffer) - 1); - } + if (read_offset == receive_buffer.size()) { + read_offset = 0; + receive_offset = 0; + } - int flushBuffer(const char* buffer, size_t size); + struct pollfd pfd { + .fd = socketfd, .events = POLLIN, .revents = 0, + }; - int socket; - std::array output_buffer; - std::array input_buffer; // enough for a typical packet + timespec tmo { + .tv_sec = timeout.has_value() ? timeout->count() : 0, + .tv_nsec = 0, }; - int NetBuf::flushBuffer(const char* buffer, size_t size) { -#ifndef _WIN32 - size_t total = 0; + timespec* tmo_p = timeout.has_value() ? &tmo : nullptr; - // Send data - while (total < size) { - size_t sent = send(socket, (const void*)(buffer + total), size - total, MSG_NOSIGNAL); - if (sent == -1) { - // lets just assume all errors are end of file. - return -1; + while (true) { + auto Result = ppoll(&pfd, 1, tmo_p, nullptr); + if (Result > 0) { + if (pfd.revents & POLLHUP) { + return ReturnGet {'\0', true}; } - total += sent; - } - - return 0; -#else - ERROR_AND_DIE_FMT("Unsupported"); -#endif - } - - std::streamsize NetBuf::xsputn(const char* buffer, std::streamsize size) { - size_t buf_remaining = epptr() - pptr(); - - // Check if the string fits neatly in our buffer - if (size <= buf_remaining) { - ::memcpy(pptr(), buffer, size); - pbump(size); - return size; - } - - // Otherwise, flush the buffer first - if (sync() < 0) { - return traits_type::eof(); - } - if (size > sizeof(output_buffer) / 2) { - // If we have a large string, bypass the buffer - flushBuffer(buffer, size); - return size; + const auto remaining_size = receive_buffer.size() - receive_offset; + Result = ::recv(socketfd, &receive_buffer.at(receive_offset), remaining_size, 0); + if (Result > 0) { + receive_offset += Result; + auto Result = receive_buffer.at(read_offset); + ++read_offset; + return ReturnGet {Result, false}; + } else { + return std::nullopt; + } } else { - return xsputn(buffer, size); + return std::nullopt; } } +} - std::streambuf::int_type NetBuf::overflow(std::streambuf::int_type ch) { - // we always leave room for one extra char - *pptr() = (char)ch; - pbump(1); - return sync(); - } - - int NetBuf::sync() { - // Flush and reset output buffer to zero - if (flushBuffer(pbase(), pptr() - pbase()) < 0) { - return -1; +size_t NetStream::read(char* buf, size_t size) { + size_t Read {}; + while (Read < size) { + auto Result = get(); + if (Result.has_value() && !Result->Hangup) { + buf[Read] = Result->data; + ++Read; + } else { + return Read; } - reset_output_buffer(); - return 0; } + return Read; +} - std::streambuf::int_type NetBuf::underflow() { -#ifndef _WIN32 - ssize_t size = recv(socket, (void*)std::begin(input_buffer), sizeof(input_buffer), 0); - - if (size <= 0) { - setg(nullptr, nullptr, nullptr); - return traits_type::eof(); +bool NetStream::SendPacket(const fextl::string& packet) { + size_t Total {}; + while (Total < packet.size()) { + size_t Remaining = packet.size() - Total; + size_t sent = ::send(socketfd, &packet.at(Total), Remaining, MSG_NOSIGNAL); + if (sent == -1) { + return false; } - - setg(&input_buffer[0], &input_buffer[0], &input_buffer[size]); - - return traits_type::to_int_type(*gptr()); -#else - ERROR_AND_DIE_FMT("Unsupported"); -#endif + Total += sent; } -} // Anonymous namespace - -NetStream::NetStream(int socketfd) - : std::iostream(new NetBuf(socketfd)) {} -NetStream::~NetStream() { - delete rdbuf(); + return Total == packet.size(); } -} // namespace FEXCore::Utils +} // namespace FEX::Utils diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/NetStream.h b/Source/Tools/LinuxEmulation/LinuxSyscalls/NetStream.h index 5002962727..0aed5d669a 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/NetStream.h +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/NetStream.h @@ -2,13 +2,31 @@ #pragma once #include +#include +#include -#include +#include +#include -namespace FEXCore::Utils { -class FEX_DEFAULT_VISIBILITY NetStream : public std::iostream { +namespace FEX::Utils { +class NetStream final { public: - explicit NetStream(int socketfd); - ~NetStream() override; + NetStream(int socketfd) + : socketfd {socketfd} + , receive_buffer(1500) {} + + struct ReturnGet { + char data; + bool Hangup; + }; + std::optional get(std::optional timeout = std::nullopt); + size_t read(char* buf, size_t size); + + bool SendPacket(const fextl::string& packet); +private: + int socketfd; + size_t read_offset {}; + size_t receive_offset {}; + fextl::vector receive_buffer; }; -} // namespace FEXCore::Utils +} // namespace FEX::Utils