From: George Wright Date: Fri, 13 Dec 2024 04:08:53 +0000 (-0800) Subject: Initial gdbserver implementation X-Git-Url: http://git.gwright.org.uk/?a=commitdiff_plain;h=HEAD;p=WindEmu.git Initial gdbserver implementation --- diff --git a/WindCore/arm710.h b/WindCore/arm710.h index 9e4c2b8..68443c9 100644 --- a/WindCore/arm710.h +++ b/WindCore/arm710.h @@ -4,6 +4,8 @@ #include #include +#include "../WindGdbServer/debuginterface.h" + using namespace std; // Everything I thought is a lie. @@ -23,7 +25,7 @@ using namespace std; typedef optional 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 newLogger) { logger = newLogger; } void setSerialOutput(std::function 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(); diff --git a/WindEmu.pro b/WindEmu.pro index 2416460..bc05afc 100644 --- a/WindEmu.pro +++ b/WindEmu.pro @@ -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 index 0000000..b8a57ce --- /dev/null +++ b/WindGdbServer/WindGdbServer.pro @@ -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 index 0000000..0ef3814 --- /dev/null +++ b/WindGdbServer/debuginterface.h @@ -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 index 0000000..fd3e74c --- /dev/null +++ b/WindGdbServer/gdbserver.cpp @@ -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 index 0000000..2392239 --- /dev/null +++ b/WindGdbServer/gdbserver.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#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 index 0000000..2e61633 --- /dev/null +++ b/WindGdbServer/gdbserverthread.cpp @@ -0,0 +1,238 @@ +#include "gdbserverthread.h" + +#include + +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): + // $[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 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 index 0000000..23ec282 --- /dev/null +++ b/WindGdbServer/gdbserverthread.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#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 arguments; + }; + + GdbMessage parseMessage(const QByteArray& message); + + qintptr mSocketDescriptor; + QByteArray mData; + DebugInterface* mDebugInterface; +}; \ No newline at end of file diff --git a/WindQt/WindQt.pro b/WindQt/WindQt.pro index a1b13fa..9aa0135 100644 --- a/WindQt/WindQt.pro +++ b/WindQt/WindQt.pro @@ -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 diff --git a/WindQt/mainwindow.cpp b/WindQt/mainwindow.cpp index f6a8023..c699ea5 100644 --- a/WindQt/mainwindow.cpp +++ b/WindQt/mainwindow.cpp @@ -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() diff --git a/WindQt/mainwindow.h b/WindQt/mainwindow.h index 636374a..100f725 100644 --- a/WindQt/mainwindow.h +++ b/WindQt/mainwindow.h @@ -5,7 +5,7 @@ #include #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();