From 7dcacc2cda22baa915bd6fffb3ced23864f1ecb3 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Boric Date: Thu, 17 Dec 2020 18:32:29 +0100 Subject: [PATCH] Qt: Implement GDB server --- src/duckstation-qt/CMakeLists.txt | 4 + src/duckstation-qt/duckstation-qt.vcxproj | 6 ++ .../duckstation-qt.vcxproj.filters | 6 ++ src/duckstation-qt/gdbconnection.cpp | 82 +++++++++++++++++++ src/duckstation-qt/gdbconnection.h | 24 ++++++ src/duckstation-qt/gdbserver.cpp | 35 ++++++++ src/duckstation-qt/gdbserver.h | 19 +++++ src/duckstation-qt/mainwindow.cpp | 11 +++ src/duckstation-qt/mainwindow.h | 4 + 9 files changed, 191 insertions(+) create mode 100644 src/duckstation-qt/gdbconnection.cpp create mode 100644 src/duckstation-qt/gdbconnection.h create mode 100644 src/duckstation-qt/gdbserver.cpp create mode 100644 src/duckstation-qt/gdbserver.h diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index fac43c039..622793ec2 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -52,6 +52,10 @@ set(SRCS gamepropertiesdialog.cpp gamepropertiesdialog.h gamepropertiesdialog.ui + gdbconnection.cpp + gdbconnection.h + gdbserver.cpp + gdbserver.h generalsettingswidget.cpp generalsettingswidget.h generalsettingswidget.ui diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index 67b74ccda..c9ea12336 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -75,6 +75,8 @@ + + @@ -119,6 +121,8 @@ + + @@ -221,6 +225,8 @@ + + diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters index 7e0a6b93a..88296c22a 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj.filters +++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters @@ -31,6 +31,10 @@ + + + + @@ -101,6 +105,8 @@ + + diff --git a/src/duckstation-qt/gdbconnection.cpp b/src/duckstation-qt/gdbconnection.cpp new file mode 100644 index 000000000..c81181ce4 --- /dev/null +++ b/src/duckstation-qt/gdbconnection.cpp @@ -0,0 +1,82 @@ +#include "gdbconnection.h" +#include "qthostinterface.h" +#include "common/log.h" +#include "core/gdb_protocol.h" +Log_SetChannel(GDBConnection); + +GDBConnection::GDBConnection(QObject *parent, int descriptor) + : QThread(parent), m_descriptor(descriptor) +{ + Log_InfoPrintf("(%u) Accepted new connection on GDB server", m_descriptor); + + connect(&m_socket, &QTcpSocket::readyRead, this, &GDBConnection::receivedData); + connect(&m_socket, &QTcpSocket::disconnected, this, &GDBConnection::gotDisconnected); + + if (m_socket.setSocketDescriptor(m_descriptor)) { + QtHostInterface::GetInstance()->pauseSystem(true, true); + } + else { + Log_ErrorPrintf("(%u) Failed to set socket descriptor: %s", m_descriptor, m_socket.errorString().toUtf8().constData()); + } +} + +void GDBConnection::gotDisconnected() +{ + Log_InfoPrintf("(%u) Client disconnected", m_descriptor); + this->exit(0); +} + +void GDBConnection::receivedData() +{ + qint64 bytesRead; + char buffer[256]; + + while ((bytesRead = m_socket.read(buffer, sizeof(buffer))) > 0) { + for (char c : std::string_view(buffer, bytesRead)) { + m_readBuffer.push_back(c); + + if (GDBProtocol::IsPacketInterrupt(m_readBuffer)) { + Log_DebugPrintf("(%u) > Interrupt request", m_descriptor); + QtHostInterface::GetInstance()->pauseSystem(true, true); + m_readBuffer.erase(); + } + else if (GDBProtocol::IsPacketContinue(m_readBuffer)) { + Log_DebugPrintf("(%u) > Continue request", m_descriptor); + QtHostInterface::GetInstance()->pauseSystem(false, false); + m_readBuffer.erase(); + } + else if (GDBProtocol::IsPacketComplete(m_readBuffer)) { + Log_DebugPrintf("(%u) > %s", m_descriptor, m_readBuffer.c_str()); + writePacket(GDBProtocol::ProcessPacket(m_readBuffer)); + m_readBuffer.erase(); + } + } + } + if (bytesRead == -1) { + Log_ErrorPrintf("(%u) Failed to read from socket: %s", m_descriptor, m_socket.errorString().toUtf8().constData()); + } +} + +void GDBConnection::onEmulationPaused(bool paused) +{ + if (paused) { + if (m_seen_resume) { + m_seen_resume = false; + // Generate a stop reply packet, insert '?' command to generate it. + writePacket(GDBProtocol::ProcessPacket("$?#3f")); + } + } + else { + m_seen_resume = true; + // Send ack, in case GDB sent a continue request. + writePacket("+"); + } +} + +void GDBConnection::writePacket(std::string_view packet) +{ + Log_DebugPrintf("(%u) < %*s", m_descriptor, packet.length(), packet.data()); + if (m_socket.write(packet.data(), packet.length()) == -1) { + Log_ErrorPrintf("(%u) Failed to write to socket: %s", m_descriptor, m_socket.errorString().toUtf8().constData()); + } +} diff --git a/src/duckstation-qt/gdbconnection.h b/src/duckstation-qt/gdbconnection.h new file mode 100644 index 000000000..b3917ec22 --- /dev/null +++ b/src/duckstation-qt/gdbconnection.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include + +class GDBConnection : public QThread +{ + Q_OBJECT + +public: + GDBConnection(QObject *parent, int descriptor); + +public Q_SLOTS: + void gotDisconnected(); + void receivedData(); + void onEmulationPaused(bool paused); + +private: + void writePacket(std::string_view data); + + int m_descriptor; + QTcpSocket m_socket; + std::string m_readBuffer; + bool m_seen_resume; +}; diff --git a/src/duckstation-qt/gdbserver.cpp b/src/duckstation-qt/gdbserver.cpp new file mode 100644 index 000000000..970f592e5 --- /dev/null +++ b/src/duckstation-qt/gdbserver.cpp @@ -0,0 +1,35 @@ +#include "gdbserver.h" +#include "gdbconnection.h" +#include "common/log.h" +#include "qthostinterface.h" +Log_SetChannel(GDBServer); + +GDBServer::GDBServer(QObject *parent, u16 port) + : QTcpServer(parent) +{ + if (listen(QHostAddress::LocalHost, port)) { + Log_InfoPrintf("GDB server listening on TCP port %u", port); + } + else { + Log_InfoPrintf("Failed to listen on TCP port %u for GDB server: %s", port, errorString().toUtf8().constData()); + } +} + +GDBServer::~GDBServer() +{ + Log_InfoPrint("GDB server stopped"); + for (auto* thread : m_connections) { + thread->quit(); + thread->wait(); + delete thread; + } +} + +void GDBServer::incomingConnection(qintptr descriptor) +{ + Log_InfoPrint("Accepted connection on GDB server"); + GDBConnection *thread = new GDBConnection(this, descriptor); + connect(QtHostInterface::GetInstance(), &QtHostInterface::emulationPaused, thread, &GDBConnection::onEmulationPaused); + thread->start(); + m_connections.push_back(thread); +} diff --git a/src/duckstation-qt/gdbserver.h b/src/duckstation-qt/gdbserver.h new file mode 100644 index 000000000..d9cc7febf --- /dev/null +++ b/src/duckstation-qt/gdbserver.h @@ -0,0 +1,19 @@ +#pragma once +#include "core/types.h" +#include "gdbconnection.h" +#include + +class GDBServer : public QTcpServer +{ + Q_OBJECT + +public: + GDBServer(QObject* parent, u16 port); + ~GDBServer(); + +protected: + void incomingConnection(qintptr socketDescriptor) override; + +private: + std::list m_connections; +}; diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 427d8e4bd..f4fda8dea 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -11,6 +11,7 @@ #include "gamelistsettingswidget.h" #include "gamelistwidget.h" #include "gamepropertiesdialog.h" +#include "gdbserver.h" #include "memorycardeditordialog.h" #include "qtdisplaywidget.h" #include "qthostinterface.h" @@ -814,6 +815,16 @@ void MainWindow::updateEmulationActions(bool starting, bool running) } } + if (g_settings.debugging.enable_gdb_server) { + if (starting && !m_gdb_server) { + m_gdb_server = new GDBServer(this, g_settings.debugging.gdb_server_port); + } + else if (!running && m_gdb_server) { + delete m_gdb_server; + m_gdb_server = nullptr; + } + } + m_ui.statusBar->clearMessage(); } diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index a5bdc1e56..19ba30612 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -21,6 +21,8 @@ class DebuggerWindow; class HostDisplay; struct GameListEntry; +class GDBServer; + class MainWindow final : public QMainWindow { Q_OBJECT @@ -145,4 +147,6 @@ private: bool m_emulation_running = false; bool m_was_paused_by_focus_loss = false; + + GDBServer* m_gdb_server = nullptr; };