debugger: allow more than one connection attempt per session

This commit is contained in:
Liam 2022-11-10 17:05:47 -05:00
parent ea41c53ab1
commit ceb829cc33

View File

@ -27,12 +27,21 @@ static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
const u8* buffer_start = reinterpret_cast<const u8*>(&buffer); const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
std::span<const u8> received_data{buffer_start, buffer_start + bytes_read}; std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
c(received_data); c(received_data);
AsyncReceiveInto(r, buffer, c);
} }
AsyncReceiveInto(r, buffer, c);
}); });
} }
template <typename Callback>
static void AsyncAccept(boost::asio::ip::tcp::acceptor& acceptor, Callback&& c) {
acceptor.async_accept([&, c](const boost::system::error_code& error, auto&& peer_socket) {
if (!error.failed()) {
c(peer_socket);
AsyncAccept(acceptor, c);
}
});
}
template <typename Readable, typename Buffer> template <typename Readable, typename Buffer>
static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) { static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) {
static_assert(std::is_trivial_v<Buffer>); static_assert(std::is_trivial_v<Buffer>);
@ -59,9 +68,7 @@ namespace Core {
class DebuggerImpl : public DebuggerBackend { class DebuggerImpl : public DebuggerBackend {
public: public:
explicit DebuggerImpl(Core::System& system_, u16 port) explicit DebuggerImpl(Core::System& system_, u16 port) : system{system_} {
: system{system_}, signal_pipe{io_context}, client_socket{io_context} {
frontend = std::make_unique<GDBStub>(*this, system);
InitializeServer(port); InitializeServer(port);
} }
@ -70,39 +77,42 @@ public:
} }
bool SignalDebugger(SignalInfo signal_info) { bool SignalDebugger(SignalInfo signal_info) {
{ std::scoped_lock lk{connection_lock};
std::scoped_lock lk{connection_lock};
if (stopped) { if (stopped || !state) {
// Do not notify the debugger about another event. // Do not notify the debugger about another event.
// It should be ignored. // It should be ignored.
return false; return false;
}
// Set up the state.
stopped = true;
info = signal_info;
} }
// Set up the state.
stopped = true;
state->info = signal_info;
// Write a single byte into the pipe to wake up the debug interface. // Write a single byte into the pipe to wake up the debug interface.
boost::asio::write(signal_pipe, boost::asio::buffer(&stopped, sizeof(stopped))); boost::asio::write(state->signal_pipe, boost::asio::buffer(&stopped, sizeof(stopped)));
return true; return true;
} }
// These functions are callbacks from the frontend, and the lock will be held.
// There is no need to relock it.
std::span<const u8> ReadFromClient() override { std::span<const u8> ReadFromClient() override {
return ReceiveInto(client_socket, client_data); return ReceiveInto(state->client_socket, state->client_data);
} }
void WriteToClient(std::span<const u8> data) override { void WriteToClient(std::span<const u8> data) override {
boost::asio::write(client_socket, boost::asio::buffer(data.data(), data.size_bytes())); boost::asio::write(state->client_socket,
boost::asio::buffer(data.data(), data.size_bytes()));
} }
void SetActiveThread(Kernel::KThread* thread) override { void SetActiveThread(Kernel::KThread* thread) override {
active_thread = thread; state->active_thread = thread;
} }
Kernel::KThread* GetActiveThread() override { Kernel::KThread* GetActiveThread() override {
return active_thread; return state->active_thread;
} }
private: private:
@ -113,65 +123,78 @@ private:
// Run the connection thread. // Run the connection thread.
connection_thread = std::jthread([&, port](std::stop_token stop_token) { connection_thread = std::jthread([&, port](std::stop_token stop_token) {
Common::SetCurrentThreadName("Debugger");
try { try {
// Initialize the listening socket and accept a new client. // Initialize the listening socket and accept a new client.
tcp::endpoint endpoint{boost::asio::ip::address_v4::any(), port}; tcp::endpoint endpoint{boost::asio::ip::address_v4::any(), port};
tcp::acceptor acceptor{io_context, endpoint}; tcp::acceptor acceptor{io_context, endpoint};
acceptor.async_accept(client_socket, [](const auto&) {}); AsyncAccept(acceptor, [&](auto&& peer) { AcceptConnection(std::move(peer)); });
io_context.run_one();
io_context.restart();
if (stop_token.stop_requested()) { while (!stop_token.stop_requested() && io_context.run()) {
return;
} }
ThreadLoop(stop_token);
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what()); LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what());
} }
}); });
} }
void AcceptConnection(boost::asio::ip::tcp::socket&& peer) {
LOG_INFO(Debug_GDBStub, "Accepting new peer connection");
std::scoped_lock lk{connection_lock};
// Ensure everything is stopped.
PauseEmulation();
// Set up the new frontend.
frontend = std::make_unique<GDBStub>(*this, system);
// Set the new state. This will tear down any existing state.
state = ConnectionState{
.client_socket{std::move(peer)},
.signal_pipe{io_context},
.info{},
.active_thread{},
.client_data{},
.pipe_data{},
};
// Set up the client signals for new data.
AsyncReceiveInto(state->signal_pipe, state->pipe_data, [&](auto d) { PipeData(d); });
AsyncReceiveInto(state->client_socket, state->client_data, [&](auto d) { ClientData(d); });
// Set the active thread.
UpdateActiveThread();
// Set up the frontend.
frontend->Connected();
}
void ShutdownServer() { void ShutdownServer() {
connection_thread.request_stop(); connection_thread.request_stop();
io_context.stop(); io_context.stop();
connection_thread.join(); connection_thread.join();
} }
void ThreadLoop(std::stop_token stop_token) {
Common::SetCurrentThreadName("Debugger");
// Set up the client signals for new data.
AsyncReceiveInto(signal_pipe, pipe_data, [&](auto d) { PipeData(d); });
AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); });
// Set the active thread.
UpdateActiveThread();
// Set up the frontend.
frontend->Connected();
// Main event loop.
while (!stop_token.stop_requested() && io_context.run()) {
}
}
void PipeData(std::span<const u8> data) { void PipeData(std::span<const u8> data) {
switch (info.type) { std::scoped_lock lk{connection_lock};
switch (state->info.type) {
case SignalType::Stopped: case SignalType::Stopped:
case SignalType::Watchpoint: case SignalType::Watchpoint:
// Stop emulation. // Stop emulation.
PauseEmulation(); PauseEmulation();
// Notify the client. // Notify the client.
active_thread = info.thread; state->active_thread = state->info.thread;
UpdateActiveThread(); UpdateActiveThread();
if (info.type == SignalType::Watchpoint) { if (state->info.type == SignalType::Watchpoint) {
frontend->Watchpoint(active_thread, *info.watchpoint); frontend->Watchpoint(state->active_thread, *state->info.watchpoint);
} else { } else {
frontend->Stopped(active_thread); frontend->Stopped(state->active_thread);
} }
break; break;
@ -179,8 +202,8 @@ private:
frontend->ShuttingDown(); frontend->ShuttingDown();
// Wait for emulation to shut down gracefully now. // Wait for emulation to shut down gracefully now.
signal_pipe.close(); state->signal_pipe.close();
client_socket.shutdown(boost::asio::socket_base::shutdown_both); state->client_socket.shutdown(boost::asio::socket_base::shutdown_both);
LOG_INFO(Debug_GDBStub, "Shut down server"); LOG_INFO(Debug_GDBStub, "Shut down server");
break; break;
@ -188,17 +211,16 @@ private:
} }
void ClientData(std::span<const u8> data) { void ClientData(std::span<const u8> data) {
std::scoped_lock lk{connection_lock};
const auto actions{frontend->ClientData(data)}; const auto actions{frontend->ClientData(data)};
for (const auto action : actions) { for (const auto action : actions) {
switch (action) { switch (action) {
case DebuggerAction::Interrupt: { case DebuggerAction::Interrupt: {
{ stopped = true;
std::scoped_lock lk{connection_lock};
stopped = true;
}
PauseEmulation(); PauseEmulation();
UpdateActiveThread(); UpdateActiveThread();
frontend->Stopped(active_thread); frontend->Stopped(state->active_thread);
break; break;
} }
case DebuggerAction::Continue: case DebuggerAction::Continue:
@ -206,15 +228,15 @@ private:
break; break;
case DebuggerAction::StepThreadUnlocked: case DebuggerAction::StepThreadUnlocked:
MarkResumed([&] { MarkResumed([&] {
active_thread->SetStepState(Kernel::StepState::StepPending); state->active_thread->SetStepState(Kernel::StepState::StepPending);
active_thread->Resume(Kernel::SuspendType::Debug); state->active_thread->Resume(Kernel::SuspendType::Debug);
ResumeEmulation(active_thread); ResumeEmulation(state->active_thread);
}); });
break; break;
case DebuggerAction::StepThreadLocked: { case DebuggerAction::StepThreadLocked: {
MarkResumed([&] { MarkResumed([&] {
active_thread->SetStepState(Kernel::StepState::StepPending); state->active_thread->SetStepState(Kernel::StepState::StepPending);
active_thread->Resume(Kernel::SuspendType::Debug); state->active_thread->Resume(Kernel::SuspendType::Debug);
}); });
break; break;
} }
@ -254,15 +276,14 @@ private:
template <typename Callback> template <typename Callback>
void MarkResumed(Callback&& cb) { void MarkResumed(Callback&& cb) {
Kernel::KScopedSchedulerLock sl{system.Kernel()}; Kernel::KScopedSchedulerLock sl{system.Kernel()};
std::scoped_lock cl{connection_lock};
stopped = false; stopped = false;
cb(); cb();
} }
void UpdateActiveThread() { void UpdateActiveThread() {
const auto& threads{ThreadList()}; const auto& threads{ThreadList()};
if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) { if (std::find(threads.begin(), threads.end(), state->active_thread) == threads.end()) {
active_thread = threads[0]; state->active_thread = threads[0];
} }
} }
@ -274,18 +295,22 @@ private:
System& system; System& system;
std::unique_ptr<DebuggerFrontend> frontend; std::unique_ptr<DebuggerFrontend> frontend;
boost::asio::io_context io_context;
std::jthread connection_thread; std::jthread connection_thread;
std::mutex connection_lock; std::mutex connection_lock;
boost::asio::io_context io_context;
boost::process::async_pipe signal_pipe;
boost::asio::ip::tcp::socket client_socket;
SignalInfo info; struct ConnectionState {
Kernel::KThread* active_thread; boost::asio::ip::tcp::socket client_socket;
bool pipe_data; boost::process::async_pipe signal_pipe;
bool stopped;
std::array<u8, 4096> client_data; SignalInfo info;
Kernel::KThread* active_thread;
std::array<u8, 4096> client_data;
bool pipe_data;
};
std::optional<ConnectionState> state{};
bool stopped{};
}; };
Debugger::Debugger(Core::System& system, u16 port) { Debugger::Debugger(Core::System& system, u16 port) {