Skip to content

Commit

Permalink
[GDBStub] Use xe::SocketServer instead of winsock, allow detaching
Browse files Browse the repository at this point in the history
Detach will clear breakpoints and resume execution
Reconnecting should now work fine, could probably have multiple connections
too (may be some threading issues with that though...)
  • Loading branch information
emoose committed Oct 6, 2024
1 parent 3c34e4b commit 4171da0
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 135 deletions.
3 changes: 3 additions & 0 deletions src/xenia/base/socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ class Socket {
// Returns true if the client is connected and can send/receive data.
virtual bool is_connected() = 0;

// Sets socket non-blocking mode
virtual void set_nonblocking(bool nonblocking) = 0;

// Closes the socket.
// This will signal the wait handle.
virtual void Close() = 0;
Expand Down
9 changes: 9 additions & 0 deletions src/xenia/base/socket_win.cc
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@ class Win32Socket : public Socket {
return socket_ != INVALID_SOCKET;
}

void set_nonblocking(bool nonblocking) override {
std::lock_guard<std::recursive_mutex> lock(mutex_);
if (socket_ == INVALID_SOCKET) {
return;
}
u_long val = nonblocking ? 1 : 0;
ioctlsocket(socket_, FIONBIO, &val);
}

void Close() override {
std::lock_guard<std::recursive_mutex> lock(mutex_);

Expand Down
219 changes: 94 additions & 125 deletions src/xenia/debug/gdb/gdbstub.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,13 @@

#include "xenia/debug/gdb/gdbstub.h"

#include <winsock2.h>
#include <ws2tcpip.h>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>

// Link with ws2_32.lib
#pragma comment(lib, "ws2_32.lib")

#include "xenia/base/clock.h"
#include "xenia/base/debugging.h"
#include "xenia/base/fuzzy.h"
Expand Down Expand Up @@ -184,6 +179,24 @@ uint8_t from_hexchar(char c) {
return 0;
}

std::string GDBStub::DebuggerDetached() {
// Delete all breakpoints
auto& state = cache_.breakpoints;

for (auto& breakpoint : state.all_breakpoints) {
processor_->RemoveBreakpoint(breakpoint.get());
}

state.code_breakpoints_by_guest_address.clear();
state.all_breakpoints.clear();

if (processor_->execution_state() == cpu::ExecutionState::kPaused) {
ExecutionContinue();
}

return kGdbReplyOK;
}

std::string GDBStub::ReadRegister(xe::cpu::ThreadDebugInfo* thread,
uint32_t rid) {
// Send registers as 32-bit, otherwise some debuggers will switch to 64-bit
Expand Down Expand Up @@ -242,23 +255,9 @@ std::string GDBStub::ReadRegister(xe::cpu::ThreadDebugInfo* thread,
GDBStub::GDBStub(Emulator* emulator, int listen_port)
: emulator_(emulator),
processor_(emulator->processor()),
listen_port_(listen_port),
client_socket_(0),
server_socket_(0) {}

GDBStub::~GDBStub() {
stop_thread_ = true;
if (listener_thread_.joinable()) {
listener_thread_.join();
}
if (server_socket_ != INVALID_SOCKET) {
closesocket(server_socket_);
}
if (client_socket_ != INVALID_SOCKET) {
closesocket(client_socket_);
}
WSACleanup();
}
listen_port_(listen_port) {}

GDBStub::~GDBStub() { stop_thread_ = true; }

std::unique_ptr<GDBStub> GDBStub::Create(Emulator* emulator, int listen_port) {
std::unique_ptr<GDBStub> debugger(new GDBStub(emulator, listen_port));
Expand All @@ -270,71 +269,31 @@ std::unique_ptr<GDBStub> GDBStub::Create(Emulator* emulator, int listen_port) {
}

bool GDBStub::Initialize() {
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0) {
XELOGE("GDBStub::Initialize: WSAStartup failed with error %d", result);
return false;
}

listener_thread_ = std::thread(&GDBStub::Listen, this);
socket_ = xe::SocketServer::Create(
listen_port_, [this](std::unique_ptr<Socket> client) { Listen(client); });

UpdateCache();
return true;
}

bool GDBStub::CreateSocket(int port) {
server_socket_ = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket_ == INVALID_SOCKET) {
XELOGE("GDBStub::CreateSocket: Socket creation failed");
return false;
}

sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = INADDR_ANY;

if (bind(server_socket_, (struct sockaddr*)&server_addr,
sizeof(server_addr)) == SOCKET_ERROR) {
XELOGE("GDBStub::CreateSocket: Socket bind failed");
return false;
}

if (listen(server_socket_, 1) == SOCKET_ERROR) {
XELOGE("GDBStub::CreateSocket: Socket listen failed");
return false;
}

return true;
}

bool GDBStub::Accept() {
client_socket_ = accept(server_socket_, nullptr, nullptr);
if (client_socket_ == INVALID_SOCKET) {
XELOGE("GDBStub::Accept: Socket accept failed");
return false;
}
return true;
}

void GDBStub::Listen() {
if (!CreateSocket(listen_port_)) {
return;
}
if (!Accept()) {
return;
}

void GDBStub::Listen(std::unique_ptr<Socket>& client) {
// Client is connected - pause execution
ExecutionPause();
UpdateCache();

u_long mode = 1; // 1 to enable non-blocking mode
ioctlsocket(client_socket_, FIONBIO, &mode);
client->set_nonblocking(true);

std::string receive_buffer;

while (!stop_thread_) {
if (!ProcessIncomingData()) {
if (!client->is_connected()) {
break;
}

if (!ProcessIncomingData(client, receive_buffer)) {
if (!client->is_connected()) {
break;
}
// No data available, can do other work or sleep
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
Expand All @@ -345,16 +304,17 @@ void GDBStub::Listen() {
if (cache_.notify_stopped) {
if (cache_.notify_bp_thread_id != -1)
cache_.cur_thread_id = cache_.notify_bp_thread_id;
SendPacket(
GetThreadStateReply(cache_.notify_bp_thread_id, kSignalSigtrap));
SendPacket(client, GetThreadStateReply(cache_.notify_bp_thread_id,
kSignalSigtrap));
cache_.notify_bp_thread_id = -1;
cache_.notify_stopped = false;
}
}
}
}

void GDBStub::SendPacket(const std::string& data) {
void GDBStub::SendPacket(std::unique_ptr<Socket>& client,
const std::string& data) {
std::stringstream ss;
ss << char(GdbStubControl::PacketStart) << data
<< char(GdbStubControl::PacketEnd);
Expand All @@ -365,7 +325,7 @@ void GDBStub::SendPacket(const std::string& data) {
ss << std::hex << std::setw(2) << std::setfill('0') << (checksum & 0xff);
std::string packet = ss.str();

send(client_socket_, packet.c_str(), int(packet.size()), 0);
client->Send(packet.c_str(), packet.size());
}

#ifdef DEBUG
Expand All @@ -388,6 +348,7 @@ std::string GetPacketFriendlyName(const std::string& packetCommand) {
{"qSupported", "Supported"},
{"qfThreadInfo", "qfThreadInfo"},
{"qC", "GetThreadId"},
{"D", "Detach"},
{"\03", "Break"},
};

Expand All @@ -401,77 +362,82 @@ std::string GetPacketFriendlyName(const std::string& packetCommand) {
}
#endif

bool GDBStub::ProcessIncomingData() {
bool GDBStub::ProcessIncomingData(std::unique_ptr<Socket>& client,
std::string& receive_buffer) {
char buffer[1024];
int received = recv(client_socket_, buffer, sizeof(buffer), 0);

if (received > 0) {
receive_buffer_.append(buffer, received);

// Hacky interrupt '\03' packet handling, some reason checksum isn't
// attached to this?
bool isInterrupt =
buffer[0] == char(GdbStubControl::Interrupt) && received == 1;

size_t packet_end;
while (isInterrupt ||
(packet_end = receive_buffer_.find('#')) != std::string::npos) {
if (isInterrupt || packet_end + 2 < receive_buffer_.length()) {
if (isInterrupt) {
current_packet_ = char(GdbStubControl::Interrupt);
receive_buffer_ = "";
isInterrupt = false;
} else {
current_packet_ = receive_buffer_.substr(0, packet_end + 3);
receive_buffer_ = receive_buffer_.substr(packet_end + 3);
}
size_t received = client->Receive(buffer, sizeof(buffer));
if (received == -1 || received == 0) {
return false;
}

GDBCommand command;
if (ParsePacket(command)) {
receive_buffer.append(buffer, received);

// Hacky interrupt '\03' packet handling, some reason checksum isn't
// attached to this?
bool isInterrupt =
buffer[0] == char(GdbStubControl::Interrupt) && received == 1;

// Check if we've received a full packet yet, if not exit and allow caller
// to try again
size_t packet_end;
while (isInterrupt ||
(packet_end = receive_buffer.find('#')) != std::string::npos) {
if (isInterrupt || packet_end + 2 < receive_buffer.length()) {
std::string current_packet;
if (isInterrupt) {
current_packet = char(GdbStubControl::Interrupt);
receive_buffer = "";
isInterrupt = false;
} else {
current_packet = receive_buffer.substr(0, packet_end + 3);
receive_buffer = receive_buffer.substr(packet_end + 3);
}

GDBCommand command;
if (ParsePacket(current_packet, command)) {
#ifdef DEBUG
auto packet_name = GetPacketFriendlyName(command.cmd);
auto packet_name = GetPacketFriendlyName(command.cmd);

debugging::DebugPrint("GDBStub: Packet {}({})\n",
packet_name.empty() ? command.cmd : packet_name,
command.data);
debugging::DebugPrint("GDBStub: Packet {}({})\n",
packet_name.empty() ? command.cmd : packet_name,
command.data);
#endif

GdbStubControl result = GdbStubControl::Ack;
send(client_socket_, (const char*)&result, 1, 0);
std::string response = HandleGDBCommand(command);
SendPacket(response);
} else {
GdbStubControl result = GdbStubControl::Nack;
send(client_socket_, (const char*)&result, 1, 0);
}
GdbStubControl result = GdbStubControl::Ack;
client->Send(&result, 1);
std::string response = HandleGDBCommand(command);
SendPacket(client, response);
} else {
break;
GdbStubControl result = GdbStubControl::Nack;
client->Send(&result, 1);
}
} else {
break;
}
}

return received > 0;
return true;
}

bool GDBStub::ParsePacket(GDBCommand& out_cmd) {
bool GDBStub::ParsePacket(const std::string& packet, GDBCommand& out_cmd) {
// Index to track position in current_packet_
size_t buffer_index = 0;

// Read a character from the buffer and increment index
auto ReadCharFromBuffer = [&]() -> char {
if (buffer_index >= current_packet_.size()) {
if (buffer_index >= packet.size()) {
return '\0';
}
return current_packet_[buffer_index++];
return packet[buffer_index++];
};

// Parse two hex digits from buffer
auto ReadHexByteFromBuffer = [&]() -> char {
if (buffer_index + 2 > current_packet_.size()) {
if (buffer_index + 2 > packet.size()) {
return 0;
}
char high = current_packet_[buffer_index++];
char low = current_packet_[buffer_index++];
char high = packet[buffer_index++];
char low = packet[buffer_index++];
return (from_hexchar(high) << 4) | from_hexchar(low);
};

Expand Down Expand Up @@ -835,6 +801,9 @@ std::string GDBStub::HandleGDBCommand(const GDBCommand& command) {
return "S05"; // tell debugger we're currently paused
}},

// Detach
{"D", [&](const GDBCommand& cmd) { return DebuggerDetached(); }},

// Enable extended mode
{"!", [&](const GDBCommand& cmd) { return kGdbReplyOK; }},

Expand Down
Loading

0 comments on commit 4171da0

Please sign in to comment.