Basic debugger networking.

This commit is contained in:
Ben Vanik
2015-05-23 22:27:43 -07:00
parent 969badd8c3
commit 576d6492dc
51 changed files with 1745 additions and 162 deletions

View File

@@ -10,14 +10,28 @@
#include "xenia/debug/debugger.h"
#include <gflags/gflags.h>
#include <mstcpip.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <mutex>
#include "xenia/base/logging.h"
#include "xenia/base/string.h"
#include "xenia/base/threading.h"
#include "xenia/cpu/function.h"
#include "xenia/cpu/processor.h"
#include "xenia/emulator.h"
// Autogenerated Flatbuffers files:
#include "xenia/debug/proto/breakpoints_generated.h"
#include "xenia/debug/proto/messages_generated.h"
DEFINE_string(debug_session_path, "", "Debug output path.");
DEFINE_int32(debug_port, 19000, "Port the debugger listens on.");
DEFINE_bool(wait_for_debugger, false,
"Waits for the debugger to attach before starting the game.");
DEFINE_bool(exit_with_debugger, true, "Exit whe the debugger disconnects.");
namespace xe {
namespace debug {
@@ -29,9 +43,19 @@ Breakpoint::Breakpoint(Type type, uint32_t address)
Breakpoint::~Breakpoint() = default;
Debugger::Debugger(cpu::Processor* processor) : processor_(processor) {}
Debugger::Debugger(Emulator* emulator)
: emulator_(emulator),
listen_socket_(INVALID_SOCKET),
client_socket_(INVALID_SOCKET) {
WSADATA wsa_data;
WSAStartup(MAKEWORD(2, 2), &wsa_data);
}
Debugger::~Debugger() = default;
Debugger::~Debugger() {
if (listen_socket_ != INVALID_SOCKET) {
StopSession();
}
}
bool Debugger::StartSession() {
std::wstring session_path = xe::to_wstring(FLAGS_debug_session_path);
@@ -44,11 +68,194 @@ bool Debugger::StartSession() {
xe::join_paths(session_path, L"functions.trace");
functions_trace_file_ = ChunkedMappedMemoryWriter::Open(
functions_trace_path, 32 * 1024 * 1024, true);
listen_socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listen_socket_ < 1) {
XELOGE("Failed to create debugger socket");
return false;
}
struct tcp_keepalive alive;
alive.onoff = TRUE;
alive.keepalivetime = 7200000;
alive.keepaliveinterval = 6000;
DWORD bytes_returned;
WSAIoctl(listen_socket_, SIO_KEEPALIVE_VALS, &alive, sizeof(alive), nullptr,
0, &bytes_returned, nullptr, nullptr);
int opt_value = 1;
setsockopt(listen_socket_, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<const char*>(&opt_value), sizeof(opt_value));
opt_value = 1;
setsockopt(listen_socket_, IPPROTO_TCP, TCP_NODELAY,
reinterpret_cast<const char*>(&opt_value), sizeof(opt_value));
sockaddr_in socket_addr = {0};
socket_addr.sin_family = AF_INET;
socket_addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket_addr.sin_port = htons(FLAGS_debug_port);
if (bind(listen_socket_, reinterpret_cast<sockaddr*>(&socket_addr),
sizeof(socket_addr)) == SOCKET_ERROR) {
int e = WSAGetLastError();
XELOGE("Unable to bind debug socket");
return false;
}
if (listen(listen_socket_, 5) == SOCKET_ERROR) {
XELOGE("Unable to listen on debug socket");
return 1;
}
return true;
}
void SendResponse(SOCKET client_socket, flatbuffers::FlatBufferBuilder& fbb,
uint32_t request_id, proto::ResponseData response_data_type,
flatbuffers::Offset<void> response_data_offset) {
auto response_offset = proto::CreateResponse(
fbb, request_id, response_data_type, response_data_offset);
fbb.Finish(response_offset);
int buffer_length = fbb.GetSize();
send(client_socket, reinterpret_cast<const char*>(&buffer_length), 4, 0);
send(client_socket, reinterpret_cast<const char*>(fbb.GetBufferPointer()),
fbb.GetSize(), 0);
}
void Debugger::PreLaunch() {
accept_thread_ = std::thread([this]() {
while (listen_socket_ != INVALID_SOCKET) {
sockaddr_in6 client_addr;
int client_count = sizeof(client_addr);
SOCKET client_socket_id =
accept(listen_socket_, reinterpret_cast<sockaddr*>(&client_addr),
&client_count);
if (client_socket_id == INVALID_SOCKET) {
XELOGE("Failed to accept socket");
continue;
}
// Only one debugger at a time.
if (client_socket_ != INVALID_SOCKET) {
XELOGW("Ignoring debugger connection as one is already connected");
closesocket(client_socket_id);
continue;
}
// Setup recv thread.
client_socket_ = client_socket_id;
receive_thread_ = std::thread([this]() {
while (client_socket_ != INVALID_SOCKET) {
// Read length prefix.
uint32_t length = 0;
int r = recv(client_socket_, reinterpret_cast<char*>(&length), 4,
MSG_WAITALL);
if (r != 4) {
// Failed?
XELOGE("Failed to recv debug data length - dead connection?");
if (FLAGS_exit_with_debugger) {
exit(1);
}
break;
}
// Read body.
std::vector<uint8_t> body(length);
r = recv(client_socket_, reinterpret_cast<char*>(body.data()), length,
MSG_WAITALL);
if (r != length) {
// Failed?
XELOGE("Failed to recv debug data body - dead connection?");
if (FLAGS_exit_with_debugger) {
exit(1);
}
break;
}
// Read message contents and dispatch.
OnMessage(std::move(body));
}
});
// This will WaitForClient if it was waiting.
}
});
if (FLAGS_wait_for_debugger) {
// Wait for the first client.
XELOGI("Waiting for debugger because of --wait_for_debugger...");
accept_fence_.Wait();
XELOGI("Debugger attached, continuing...");
}
}
void Debugger::OnMessage(std::vector<uint8_t> buffer) {
flatbuffers::FlatBufferBuilder fbb;
proto::ResponseData response_data_type;
flatbuffers::Offset<void> response_data_offset;
auto request = flatbuffers::GetRoot<proto::Request>(buffer.data());
switch (request->request_data_type()) {
case proto::RequestData_AttachRequest: {
// Send debug info.
response_data_type = proto::ResponseData_AttachResponse;
auto response_data = proto::AttachResponseBuilder(fbb);
//
response_data_offset = response_data.Finish().Union();
// Allow continuation if we were blocked waiting for a client.
accept_fence_.Signal();
} break;
case proto::RequestData_ListBreakpointsRequest: {
response_data_type = proto::ResponseData_ListBreakpointsResponse;
auto response_data = proto::ListBreakpointsResponseBuilder(fbb);
//
response_data_offset = response_data.Finish().Union();
} break;
case proto::RequestData_AddBreakpointRequest: {
response_data_type = proto::ResponseData_RemoveBreakpointResponse;
auto response_data = proto::AddBreakpointResponseBuilder(fbb);
//
response_data_offset = response_data.Finish().Union();
} break;
case proto::RequestData_UpdateBreakpointRequest: {
response_data_type = proto::ResponseData_UpdateBreakpointResponse;
auto response_data = proto::UpdateBreakpointResponseBuilder(fbb);
//
response_data_offset = response_data.Finish().Union();
} break;
case proto::RequestData_RemoveBreakpointRequest: {
response_data_type = proto::ResponseData_AddBreakpointResponse;
auto response_data = proto::RemoveBreakpointResponseBuilder(fbb);
//
response_data_offset = response_data.Finish().Union();
} break;
default:
assert_unhandled_case(request->request_data_type());
break;
}
SendResponse(client_socket_, std::move(fbb), request->id(),
response_data_type, response_data_offset);
}
void Debugger::StopSession() {
FlushSession();
if (client_socket_ != INVALID_SOCKET) {
shutdown(client_socket_, SD_SEND);
closesocket(client_socket_);
client_socket_ = INVALID_SOCKET;
}
linger so_linger;
so_linger.l_onoff = TRUE;
so_linger.l_linger = 30;
setsockopt(listen_socket_, SOL_SOCKET, SO_LINGER,
reinterpret_cast<const char*>(&so_linger), sizeof(so_linger));
shutdown(listen_socket_, SD_SEND);
closesocket(listen_socket_);
listen_socket_ = INVALID_SOCKET;
accept_thread_.join();
functions_file_.reset();
functions_trace_file_.reset();
}
@@ -132,7 +339,8 @@ int Debugger::AddBreakpoint(Breakpoint* breakpoint) {
}
// Find all functions that contain the breakpoint address.
auto fns = processor_->FindFunctionsWithAddress(breakpoint->address());
auto fns =
emulator_->processor()->FindFunctionsWithAddress(breakpoint->address());
// Add.
for (auto fn : fns) {
@@ -166,7 +374,8 @@ int Debugger::RemoveBreakpoint(Breakpoint* breakpoint) {
}
// Find all functions that have the breakpoint set.
auto fns = processor_->FindFunctionsWithAddress(breakpoint->address());
auto fns =
emulator_->processor()->FindFunctionsWithAddress(breakpoint->address());
// Remove.
for (auto fn : fns) {