#include <optional>
#include <variant>
+#include "../WindGdbServer/debuginterface.h"
+
using namespace std;
// Everything I thought is a lie.
typedef optional<uint32_t> MaybeU32;
-class ARM710
+class ARM710 : public DebugInterface
{
public:
enum ValueSize { V8 = 0, V16 = 1, V32 = 2 };
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();
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
--- /dev/null
+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
--- /dev/null
+#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;
+};
--- /dev/null
+#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();
+}
--- /dev/null
+#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
--- /dev/null
+#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;
+}
--- /dev/null
+#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
#
#-------------------------------------------------
-QT += core gui widgets
+QT += core network gui widgets
TARGET = WindQt
TEMPLATE = app
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
pdaScreen.show();
updateScreen();
+
+ gdbServer = new GdbServer(this, emu);
+ gdbServer->listen(QHostAddress::LocalHost, 2159);
+
}
MainWindow::~MainWindow()
#include <QElapsedTimer>
#include "../WindCore/emubase.h"
#include "pdascreenwindow.h"
-
+#include "../WindGdbServer/gdbserver.h"
namespace Ui {
class MainWindow;
}
PDAScreenWindow pdaScreen;
EmuBase *emu;
QTimer *timer;
+ GdbServer* gdbServer;
void updateScreen();
void updateBreakpointsList();
void updateMemory();