]> localhost Git - WindEmu.git/commitdiff
Initial gdbserver implementation master
authorGeorge Wright <gw@gwright.org.uk>
Fri, 13 Dec 2024 04:08:53 +0000 (20:08 -0800)
committerGeorge Wright <gw@gwright.org.uk>
Fri, 13 Dec 2024 04:09:13 +0000 (20:09 -0800)
WindCore/arm710.h
WindEmu.pro
WindGdbServer/WindGdbServer.pro [new file with mode: 0644]
WindGdbServer/debuginterface.h [new file with mode: 0644]
WindGdbServer/gdbserver.cpp [new file with mode: 0644]
WindGdbServer/gdbserver.h [new file with mode: 0644]
WindGdbServer/gdbserverthread.cpp [new file with mode: 0644]
WindGdbServer/gdbserverthread.h [new file with mode: 0644]
WindQt/WindQt.pro
WindQt/mainwindow.cpp
WindQt/mainwindow.h

index 9e4c2b88d4fff5879b6fe2380faa49d9a898ab0c..68443c98519a0589e047abb081a8bfe32d539622 100644 (file)
@@ -4,6 +4,8 @@
 #include <optional>
 #include <variant>
 
+#include "../WindGdbServer/debuginterface.h"
+
 using namespace std;
 
 // Everything I thought is a lie.
@@ -23,7 +25,7 @@ using namespace std;
 
 typedef optional<uint32_t> MaybeU32;
 
-class ARM710
+class ARM710 : public DebugInterface
 {
 public:
        enum ValueSize { V8 = 0, V16 = 1, V32 = 2 };
@@ -121,6 +123,40 @@ public:
        void setLogger(std::function<void(const char *)> newLogger) { logger = newLogger; }
        void setSerialOutput(std::function<void(const char *)> newSerialOutput) { serialOutput = newSerialOutput; }
        uint32_t lastPcExecuted() const { return pcHistory[(pcHistoryIndex - 1) % PcHistoryCount].addr; }
+
+       uint32_t readRegister(int registerNumber) override {
+               if (registerNumber == 25) {
+                       return getCPSR();
+               }
+               return getGPR(registerNumber);
+       }
+
+       void writeRegister(int registerNumber, uint32_t data) override {
+               if (registerNumber == 25) {
+                       CPSR = data;
+               } else {
+                       GPRs[registerNumber] = data;
+               }
+       }
+
+       uint8_t readMemory8(uint32_t address) {
+               auto readResult = readVirtualDebug(address, V8);
+               return readResult.has_value() ? readResult.value() : 0;
+       }
+
+       void writeMemory8(uint32_t address, uint8_t data) {
+               writeVirtual(data, address, V8);
+       }
+
+       uint32_t readMemory32(uint32_t address) {
+               auto readResult = readVirtualDebug(address, V32);
+               return readResult.has_value() ? readResult.value() : 0;
+       }
+
+       void writeMemory32(uint32_t address, uint32_t data) {
+               writeVirtual(data, address, V32);
+       }
+
 public:
        void log(const char *format, ...);
        void logPcHistory();
index 2416460362e6161243b340224cf657e600b0c9ba..bc05afc2dd4dc14c8e4bd48aebb8b1a73eb7e7f2 100644 (file)
@@ -4,6 +4,8 @@ CONFIG += console
 
 SUBDIRS += \
     WindQt \
-    WindCore
+    WindCore \
+    WindGdbServer
 
-WindQt.depends = WindCore
\ No newline at end of file
+WindQt.depends += WindCore
+WindQt.depends += WindGdbServer
\ No newline at end of file
diff --git a/WindGdbServer/WindGdbServer.pro b/WindGdbServer/WindGdbServer.pro
new file mode 100644 (file)
index 0000000..b8a57ce
--- /dev/null
@@ -0,0 +1,26 @@
+QT       += core network
+
+TARGET = WindGdbServer
+TEMPLATE = lib
+
+# The following define makes your compiler emit warnings if you use
+# any feature of Qt which has been marked as deprecated (the exact warnings
+# depend on your compiler). Please consult the documentation of the
+# deprecated API in order to know how to port your code away from it.
+DEFINES += QT_DEPRECATED_WARNINGS
+
+# You can also make your code fail to compile if you use deprecated APIs.
+# In order to do so, uncomment the following line.
+# You can also select to disable deprecated APIs only up to a certain version of Qt.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
+
+CONFIG += staticlib c++17
+QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14
+
+SOURCES += \
+        gdbserver.cpp \
+        gdbserverthread.cpp
+
+HEADERS += \
+        gdbserver.h \
+        gdbserverthread.h
diff --git a/WindGdbServer/debuginterface.h b/WindGdbServer/debuginterface.h
new file mode 100644 (file)
index 0000000..0ef3814
--- /dev/null
@@ -0,0 +1,11 @@
+#pragma once
+
+class DebugInterface {
+  public:
+    virtual uint32_t readRegister(int registerNumber) = 0;
+    virtual void writeRegister(int registerNumber, uint32_t data) = 0;
+    virtual uint8_t readMemory8(uint32_t address) = 0;
+    virtual void writeMemory8(uint32_t address, uint8_t data) = 0;
+    virtual uint32_t readMemory32(uint32_t address) = 0;
+    virtual void writeMemory32(uint32_t address, uint32_t data) = 0;
+};
diff --git a/WindGdbServer/gdbserver.cpp b/WindGdbServer/gdbserver.cpp
new file mode 100644 (file)
index 0000000..fd3e74c
--- /dev/null
@@ -0,0 +1,9 @@
+#include "gdbserver.h"
+#include "gdbserverthread.h"
+
+void GdbServer::incomingConnection(qintptr socketDescriptor)
+{
+    GdbServerThread *thread = new GdbServerThread(socketDescriptor, this, mDebugInterface);
+    connect(thread, &GdbServerThread::finished, thread, &GdbServerThread::deleteLater);
+    thread->start();
+}
diff --git a/WindGdbServer/gdbserver.h b/WindGdbServer/gdbserver.h
new file mode 100644 (file)
index 0000000..2392239
--- /dev/null
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <QTcpServer>
+#include "debuginterface.h"
+
+class GdbServer : public QTcpServer {
+    Q_OBJECT
+
+public:
+    GdbServer(QObject* parent, DebugInterface* debugInterface)
+        : QTcpServer(parent),
+          mDebugInterface(debugInterface) {}
+
+protected:
+    void incomingConnection(qintptr socketDescriptor) override;
+
+private:
+    DebugInterface* mDebugInterface;
+};
\ No newline at end of file
diff --git a/WindGdbServer/gdbserverthread.cpp b/WindGdbServer/gdbserverthread.cpp
new file mode 100644 (file)
index 0000000..2e61633
--- /dev/null
@@ -0,0 +1,238 @@
+#include "gdbserverthread.h"
+
+#include <iostream>
+
+GdbServerThread::GdbServerThread(qintptr socketDescriptor, QObject* parent, DebugInterface* debugInterface)
+    : QThread(parent),
+      mSocketDescriptor(socketDescriptor),
+      mDebugInterface(debugInterface) {}
+
+void GdbServerThread::run() {
+    QTcpSocket socket;
+
+    if (!socket.setSocketDescriptor(mSocketDescriptor)) {
+        emit error(socket.error());
+        return;
+    }
+
+    QByteArray response;
+    while (true) {
+        socket.waitForReadyRead();
+        bool receivedFullMessage = processData(socket.readAll(), response);
+        if (receivedFullMessage) {
+            sendAck(socket);
+            sendResponse(socket, response);
+        }
+    }
+}
+
+bool GdbServerThread::processData(QByteArray data, QByteArray& response) {
+    while (!data.isEmpty()) {
+        const char c = data.front();
+
+        switch (c) {
+            case '+':
+                if (mData.isEmpty()) {
+                    mData.clear();
+                } else {
+                    mData += c;
+                }
+                break;
+            case '-':
+                if (mData.isEmpty()) {
+                    printf("Received nack packet!\n");
+                } else {
+                    mData += c;
+                }
+                break;
+            case '$':
+                mData.clear();
+                mData += c;
+                break;
+            case '#':
+                mData += c;
+                // read checksum
+                mData += data[1];
+                mData += data[2];
+                response = handleMessage(mData);
+                return true;
+            default:
+                mData += c;
+                break;
+        }
+        data.remove(0, 1);
+    }
+
+    return false;
+}
+
+GdbServerThread::GdbMessage GdbServerThread::parseMessage(const QByteArray& message) {
+    GdbMessage parsedMessage;
+
+    // GDB commands are in the format (cs meaning two checksum digits):
+    // $<command>[addr,len]:[payload]#cs
+    //
+    // So e.g. $M0,1:FF#cs means
+    // Command = M (write memory at location)
+    // Arg1 (address) = 0x0
+    // Arg2 (length) = 0x1 bytes
+    // Payload = 0xFF
+
+    std::string command;
+    std::vector<std::string> arguments;
+    std::string payload;
+
+    GdbMessageSegment currentSegment = GdbMessageSegment::Unknown;
+
+    for (char c : message) {
+        switch (c) {
+            case '$':
+                currentSegment = GdbMessageSegment::Command;
+                break;
+            case ',':
+                if (currentSegment == GdbMessageSegment::Arguments) {
+                    arguments.emplace_back();
+                }
+                break;
+            case ':':
+                currentSegment = GdbMessageSegment::Payload;
+                break;
+            case '#':
+                currentSegment = GdbMessageSegment::End;
+                break;
+            default:
+                switch (currentSegment) {
+                    case GdbMessageSegment::Command:
+                        if (c >= 0x30 && c < 0x3A) {
+                            currentSegment = GdbMessageSegment::Arguments;
+                            arguments.emplace_back(1, c);
+                        } else {
+                            command += c;
+                        }
+                        break;
+                    case GdbMessageSegment::Arguments:
+                        arguments.back() += c;
+                        break;
+                    case GdbMessageSegment::Payload:
+                        payload += c;
+                        break;
+                    case GdbMessageSegment::End:
+                        // Do nothing
+                        break;
+                    default:
+                        // Should not reach here
+                        printf("Error processing message\n");
+                }
+                break;
+        }
+    }
+
+    if (command == "qSupported") {
+        parsedMessage.type = GdbMessageType::QuerySupported;
+    } else if (command == "?") {
+        parsedMessage.type = GdbMessageType::QueryHaltReason;
+    } else if (command == "qAttached") {
+        parsedMessage.type = GdbMessageType::QueryAttached;
+    } else if (command == "g") {
+        parsedMessage.type = GdbMessageType::QueryGeneralRegisters;
+    } else if (command == "p") {
+        parsedMessage.type = GdbMessageType::QueryRegister;
+    } else if (command == "m") {
+        parsedMessage.type = GdbMessageType::QueryMemory;
+    } else {
+        parsedMessage.type = GdbMessageType::Unknown;
+        return parsedMessage;
+    }
+
+    for (auto arg : arguments) {
+        parsedMessage.arguments.push_back(std::stoi(arg, nullptr, 16));
+    }
+    
+    return parsedMessage;
+}
+
+QByteArray GdbServerThread::handleMessage(const QByteArray& message) {
+    std::cout << "Received: " << message.toStdString() << std::endl;
+
+    GdbMessage parsedMessage = parseMessage(message);
+
+    switch (parsedMessage.type) {
+        case GdbMessageType::QuerySupported:
+            return "hwbreak+";
+        case GdbMessageType::QueryHaltReason:
+            return "S05";
+        case GdbMessageType::QueryAttached:
+            return "1";
+        case GdbMessageType::QueryGeneralRegisters: {
+            QByteArray response;
+            for (int i = 0; i < 16; ++i) {
+                response += dataToString(mDebugInterface->readRegister(i));
+            }
+            return response;
+        }
+        case GdbMessageType::QueryRegister:
+            return dataToString(mDebugInterface->readRegister(parsedMessage.arguments.front()));
+        case GdbMessageType::QueryMemory: {
+            uint32_t address = parsedMessage.arguments[0];
+            uint32_t length = parsedMessage.arguments[1];
+            QByteArray response;
+            while (length) {
+                if (length >= 4) {
+                    response += dataToString(mDebugInterface->readMemory32(address));
+                    length -= 4;
+                    address += 4;
+                } else {
+                    response += dataToString(mDebugInterface->readMemory8(address));
+                    length--;
+                    address++;
+                }
+            }
+            return response;
+        }
+    }
+
+    return {};
+}
+
+unsigned int GdbServerThread::generateChecksum(const QByteArray& message) {
+    unsigned int sum = 0;
+    for (const char& c : message) {
+        sum += c;
+    }
+    sum &= 0xff;
+    return sum;
+}
+
+void GdbServerThread::sendAck(QTcpSocket& socket) {
+    socket.write("+");
+}
+
+void GdbServerThread::sendResponse(QTcpSocket& socket, QByteArray& response) {
+    QString checksum = QString("#%1").arg(generateChecksum(response), 2, 16, QChar('0'));
+    response.prepend('$');
+    response.append(checksum.toUtf8());
+    std::cout << "Responding: " << response.toStdString() << std::endl;
+    socket.write(response);
+    socket.flush();    
+}
+
+QByteArray GdbServerThread::dataToString(uint32_t value) {
+    QByteArray output;
+    output.resize(8);
+    std::sprintf(output.data(),
+                 "%02x%02x%02x%02x",
+                 value & 0xFF,
+                 value >> 8 & 0xFF,
+                 value >> 16 & 0xFF,
+                 value >> 24 & 0xFF);
+    return output;
+}
+
+QByteArray GdbServerThread::dataToString(uint8_t value) {
+    QByteArray output;
+    output.resize(2);
+    std::sprintf(output.data(),
+                 "%02x",
+                 value & 0xFF);
+    return output;
+}
diff --git a/WindGdbServer/gdbserverthread.h b/WindGdbServer/gdbserverthread.h
new file mode 100644 (file)
index 0000000..23ec282
--- /dev/null
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <QTcpSocket>
+#include <QThread>
+
+#include "debuginterface.h"
+
+class GdbServerThread : public QThread
+{
+    Q_OBJECT
+
+public:
+    GdbServerThread(qintptr socketDescriptor, QObject *parent, DebugInterface* debugInterface);
+
+    void run() override;
+
+signals:
+    void error(QTcpSocket::SocketError socketError);
+
+private:
+    bool processData(QByteArray data, QByteArray& response);
+    QByteArray handleMessage(const QByteArray& message);
+    unsigned int generateChecksum(const QByteArray& message);
+    void sendAck(QTcpSocket& socket);
+    void sendResponse(QTcpSocket& socket, QByteArray& response);
+
+    QByteArray dataToString(uint32_t value);
+    QByteArray dataToString(uint8_t value);
+
+    enum class GdbMessageType {
+        Unknown,
+        QuerySupported,
+        QueryHaltReason,
+        QueryAttached,
+        QueryGeneralRegisters,
+        QueryRegister,
+        QueryMemory
+    };
+
+    enum class GdbMessageSegment {
+        Unknown,
+        Command,
+        Arguments,
+        Payload,
+        End
+    };
+
+    struct GdbMessage {
+        GdbMessageType type;
+        std::vector<uint32_t> arguments;
+    };
+
+    GdbMessage parseMessage(const QByteArray& message);
+
+    qintptr mSocketDescriptor;
+    QByteArray mData;
+    DebugInterface* mDebugInterface;
+};
\ No newline at end of file
index a1b13fad56e35acb48983fd81ccab6b4edbe019f..9aa013567bf352327fbbfc5758d226e635568f65 100644 (file)
@@ -4,7 +4,7 @@
 #
 #-------------------------------------------------
 
-QT       += core gui widgets
+QT       += core network gui widgets
 
 TARGET = WindQt
 TEMPLATE = app
@@ -40,9 +40,9 @@ qnx: target.path = /tmp/$${TARGET}/bin
 else: unix:!android: target.path = /opt/$${TARGET}/bin
 !isEmpty(target.path): INSTALLS += target
 
-win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../WindCore/release/ -lWindCore
-else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../WindCore/debug/ -lWindCore
-else:unix: LIBS += -L$$OUT_PWD/../WindCore/ -lWindCore
+win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../WindCore/release/ -L$$OUT_PWD/../WindGdbServer/release/ -lWindCore -lWindGdbServer
+else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../WindCore/debug/ -L$$OUT_PWD/../WindGdbServer/debug/ -lWindCore -lWindGdbServer
+else:unix: LIBS += -L$$OUT_PWD/../WindCore/ -L$$OUT_PWD/../WindGdbServer/ -lWindCore -lWindGdbServer
 
 INCLUDEPATH += $$PWD/../WindCore
 DEPENDPATH += $$PWD/../WindCore
index f6a802345114d4d0a8ccfa86413931fe747994f9..c699ea55956229e7a8cd2aaec15d1ca958fb802d 100644 (file)
@@ -38,6 +38,10 @@ MainWindow::MainWindow(EmuBase *emu, QWidget *parent) :
        pdaScreen.show();
 
        updateScreen();
+
+       gdbServer = new GdbServer(this, emu);
+       gdbServer->listen(QHostAddress::LocalHost, 2159);
+
 }
 
 MainWindow::~MainWindow()
index 636374abf88a9361886924e9d8e2582cfb416283..100f725048d3697d1a084af70eaf0f7cbc251fa0 100644 (file)
@@ -5,7 +5,7 @@
 #include <QElapsedTimer>
 #include "../WindCore/emubase.h"
 #include "pdascreenwindow.h"
-
+#include "../WindGdbServer/gdbserver.h"
 namespace Ui {
 class MainWindow;
 }
@@ -50,6 +50,7 @@ private:
        PDAScreenWindow pdaScreen;
        EmuBase *emu;
     QTimer *timer;
+    GdbServer* gdbServer;
     void updateScreen();
     void updateBreakpointsList();
     void updateMemory();