Skip to content

Commit

Permalink
GdbServer: Implement new netstream that can have timeouts
Browse files Browse the repository at this point in the history
A major limitation of iostream is that you can't have reads or writes
with a timeout. I'm going to need a timeout with gdbserver, so switch
the netstream code over to using raw sockets rather than iostream which
can have a read timeout.
  • Loading branch information
Sonicadvance1 committed Dec 18, 2024
1 parent d8ef702 commit cf4f99f
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 142 deletions.
57 changes: 30 additions & 27 deletions Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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.
Expand All @@ -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<char> c;
while ((c = CommsStream->get()).has_value()) {
switch (*c) {
case '$': // start of packet
if (packet.size() != 0) {
LogMan::Msg::EFmt("Dropping unexpected data: \"{}\"", packet);
Expand All @@ -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()) {
packet.push_back(*escaped ^ 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) {
Expand All @@ -221,7 +224,7 @@ fextl::string GdbServer::ReadPacket(std::iostream& stream) {
}
break;
}
default: packet.push_back((char)c); break;
default: packet.push_back(*c); break;
}
}

Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -1394,11 +1397,11 @@ void GdbServer::GdbServerLoop() {

// Outer server loop. Handles packet start, ACK/NAK and break

int c;
while ((c = CommsStream->get()) >= 0) {
switch (c) {
std::optional<char> c;
while ((c = CommsStream->get()).has_value()) {
switch (*c) {
case '$': {
auto packet = ReadPacket(*CommsStream);
auto packet = ReadPacket();
response = ProcessPacket(packet);
SendPacketPair(response);
if (response.TypeResponse == HandledPacketType::TYPE_UNKNOWN) {
Expand All @@ -1413,7 +1416,7 @@ void GdbServer::GdbServerLoop() {
// NAK, Resend requested
{
std::lock_guard lk(sendMutex);
SendPacket(*CommsStream, response.Response);
SendPacket(response.Response);
}
break;
case '\x03': { // ASCII EOT
Expand All @@ -1426,7 +1429,7 @@ void GdbServer::GdbServerLoop() {
SendPacketPair({std::move(str), HandledPacketType::TYPE_ACK});
break;
}
default: LogMan::Msg::DFmt("GdbServer: Unexpected byte {} ({:02x})", static_cast<char>(c), c);
default: LogMan::Msg::DFmt("GdbServer: Unexpected byte {} ({:02x})", *c, *c);
}
}

Expand Down Expand Up @@ -1504,14 +1507,14 @@ void GdbServer::CloseListenSocket() {
unlink(GdbUnixSocketPath.c_str());
}

fextl::unique_ptr<std::iostream> GdbServer::OpenSocket() {
fextl::unique_ptr<FEX::Utils::NetStream> 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<FEXCore::Utils::NetStream>(new_fd);
return fextl::make_unique<FEX::Utils::NetStream>(new_fd);
}

#endif
Expand Down
12 changes: 6 additions & 6 deletions Source/Tools/LinuxEmulation/LinuxSyscalls/GdbServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ tags: glue|gdbserver
#include <FEXCore/fextl/string.h>

#include <atomic>
#include <istream>
#include <memory>
#include <mutex>
#include <stdint.h>

#include "LinuxSyscalls/NetStream.h"
#include "LinuxSyscalls/SignalDelegator.h"

namespace FEX {
Expand All @@ -45,12 +45,12 @@ class GdbServer {
ERROR,
};
WaitForConnectionResult WaitForConnection();
fextl::unique_ptr<std::iostream> OpenSocket();
fextl::unique_ptr<FEX::Utils::NetStream> 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();
Expand Down Expand Up @@ -147,7 +147,7 @@ class GdbServer {
FEX::HLE::SyscallHandler* const SyscallHandler;
FEX::HLE::SignalDelegator* SignalDelegation;
fextl::unique_ptr<FEXCore::Threads::Thread> gdbServerThread;
fextl::unique_ptr<std::iostream> CommsStream;
fextl::unique_ptr<FEX::Utils::NetStream> CommsStream;
std::mutex sendMutex;
bool SettingNoAckMode {false};
bool NoAckMode {false};
Expand Down
158 changes: 55 additions & 103 deletions Source/Tools/LinuxEmulation/LinuxSyscalls/NetStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,128 +4,80 @@
#include <FEXCore/Utils/Allocator.h>
#include <FEXCore/Utils/LogManager.h>

#include <array>
#include <cstring>
#include <iterator>
#ifndef _WIN32
#include <sys/socket.h>
#endif
#include <poll.h>
#include <unistd.h>

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;

void reset_output_buffer() {
// we always leave room for one extra char
setp(std::begin(output_buffer), std::end(output_buffer) - 1);
}
namespace FEX::Utils {
std::optional<char> NetStream::get(std::optional<std::chrono::seconds> timeout) {
if (read_offset != receive_buffer.size() && read_offset != receive_offset) {
auto Result = receive_buffer.at(read_offset);
++read_offset;
return Result;
}

int flushBuffer(const char* buffer, size_t size);
if (read_offset == receive_buffer.size()) {
read_offset = 0;
receive_offset = 0;
}

int socket;
std::array<char, 1400> output_buffer;
std::array<char, 1500> input_buffer; // enough for a typical packet
struct pollfd pfd {
.fd = socketfd, .events = POLLIN, .revents = 0,
};

int NetBuf::flushBuffer(const char* buffer, size_t size) {
#ifndef _WIN32
size_t total = 0;
timespec tmo {
.tv_sec = timeout.has_value() ? timeout->count() : 0,
.tv_nsec = 0,
};

// 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;
timespec* tmo_p = timeout.has_value() ? &tmo : nullptr;

while (true) {
auto Result = ppoll(&pfd, 1, tmo_p, nullptr);
if (Result > 0) {
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 Result;
} else {
return std::nullopt;
}
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;
} 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()) {
buf[Read] = *Result;
++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
Loading

0 comments on commit cf4f99f

Please sign in to comment.