Qt: Add debugger REPL

This commit is contained in:
Jeffrey Pfau 2016-10-26 21:44:30 -07:00
parent 02c3ea80fd
commit d99923b94e
11 changed files with 328 additions and 9 deletions

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -30,6 +30,7 @@ public slots:
virtual void shutdown();
protected:
virtual void attachInternal();
virtual void shutdownInternal();
mDebugger* const m_debugger;

View File

@ -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);
}
}

View File

@ -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

View File

@ -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>

View File

@ -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));
}

View File

@ -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

View File

@ -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);

View File

@ -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<int, QAction*> m_frameSizes;
LogController m_log;
LogView* m_logView;
DebuggerREPLController* m_repl;
LoadSaveState* m_stateWindow;
WindowBackground* m_screenWidget;
QPixmap m_logo;