From 7810e68a5839d12b9b9a50e669e6a940fccdd449 Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Boric <jblbeurope@gmail.com>
Date: Sat, 25 Feb 2023 17:27:03 +0100
Subject: [PATCH 1/2] Qt: Run GDB server on emulation thread

---
 src/duckstation-qt/gdbconnection.cpp | 55 +++++++++++++++-------------
 src/duckstation-qt/gdbconnection.h   |  9 +++--
 src/duckstation-qt/gdbserver.cpp     | 49 ++++++++++++++++---------
 src/duckstation-qt/gdbserver.h       |  9 +++--
 src/duckstation-qt/mainwindow.cpp    | 17 +++------
 src/duckstation-qt/mainwindow.h      |  4 --
 src/duckstation-qt/qthost.cpp        |  3 ++
 src/duckstation-qt/qthost.h          |  2 +
 8 files changed, 82 insertions(+), 66 deletions(-)

diff --git a/src/duckstation-qt/gdbconnection.cpp b/src/duckstation-qt/gdbconnection.cpp
index 2be99414b..f3f91d878 100644
--- a/src/duckstation-qt/gdbconnection.cpp
+++ b/src/duckstation-qt/gdbconnection.cpp
@@ -7,28 +7,31 @@
 #include "qthost.h"
 Log_SetChannel(GDBConnection);
 
-GDBConnection::GDBConnection(QObject* parent, int descriptor) : QThread(parent), m_descriptor(descriptor)
+GDBConnection::GDBConnection(GDBServer* parent, intptr_t descriptor) : QTcpSocket(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))
+  if (!setSocketDescriptor(descriptor))
   {
-    g_emu_thread->setSystemPaused(true, true);
-  }
-  else
-  {
-    Log_ErrorPrintf("(%u) Failed to set socket descriptor: %s", m_descriptor,
-                    m_socket.errorString().toUtf8().constData());
+    Log_ErrorPrintf("(%" PRIdPTR ") Failed to set socket descriptor: %s", descriptor,
+                    errorString().toUtf8().constData());
+    deleteLater();
+    return;
   }
+
+  connect(g_emu_thread, &EmuThread::systemPaused, this, &GDBConnection::onEmulationPaused);
+  connect(g_emu_thread, &EmuThread::systemResumed, this, &GDBConnection::onEmulationResumed);
+  connect(this, &QTcpSocket::readyRead, this, &GDBConnection::receivedData);
+  connect(this, &QTcpSocket::disconnected, this, &GDBConnection::gotDisconnected);
+
+  Log_InfoPrintf("(%" PRIdPTR ") Client connected", m_descriptor);
+
+  m_seen_resume = System::IsPaused();
+  g_emu_thread->setSystemPaused(true);
 }
 
 void GDBConnection::gotDisconnected()
 {
-  Log_InfoPrintf("(%u) Client disconnected", m_descriptor);
-  this->exit(0);
+  Log_InfoPrintf("(%" PRIdPTR ") Client disconnected", m_descriptor);
+  deleteLater();
 }
 
 void GDBConnection::receivedData()
@@ -36,7 +39,7 @@ void GDBConnection::receivedData()
   qint64 bytesRead;
   char buffer[256];
 
-  while ((bytesRead = m_socket.read(buffer, sizeof(buffer))) > 0)
+  while ((bytesRead = read(buffer, sizeof(buffer))) > 0)
   {
     for (char c : std::string_view(buffer, bytesRead))
     {
@@ -44,19 +47,19 @@ void GDBConnection::receivedData()
 
       if (GDBProtocol::IsPacketInterrupt(m_readBuffer))
       {
-        Log_DebugPrintf("(%u) > Interrupt request", m_descriptor);
-        g_emu_thread->setSystemPaused(true, true);
+        Log_DebugPrintf("(%" PRIdPTR ") > Interrupt request", m_descriptor);
+        g_emu_thread->setSystemPaused(true);
         m_readBuffer.erase();
       }
       else if (GDBProtocol::IsPacketContinue(m_readBuffer))
       {
-        Log_DebugPrintf("(%u) > Continue request", m_descriptor);
-        g_emu_thread->setSystemPaused(false, false);
+        Log_DebugPrintf("(%" PRIdPTR ") > Continue request", m_descriptor);
+        g_emu_thread->setSystemPaused(false);
         m_readBuffer.erase();
       }
       else if (GDBProtocol::IsPacketComplete(m_readBuffer))
       {
-        Log_DebugPrintf("(%u) > %s", m_descriptor, m_readBuffer.c_str());
+        Log_DebugPrintf("(%" PRIdPTR ") > %s", m_descriptor, m_readBuffer.c_str());
         writePacket(GDBProtocol::ProcessPacket(m_readBuffer));
         m_readBuffer.erase();
       }
@@ -64,7 +67,8 @@ void GDBConnection::receivedData()
   }
   if (bytesRead == -1)
   {
-    Log_ErrorPrintf("(%u) Failed to read from socket: %s", m_descriptor, m_socket.errorString().toUtf8().constData());
+    Log_ErrorPrintf("(%" PRIdPTR ") Failed to read from socket: %s", m_descriptor,
+                    errorString().toUtf8().constData());
   }
 }
 
@@ -87,9 +91,10 @@ void GDBConnection::onEmulationResumed()
 
 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_DebugPrintf("(%" PRIdPTR ") < %*s", m_descriptor, packet.length(), packet.data());
+  if (write(packet.data(), packet.length()) == -1)
   {
-    Log_ErrorPrintf("(%u) Failed to write to socket: %s", m_descriptor, m_socket.errorString().toUtf8().constData());
+    Log_ErrorPrintf("(%" PRIdPTR ") Failed to write to socket: %s", m_descriptor,
+                    errorString().toUtf8().constData());
   }
 }
diff --git a/src/duckstation-qt/gdbconnection.h b/src/duckstation-qt/gdbconnection.h
index 0cea44fec..cea8ced8c 100644
--- a/src/duckstation-qt/gdbconnection.h
+++ b/src/duckstation-qt/gdbconnection.h
@@ -5,12 +5,14 @@
 #include <QtCore/QThread>
 #include <QtNetwork/QTcpSocket>
 
-class GDBConnection : public QThread
+class GDBServer;
+
+class GDBConnection : public QTcpSocket
 {
   Q_OBJECT
 
 public:
-  GDBConnection(QObject *parent, int descriptor);
+  GDBConnection(GDBServer *parent, intptr_t descriptor);
 
 public Q_SLOTS:
   void gotDisconnected();
@@ -21,8 +23,7 @@ public Q_SLOTS:
 private:
   void writePacket(std::string_view data);
 
-  int m_descriptor;
-  QTcpSocket m_socket;
+  intptr_t m_descriptor;
   std::string m_readBuffer;
   bool m_seen_resume;
 };
diff --git a/src/duckstation-qt/gdbserver.cpp b/src/duckstation-qt/gdbserver.cpp
index f47ef1dbe..43e717d0c 100644
--- a/src/duckstation-qt/gdbserver.cpp
+++ b/src/duckstation-qt/gdbserver.cpp
@@ -7,33 +7,46 @@
 #include "qthost.h"
 Log_SetChannel(GDBServer);
 
-GDBServer::GDBServer(QObject *parent, u16 port)
+GDBServer::GDBServer(QObject *parent)
     : 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;
+  stop();
+}
+
+void GDBServer::start(quint16 port) {
+  if (isListening())
+  {
+    return;
+  }
+
+  if (!listen(QHostAddress::LocalHost, port))
+  {
+    Log_ErrorPrintf("Failed to listen on TCP port %u for GDB server: %s", port,
+                    errorString().toUtf8().constData());
+    return;
+  }
+
+  Log_InfoPrintf("GDB server listening on TCP port %u", port);
+}
+
+void GDBServer::stop()
+{
+  if (isListening())
+  {
+    close();
+    Log_InfoPrint("GDB server stopped");
+  }
+
+  for (QObject* connection : children()) {
+    connection->deleteLater();
   }
 }
 
 void GDBServer::incomingConnection(qintptr descriptor)
 {
-  Log_InfoPrint("Accepted connection on GDB server");
-  GDBConnection *thread = new GDBConnection(this, descriptor);
-  connect(g_emu_thread, &EmuThread::systemPaused, thread, &GDBConnection::onEmulationPaused);
-  connect(g_emu_thread, &EmuThread::systemResumed, thread, &GDBConnection::onEmulationResumed);
-  thread->start();
-  m_connections.push_back(thread);
+  new GDBConnection(this, descriptor);
 }
diff --git a/src/duckstation-qt/gdbserver.h b/src/duckstation-qt/gdbserver.h
index 5407e31b8..5da9f432a 100644
--- a/src/duckstation-qt/gdbserver.h
+++ b/src/duckstation-qt/gdbserver.h
@@ -11,12 +11,13 @@ class GDBServer : public QTcpServer
   Q_OBJECT
 
 public:
-  GDBServer(QObject* parent, u16 port);
+  GDBServer(QObject* parent = nullptr);
   ~GDBServer();
 
+public Q_SLOTS:
+  void start(quint16 port);
+  void stop();
+
 protected:
   void incomingConnection(qintptr socketDescriptor) override;
-
-private:
-  std::list<GDBConnection*> m_connections;
 };
diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp
index 74389cff8..eb0c6d152 100644
--- a/src/duckstation-qt/mainwindow.cpp
+++ b/src/duckstation-qt/mainwindow.cpp
@@ -21,7 +21,6 @@
 #include "frontend-common/platform_misc.h"
 #include "gamelistsettingswidget.h"
 #include "gamelistwidget.h"
-#include "gdbserver.h"
 #include "memorycardeditordialog.h"
 #include "qthost.h"
 #include "qtutils.h"
@@ -1723,17 +1722,13 @@ void MainWindow::updateEmulationActions(bool starting, bool running, bool cheevo
   if ((!starting && !running) || running)
     m_open_debugger_on_start = false;
 
-  if (g_settings.debugging.enable_gdb_server)
+  if (!g_gdb_server->isListening() && g_settings.debugging.enable_gdb_server && starting)
   {
-    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;
-    }
+    QMetaObject::invokeMethod(g_gdb_server, "start", Qt::QueuedConnection, Q_ARG(quint16, g_settings.debugging.gdb_server_port));
+  }
+  else if (g_gdb_server->isListening() && !running)
+  {
+    QMetaObject::invokeMethod(g_gdb_server, "stop", Qt::QueuedConnection);
   }
 
   m_ui.statusBar->clearMessage();
diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h
index ec1aa123f..279f6625a 100644
--- a/src/duckstation-qt/mainwindow.h
+++ b/src/duckstation-qt/mainwindow.h
@@ -33,8 +33,6 @@ namespace GameList {
 struct Entry;
 }
 
-class GDBServer;
-
 class MainWindow final : public QMainWindow
 {
   Q_OBJECT
@@ -286,8 +284,6 @@ private:
   bool m_was_disc_change_request = false;
   bool m_is_closing = false;
 
-  GDBServer* m_gdb_server = nullptr;
-
 #ifdef _WIN32
   void* m_device_notification_handle = nullptr;
 #endif
diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp
index fd235b618..3a7fd50ad 100644
--- a/src/duckstation-qt/qthost.cpp
+++ b/src/duckstation-qt/qthost.cpp
@@ -98,6 +98,7 @@ static bool s_start_fullscreen_ui = false;
 static bool s_start_fullscreen_ui_fullscreen = false;
 
 EmuThread* g_emu_thread;
+GDBServer* g_gdb_server;
 
 EmuThread::EmuThread(QThread* ui_thread) : QThread(), m_ui_thread(ui_thread) {}
 
@@ -1373,6 +1374,8 @@ void EmuThread::start()
   AssertMsg(!g_emu_thread, "Emu thread does not exist");
 
   g_emu_thread = new EmuThread(QThread::currentThread());
+  g_gdb_server = new GDBServer();
+  g_gdb_server->moveToThread(g_emu_thread);
   g_emu_thread->QThread::start();
   g_emu_thread->m_started_semaphore.acquire();
   g_emu_thread->moveToThread(g_emu_thread);
diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h
index cd0bac444..d45be2460 100644
--- a/src/duckstation-qt/qthost.h
+++ b/src/duckstation-qt/qthost.h
@@ -10,6 +10,7 @@
 #include "frontend-common/common_host.h"
 #include "frontend-common/game_list.h"
 #include "frontend-common/input_manager.h"
+#include "gdbserver.h"
 #include "qtutils.h"
 #include <QtCore/QByteArray>
 #include <QtCore/QMetaType>
@@ -235,6 +236,7 @@ private:
 };
 
 extern EmuThread* g_emu_thread;
+extern GDBServer* g_gdb_server;
 
 namespace QtHost {
 /// Sets batch mode (exit after game shutdown).

From d65fb0e86a40b3eb4f8a4127379d420fbca14d33 Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Boric <jblbeurope@gmail.com>
Date: Sat, 25 Feb 2023 17:46:37 +0100
Subject: [PATCH 2/2] Qt: Add toggle to enable GDB server

---
 src/duckstation-qt/mainwindow.cpp | 1 +
 src/duckstation-qt/mainwindow.ui  | 9 +++++++++
 2 files changed, 10 insertions(+)

diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp
index eb0c6d152..2e442ccea 100644
--- a/src/duckstation-qt/mainwindow.cpp
+++ b/src/duckstation-qt/mainwindow.cpp
@@ -1972,6 +1972,7 @@ void MainWindow::connectSignals()
   connect(m_ui.actionCoverDownloader, &QAction::triggered, this, &MainWindow::onToolsCoverDownloaderTriggered);
   connect(m_ui.actionCheatManager, &QAction::triggered, this, &MainWindow::onToolsCheatManagerTriggered);
   connect(m_ui.actionCPUDebugger, &QAction::triggered, this, &MainWindow::openCPUDebugger);
+  SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableGDBServer, "Debug", "EnableGDBServer", false);
   connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
   connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
   connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() {
diff --git a/src/duckstation-qt/mainwindow.ui b/src/duckstation-qt/mainwindow.ui
index 03ad4266a..98248d624 100644
--- a/src/duckstation-qt/mainwindow.ui
+++ b/src/duckstation-qt/mainwindow.ui
@@ -183,6 +183,7 @@
     <addaction name="actionForceNTSCTimings"/>
     <addaction name="separator"/>
     <addaction name="actionCPUDebugger"/>
+    <addaction name="actionEnableGDBServer"/>
     <addaction name="separator"/>
     <addaction name="actionDumpRAM"/>
     <addaction name="actionDumpVRAM"/>
@@ -865,6 +866,14 @@
     <string>CPU D&amp;ebugger</string>
    </property>
   </action>
+  <action name="actionEnableGDBServer">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>Enable GDB server</string>
+   </property>
+  </action>
   <action name="actionViewGameGrid">
    <property name="icon">
     <iconset theme="function-line">