Qt: Add debugger REPL
Jeffrey Pfau jeffrey@endrift.com
Wed, 26 Oct 2016 21:44:30 -0700
11 files changed,
328 insertions(+),
9 deletions(-)
jump to
M
src/platform/qt/CMakeLists.txt
→
src/platform/qt/CMakeLists.txt
@@ -76,6 +76,8 @@ CheatsModel.cpp
CheatsView.cpp ConfigController.cpp DebuggerController.cpp + DebuggerREPL.cpp + DebuggerREPLController.cpp Display.cpp DisplayGL.cpp DisplayQt.cpp@@ -119,6 +121,7 @@ AboutScreen.ui
ArchiveInspector.ui AssetTile.ui CheatsView.ui + DebuggerREPL.ui GIFView.ui IOViewer.ui LoadSaveState.ui
M
src/platform/qt/DebuggerController.cpp
→
src/platform/qt/DebuggerController.cpp
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2016 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this@@ -25,6 +25,7 @@ if (isAttached()) {
return; } if (m_gameController->isLoaded()) { + attachInternal(); m_gameController->setDebugger(m_debugger); mDebuggerEnter(m_debugger, DEBUGGER_ENTER_ATTACHED, 0); } else {@@ -58,6 +59,10 @@ return;
} GameController::Interrupter interrupter(m_gameController); shutdownInternal(); +} + +void DebuggerController::attachInternal() { + // No default implementation } void DebuggerController::shutdownInternal() {
M
src/platform/qt/DebuggerController.h
→
src/platform/qt/DebuggerController.h
@@ -30,6 +30,7 @@ virtual void breakInto();
virtual void shutdown(); protected: + virtual void attachInternal(); virtual void shutdownInternal(); mDebugger* const m_debugger;
A
src/platform/qt/DebuggerREPL.cpp
@@ -0,0 +1,42 @@
+/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "DebuggerREPL.h" + +#include "DebuggerREPLController.h" + +#include <QScrollBar> + +using namespace QGBA; + +DebuggerREPL::DebuggerREPL(DebuggerREPLController* controller, QWidget* parent) + : QWidget(parent) + , m_replController(controller) +{ + m_ui.setupUi(this); + + connect(m_ui.prompt, SIGNAL(returnPressed()), this, SLOT(postLine())); + connect(controller, SIGNAL(log(const QString&)), this, SLOT(log(const QString&))); + connect(m_ui.breakpoint, SIGNAL(clicked()), controller, SLOT(breakInto())); + + controller->attach(); +} + +void DebuggerREPL::log(const QString& line) { + m_ui.log->moveCursor(QTextCursor::End); + m_ui.log->insertPlainText(line); + m_ui.log->verticalScrollBar()->setValue(m_ui.log->verticalScrollBar()->maximum()); +} + +void DebuggerREPL::postLine() { + QString line = m_ui.prompt->text(); + m_ui.prompt->clear(); + if (line.isEmpty()) { + m_replController->enterLine(QString("\n")); + } else { + log(QString("> %1\n").arg(line)); + m_replController->enterLine(line); + } +}
A
src/platform/qt/DebuggerREPL.h
@@ -0,0 +1,33 @@
+/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_DEBUGGER_REPL +#define QGBA_DEBUGGER_REPL + +#include "ui_DebuggerREPL.h" + +namespace QGBA { + +class DebuggerREPLController; + +class DebuggerREPL : public QWidget { +Q_OBJECT + +public: + DebuggerREPL(DebuggerREPLController* controller, QWidget* parent = nullptr); + +private slots: + void log(const QString&); + void postLine(); + +private: + Ui::DebuggerREPL m_ui; + + DebuggerREPLController* m_replController; +}; + +} + +#endif
A
src/platform/qt/DebuggerREPL.ui
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>DebuggerREPL</class> + <widget class="QWidget" name="DebuggerREPL"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>480</width> + <height>500</height> + </rect> + </property> + <property name="windowTitle"> + <string>Debugger</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QLineEdit" name="prompt"> + <property name="font"> + <font> + <family>Source Code Pro</family> + </font> + </property> + <property name="placeholderText"> + <string>Enter command (try `help` for more info)</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="breakpoint"> + <property name="text"> + <string>Break</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QPlainTextEdit" name="log"> + <property name="font"> + <font> + <family>Source Code Pro</family> + </font> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui>
A
src/platform/qt/DebuggerREPLController.cpp
@@ -0,0 +1,100 @@
+/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "DebuggerREPLController.h" + +#include "GameController.h" + +extern "C" { +#include "debugger/cli-debugger.h" +} + +using namespace QGBA; + +DebuggerREPLController::DebuggerREPLController(GameController* controller, QObject* parent) + : DebuggerController(controller, &m_cliDebugger.d, parent) +{ + m_backend.d.printf = printf; + m_backend.d.init = init; + m_backend.d.deinit = deinit; + m_backend.d.readline = readLine; + m_backend.d.lineAppend = lineAppend; + m_backend.d.historyLast = historyLast; + m_backend.d.historyAppend = historyAppend; + m_backend.self = this; + + CLIDebuggerCreate(&m_cliDebugger); + CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend.d); +} + +void DebuggerREPLController::enterLine(const QString& line) { + QMutexLocker lock(&m_mutex); + m_lines.append(line); + if (m_cliDebugger.d.state == DEBUGGER_RUNNING) { + mDebuggerEnter(&m_cliDebugger.d, DEBUGGER_ENTER_MANUAL, nullptr); + } + m_cond.wakeOne(); +} + +void DebuggerREPLController::attachInternal() { + mCore* core = m_gameController->thread()->core; + CLIDebuggerAttachSystem(&m_cliDebugger, core->cliDebuggerSystem(core)); +} + +void DebuggerREPLController::printf(struct CLIDebuggerBackend* be, const char* fmt, ...) { + Backend* replBe = reinterpret_cast<Backend*>(be); + DebuggerREPLController* self = replBe->self; + va_list args; + va_start(args, fmt); + self->log(QString().vsprintf(fmt, args)); + va_end(args); +} + +void DebuggerREPLController::init(struct CLIDebuggerBackend* be) { + Backend* replBe = reinterpret_cast<Backend*>(be); + DebuggerREPLController* self = replBe->self; +} + +void DebuggerREPLController::deinit(struct CLIDebuggerBackend* be) { + Backend* replBe = reinterpret_cast<Backend*>(be); + DebuggerREPLController* self = replBe->self; +} + +const char* DebuggerREPLController::readLine(struct CLIDebuggerBackend* be, size_t* len) { + Backend* replBe = reinterpret_cast<Backend*>(be); + DebuggerREPLController* self = replBe->self; + GameController::Interrupter interrupter(self->m_gameController, true); + QMutexLocker lock(&self->m_mutex); + while (self->m_lines.isEmpty()) { + self->m_cond.wait(&self->m_mutex); + } + self->m_last = self->m_lines.takeFirst().toUtf8(); + *len = self->m_last.size(); + return self->m_last.constData(); + +} + +void DebuggerREPLController::lineAppend(struct CLIDebuggerBackend* be, const char* line) { + Backend* replBe = reinterpret_cast<Backend*>(be); + DebuggerREPLController* self = replBe->self; + self->lineAppend(QString::fromUtf8(line)); +} + +const char* DebuggerREPLController::historyLast(struct CLIDebuggerBackend* be, size_t* len) { + Backend* replBe = reinterpret_cast<Backend*>(be); + DebuggerREPLController* self = replBe->self; + GameController::Interrupter interrupter(self->m_gameController, true); + QMutexLocker lock(&self->m_mutex); + self->m_last = self->m_history.last().toUtf8(); + return self->m_last.constData(); +} + +void DebuggerREPLController::historyAppend(struct CLIDebuggerBackend* be, const char* line) { + Backend* replBe = reinterpret_cast<Backend*>(be); + DebuggerREPLController* self = replBe->self; + GameController::Interrupter interrupter(self->m_gameController, true); + QMutexLocker lock(&self->m_mutex); + self->m_history.append(QString::fromUtf8(line)); +}
A
src/platform/qt/DebuggerREPLController.h
@@ -0,0 +1,62 @@
+/* Copyright (c) 2013-2016 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef QGBA_DEBUGGER_REPL_CONTROLLER +#define QGBA_DEBUGGER_REPL_CONTROLLER + +#include "DebuggerController.h" + +#include <QWaitCondition> + +extern "C" { +#include "debugger/cli-debugger.h" +} + +namespace QGBA { + +class GameController; + +class DebuggerREPLController : public DebuggerController { +Q_OBJECT + +public: + DebuggerREPLController(GameController* controller, QObject* parent = nullptr); + +signals: + void log(const QString&); + void lineAppend(const QString&); + +public slots: + void enterLine(const QString&); + +protected: + virtual void attachInternal() override; + +private: + static void printf(struct CLIDebuggerBackend* be, const char* fmt, ...); + static void init(struct CLIDebuggerBackend* be); + static void deinit(struct CLIDebuggerBackend* be); + static const char* readLine(struct CLIDebuggerBackend* be, size_t* len); + static void lineAppend(struct CLIDebuggerBackend* be, const char* line); + static const char* historyLast(struct CLIDebuggerBackend* be, size_t* len); + static void historyAppend(struct CLIDebuggerBackend* be, const char* line); + + CLIDebugger m_cliDebugger; + + QMutex m_mutex; + QWaitCondition m_cond; + QStringList m_history; + QStringList m_lines; + QByteArray m_last; + + struct Backend { + CLIDebuggerBackend d; + DebuggerREPLController* self; + } m_backend; +}; + +} + +#endif
M
src/platform/qt/Window.cpp
→
src/platform/qt/Window.cpp
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2016 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this@@ -18,6 +18,8 @@ #include "AboutScreen.h"
#include "ArchiveInspector.h" #include "CheatsView.h" #include "ConfigController.h" +#include "DebuggerREPL.h" +#include "DebuggerREPLController.h" #include "Display.h" #include "GameController.h" #include "GBAApp.h"@@ -70,6 +72,7 @@ #endif
#ifdef USE_GDB_STUB , m_gdbController(nullptr) #endif + , m_repl(nullptr) , m_mruMenu(nullptr) , m_shortcutController(new ShortcutController(this)) , m_fullscreenOnStart(false)@@ -512,6 +515,14 @@ GDBWindow* window = new GDBWindow(m_gdbController);
openView(window); } #endif + +void Window::replOpen() { + if (!m_repl) { + m_repl = new DebuggerREPLController(m_controller, this); + } + DebuggerREPL* window = new DebuggerREPL(m_repl); + openView(window); +} void Window::keyPressEvent(QKeyEvent* event) { if (event->isAutoRepeat()) {@@ -1331,17 +1342,22 @@ connect(cheats, &QAction::triggered, openTView<CheatsView>());
m_gameActions.append(cheats); addControlledAction(toolsMenu, cheats, "cheatsWindow"); + toolsMenu->addSeparator(); + addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), + "settings"); + + toolsMenu->addSeparator(); + + QAction* replWindow = new QAction(tr("Open debugger REPL..."), toolsMenu); + connect(replWindow, SIGNAL(triggered()), this, SLOT(replOpen())); + addControlledAction(toolsMenu, replWindow, "debuggerWindow"); + #ifdef USE_GDB_STUB QAction* gdbWindow = new QAction(tr("Start &GDB server..."), toolsMenu); connect(gdbWindow, SIGNAL(triggered()), this, SLOT(gdbOpen())); m_gbaActions.append(gdbWindow); addControlledAction(toolsMenu, gdbWindow, "gdbWindow"); #endif - - toolsMenu->addSeparator(); - addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), - "settings"); - toolsMenu->addSeparator(); QAction* paletteView = new QAction(tr("View &palette..."), toolsMenu);
M
src/platform/qt/Window.h
→
src/platform/qt/Window.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2016 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this@@ -18,7 +18,6 @@ #include "core/thread.h"
#include "gba/gba.h" } -#include "GDBController.h" #include "InputController.h" #include "LoadSaveState.h" #include "LogController.h"@@ -27,8 +26,10 @@
namespace QGBA { class ConfigController; +class DebuggerREPLController; class Display; class GameController; +class GDBController; class GIFView; class LogView; class ShaderSelector;@@ -79,6 +80,8 @@ void exportSharkport();
void openSettingsWindow(); void openAboutScreen(); + + void replOpen(); #ifdef USE_FFMPEG void openVideoWindow();@@ -156,6 +159,7 @@ #endif
QMap<int, QAction*> m_frameSizes; LogController m_log; LogView* m_logView; + DebuggerREPLController* m_repl; LoadSaveState* m_stateWindow; WindowBackground* m_screenWidget; QPixmap m_logo;