diff --git a/CHANGES b/CHANGES index 4dacc3119..b118de5ca 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ Features: - GBA: Better cheat type autodetection - GB: Tile viewer - Sprite viewer + - Debugging REPL Bugfixes: - LR35902: Fix core never exiting with certain event patterns - GB Timer: Improve DIV reset behavior diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index d3e20c167..fe062276f 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -76,6 +76,8 @@ set(SOURCE_FILES CheatsView.cpp ConfigController.cpp DebuggerController.cpp + DebuggerREPL.cpp + DebuggerREPLController.cpp Display.cpp DisplayGL.cpp DisplayQt.cpp @@ -119,6 +121,7 @@ set(UI_FILES ArchiveInspector.ui AssetTile.ui CheatsView.ui + DebuggerREPL.ui GIFView.ui IOViewer.ui LoadSaveState.ui diff --git a/src/platform/qt/DebuggerController.cpp b/src/platform/qt/DebuggerController.cpp index 94e063f12..dcec4efb2 100644 --- a/src/platform/qt/DebuggerController.cpp +++ b/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 @@ void DebuggerController::attach() { return; } if (m_gameController->isLoaded()) { + attachInternal(); m_gameController->setDebugger(m_debugger); mDebuggerEnter(m_debugger, DEBUGGER_ENTER_ATTACHED, 0); } else { @@ -60,6 +61,10 @@ void DebuggerController::shutdown() { shutdownInternal(); } +void DebuggerController::attachInternal() { + // No default implementation +} + void DebuggerController::shutdownInternal() { // No default implementation } diff --git a/src/platform/qt/DebuggerController.h b/src/platform/qt/DebuggerController.h index 44cb5c7ed..01603ae02 100644 --- a/src/platform/qt/DebuggerController.h +++ b/src/platform/qt/DebuggerController.h @@ -30,6 +30,7 @@ public slots: virtual void shutdown(); protected: + virtual void attachInternal(); virtual void shutdownInternal(); mDebugger* const m_debugger; diff --git a/src/platform/qt/DebuggerREPL.cpp b/src/platform/qt/DebuggerREPL.cpp new file mode 100644 index 000000000..3980189cf --- /dev/null +++ b/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 + +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); + } +} diff --git a/src/platform/qt/DebuggerREPL.h b/src/platform/qt/DebuggerREPL.h new file mode 100644 index 000000000..26b8ea796 --- /dev/null +++ b/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 diff --git a/src/platform/qt/DebuggerREPL.ui b/src/platform/qt/DebuggerREPL.ui new file mode 100644 index 000000000..77ea2e9d3 --- /dev/null +++ b/src/platform/qt/DebuggerREPL.ui @@ -0,0 +1,52 @@ + + + DebuggerREPL + + + + 0 + 0 + 480 + 500 + + + + Debugger + + + + + + + Source Code Pro + + + + Enter command (try `help` for more info) + + + + + + + Break + + + + + + + + Source Code Pro + + + + true + + + + + + + + diff --git a/src/platform/qt/DebuggerREPLController.cpp b/src/platform/qt/DebuggerREPLController.cpp new file mode 100644 index 000000000..381a7d20b --- /dev/null +++ b/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(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(be); + DebuggerREPLController* self = replBe->self; +} + +void DebuggerREPLController::deinit(struct CLIDebuggerBackend* be) { + Backend* replBe = reinterpret_cast(be); + DebuggerREPLController* self = replBe->self; +} + +const char* DebuggerREPLController::readLine(struct CLIDebuggerBackend* be, size_t* len) { + Backend* replBe = reinterpret_cast(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(be); + DebuggerREPLController* self = replBe->self; + self->lineAppend(QString::fromUtf8(line)); +} + +const char* DebuggerREPLController::historyLast(struct CLIDebuggerBackend* be, size_t* len) { + Backend* replBe = reinterpret_cast(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(be); + DebuggerREPLController* self = replBe->self; + GameController::Interrupter interrupter(self->m_gameController, true); + QMutexLocker lock(&self->m_mutex); + self->m_history.append(QString::fromUtf8(line)); +} diff --git a/src/platform/qt/DebuggerREPLController.h b/src/platform/qt/DebuggerREPLController.h new file mode 100644 index 000000000..45dc7772b --- /dev/null +++ b/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 + +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 diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 3a9a7982a..9dca63a4e 100644 --- a/src/platform/qt/Window.cpp +++ b/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 "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 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent) #ifdef USE_GDB_STUB , m_gdbController(nullptr) #endif + , m_repl(nullptr) , m_mruMenu(nullptr) , m_shortcutController(new ShortcutController(this)) , m_fullscreenOnStart(false) @@ -513,6 +516,14 @@ void Window::gdbOpen() { } #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()) { QWidget::keyPressEvent(event); @@ -1331,17 +1342,22 @@ void Window::setupMenu(QMenuBar* menubar) { 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); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 6189fedd7..0436e08ca 100644 --- a/src/platform/qt/Window.h +++ b/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 @@ extern "C" { #include "gba/gba.h" } -#include "GDBController.h" #include "InputController.h" #include "LoadSaveState.h" #include "LogController.h" @@ -27,8 +26,10 @@ struct mArguments; namespace QGBA { class ConfigController; +class DebuggerREPLController; class Display; class GameController; +class GDBController; class GIFView; class LogView; class ShaderSelector; @@ -80,6 +81,8 @@ public slots: void openSettingsWindow(); void openAboutScreen(); + void replOpen(); + #ifdef USE_FFMPEG void openVideoWindow(); #endif @@ -156,6 +159,7 @@ private: QMap m_frameSizes; LogController m_log; LogView* m_logView; + DebuggerREPLController* m_repl; LoadSaveState* m_stateWindow; WindowBackground* m_screenWidget; QPixmap m_logo;