From 6f697744d14d3b950b537fe4591db76a08ef50cb Mon Sep 17 00:00:00 2001 From: aldelaro5 Date: Thu, 30 Dec 2021 16:54:24 -0500 Subject: [PATCH] GDB Stub: allow to override GDB's handling logic of write watchpoint Also allow to break on nonmodifying writes. --- include/mgba/internal/debugger/gdb-stub.h | 10 +++++++- src/debugger/debugger.c | 2 +- src/debugger/gdb-stub.c | 22 ++++++++++------- src/platform/qt/GDBController.cpp | 6 ++++- src/platform/qt/GDBController.h | 2 ++ src/platform/qt/GDBWindow.cpp | 30 +++++++++++++++++++++++ src/platform/qt/GDBWindow.h | 4 +++ 7 files changed, 64 insertions(+), 12 deletions(-) diff --git a/include/mgba/internal/debugger/gdb-stub.h b/include/mgba/internal/debugger/gdb-stub.h index a617bc558..b76b52452 100644 --- a/include/mgba/internal/debugger/gdb-stub.h +++ b/include/mgba/internal/debugger/gdb-stub.h @@ -24,6 +24,12 @@ enum GDBStubAckState { GDB_ACK_OFF }; +enum GDBWatchpointsBehvaior { + GDB_WATCHPOINT_STANDARD_LOGIC = 0, + GDB_WATCHPOINT_OVERRIDE_LOGIC, + GDB_WATCHPOINT_OVERRIDE_LOGIC_ANY_WRITE, +}; + struct GDBStub { struct mDebugger d; @@ -40,10 +46,12 @@ struct GDBStub { bool supportsSwbreak; bool supportsHwbreak; + + enum GDBWatchpointsBehvaior watchpointsBehavior; }; void GDBStubCreate(struct GDBStub*); -bool GDBStubListen(struct GDBStub*, int port, const struct Address* bindAddress); +bool GDBStubListen(struct GDBStub*, int port, const struct Address* bindAddress, enum GDBWatchpointsBehvaior watchpointsBehavior); void GDBStubHangup(struct GDBStub*); void GDBStubShutdown(struct GDBStub*); diff --git a/src/debugger/debugger.c b/src/debugger/debugger.c index bdf7b1cab..1c6ecd7ef 100644 --- a/src/debugger/debugger.c +++ b/src/debugger/debugger.c @@ -53,7 +53,7 @@ struct mDebugger* mDebuggerCreate(enum mDebuggerType type, struct mCore* core) { case DEBUGGER_GDB: #ifdef USE_GDB_STUB GDBStubCreate(&debugger->gdb); - GDBStubListen(&debugger->gdb, 2345, 0); + GDBStubListen(&debugger->gdb, 2345, 0, GDB_WATCHPOINT_STANDARD_LOGIC); break; #endif case DEBUGGER_NONE: diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index 7069c7f75..5526aa0b2 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -79,15 +79,18 @@ static void _gdbStubEntered(struct mDebugger* debugger, enum mDebuggerEntryReaso case DEBUGGER_ENTER_WATCHPOINT: if (info) { const char* type = 0; + + if (stub->watchpointsBehavior != GDB_WATCHPOINT_STANDARD_LOGIC && info->type.wp.watchType & WATCHPOINT_WRITE) { + // We send S05 instead of T05watch because it bypasses GDB's internal logic to check if + // the value changed and to bypass a step into by GDB. This allows to control the change + // logic even when using savestates which we already handle in the core debugger logic + snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); + _sendMessage(stub); + return; + } + switch (info->type.wp.watchType) { case WATCHPOINT_WRITE: - if (info->type.wp.newValue == info->type.wp.oldValue) { - if (stub->d.state == DEBUGGER_PAUSED) { - stub->d.state = DEBUGGER_RUNNING; - } - return; - } - // Fall through case WATCHPOINT_WRITE_CHANGE: type = "watch"; break; @@ -575,7 +578,7 @@ static void _setBreakpoint(struct GDBStub* stub, const char* message) { stub->d.platform->setBreakpoint(stub->d.platform, &breakpoint); break; case '2': - watchpoint.type = WATCHPOINT_WRITE_CHANGE; + watchpoint.type = stub->watchpointsBehavior == GDB_WATCHPOINT_OVERRIDE_LOGIC_ANY_WRITE ? WATCHPOINT_WRITE : WATCHPOINT_WRITE_CHANGE; stub->d.platform->setWatchpoint(stub->d.platform, &watchpoint); break; case '3': @@ -763,7 +766,7 @@ void GDBStubCreate(struct GDBStub* stub) { stub->shouldBlock = false; } -bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAddress) { +bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAddress, enum GDBWatchpointsBehvaior watchpointsBehavior) { if (!SOCKET_FAILED(stub->socket)) { GDBStubShutdown(stub); } @@ -780,6 +783,7 @@ bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAdd goto cleanup; } + stub->watchpointsBehavior = watchpointsBehavior; memset(stub->memoryMapXml, 0, GDB_STUB_MAX_LINE); return true; diff --git a/src/platform/qt/GDBController.cpp b/src/platform/qt/GDBController.cpp index 86ae0dc45..5fd2a1768 100644 --- a/src/platform/qt/GDBController.cpp +++ b/src/platform/qt/GDBController.cpp @@ -32,8 +32,12 @@ void GDBController::setBindAddress(const Address& address) { m_bindAddress = address; } +void GDBController::setWatchpointsBehavior(int watchpointsBehaviorId) { + m_watchpointsBehavior = static_cast(watchpointsBehaviorId); +} + void GDBController::listen() { - if (GDBStubListen(&m_gdbStub, m_port, &m_bindAddress)) { + if (GDBStubListen(&m_gdbStub, m_port, &m_bindAddress, m_watchpointsBehavior)) { if (!isAttached()) { attach(); } diff --git a/src/platform/qt/GDBController.h b/src/platform/qt/GDBController.h index 03619fe87..d83f00028 100644 --- a/src/platform/qt/GDBController.h +++ b/src/platform/qt/GDBController.h @@ -28,6 +28,7 @@ public: public slots: void setPort(ushort port); void setBindAddress(const Address&); + void setWatchpointsBehavior(int watchpointsBehaviorId); void listen(); signals: @@ -41,6 +42,7 @@ private: ushort m_port = 2345; Address m_bindAddress; + enum GDBWatchpointsBehvaior m_watchpointsBehavior = GDB_WATCHPOINT_STANDARD_LOGIC; }; } diff --git a/src/platform/qt/GDBWindow.cpp b/src/platform/qt/GDBWindow.cpp index b719c324d..8f1c44fe8 100644 --- a/src/platform/qt/GDBWindow.cpp +++ b/src/platform/qt/GDBWindow.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "GDBWindow.h" +#include #include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include #include #include "GDBController.h" @@ -47,6 +49,28 @@ GDBWindow::GDBWindow(GDBController* controller, QWidget* parent) connect(m_bindAddressEdit, &QLineEdit::textChanged, this, &GDBWindow::bindAddressChanged); settingsGrid->addWidget(m_bindAddressEdit, 1, 1, Qt::AlignLeft); + QGroupBox* watchpointsSettings = new QGroupBox(tr("Write watchpoints behavior")); + mainSegment->addWidget(watchpointsSettings); + + QVBoxLayout* watchpointsSettingsLayout = new QVBoxLayout; + watchpointsSettings->setLayout(watchpointsSettingsLayout); + + QButtonGroup* watchpointsButtonGroup = new QButtonGroup(watchpointsSettings); + connect(watchpointsButtonGroup, &QButtonGroup::idClicked, controller, &GDBController::setWatchpointsBehavior); + + m_watchpointsStandardRadio = new QRadioButton(tr("Standard GDB"), watchpointsSettings); + m_watchpointsStandardRadio->setChecked(true); + watchpointsButtonGroup->addButton(m_watchpointsStandardRadio, GDB_WATCHPOINT_STANDARD_LOGIC); + watchpointsSettingsLayout->addWidget(m_watchpointsStandardRadio); + + m_watchpointsOverrideLogicRadio = new QRadioButton(tr("Internal change detection"), watchpointsSettings); + watchpointsButtonGroup->addButton(m_watchpointsOverrideLogicRadio, GDB_WATCHPOINT_OVERRIDE_LOGIC); + watchpointsSettingsLayout->addWidget(m_watchpointsOverrideLogicRadio); + + m_watchpointsOverrideLogicAnyWriteRadio = new QRadioButton(tr("Break on all writes"), watchpointsSettings); + watchpointsButtonGroup->addButton(m_watchpointsOverrideLogicAnyWriteRadio, GDB_WATCHPOINT_OVERRIDE_LOGIC_ANY_WRITE); + watchpointsSettingsLayout->addWidget(m_watchpointsOverrideLogicAnyWriteRadio); + QHBoxLayout* buttons = new QHBoxLayout; m_startStopButton = new QPushButton; @@ -92,6 +116,9 @@ void GDBWindow::bindAddressChanged(const QString& bindAddressString) { void GDBWindow::started() { m_portEdit->setEnabled(false); m_bindAddressEdit->setEnabled(false); + m_watchpointsStandardRadio->setEnabled(false); + m_watchpointsOverrideLogicRadio->setEnabled(false); + m_watchpointsOverrideLogicAnyWriteRadio->setEnabled(false); m_startStopButton->setText(tr("Stop")); m_breakButton->setEnabled(true); disconnect(m_startStopButton, &QAbstractButton::clicked, m_gdbController, &GDBController::listen); @@ -102,6 +129,9 @@ void GDBWindow::started() { void GDBWindow::stopped() { m_portEdit->setEnabled(true); m_bindAddressEdit->setEnabled(true); + m_watchpointsStandardRadio->setEnabled(true); + m_watchpointsOverrideLogicRadio->setEnabled(true); + m_watchpointsOverrideLogicAnyWriteRadio->setEnabled(true); m_startStopButton->setText(tr("Start")); m_breakButton->setEnabled(false); disconnect(m_startStopButton, &QAbstractButton::clicked, m_gdbController, &DebuggerController::detach); diff --git a/src/platform/qt/GDBWindow.h b/src/platform/qt/GDBWindow.h index f185a7df7..929520d96 100644 --- a/src/platform/qt/GDBWindow.h +++ b/src/platform/qt/GDBWindow.h @@ -9,6 +9,7 @@ class QLineEdit; class QPushButton; +class QRadioButton; namespace QGBA { @@ -34,6 +35,9 @@ private: QLineEdit* m_portEdit; QLineEdit* m_bindAddressEdit; + QRadioButton* m_watchpointsStandardRadio; + QRadioButton* m_watchpointsOverrideLogicRadio; + QRadioButton* m_watchpointsOverrideLogicAnyWriteRadio; QPushButton* m_startStopButton; QPushButton* m_breakButton; };