Qt: Split MemoryAccessLogView and MemoryAccessLogController

This commit is contained in:
Vicki Pfau 2025-01-11 14:50:30 -08:00
parent f58f9746d7
commit a2fda7f441
8 changed files with 272 additions and 93 deletions

View File

@ -261,6 +261,7 @@ if(ENABLE_DEBUGGERS)
DebuggerController.cpp DebuggerController.cpp
DebuggerConsole.cpp DebuggerConsole.cpp
DebuggerConsoleController.cpp DebuggerConsoleController.cpp
MemoryAccessLogController.cpp
MemoryAccessLogView.cpp) MemoryAccessLogView.cpp)
endif() endif()

View File

@ -8,6 +8,7 @@
#include "ConfigController.h" #include "ConfigController.h"
#include "InputController.h" #include "InputController.h"
#include "LogController.h" #include "LogController.h"
#include "MemoryAccessLogController.h"
#include "MultiplayerController.h" #include "MultiplayerController.h"
#include "Override.h" #include "Override.h"
@ -459,6 +460,15 @@ void CoreController::setLogger(LogController* logger) {
connect(this, &CoreController::logPosted, m_log, &LogController::postLog); connect(this, &CoreController::logPosted, m_log, &LogController::postLog);
} }
#ifdef ENABLE_DEBUGGERS
std::weak_ptr<MemoryAccessLogController> CoreController::memoryAccessLogController() {
if (!m_malController) {
m_malController = std::make_shared<MemoryAccessLogController>(this);
}
return m_malController;
}
#endif
void CoreController::start() { void CoreController::start() {
QSize size(screenDimensions()); QSize size(screenDimensions());
m_activeBuffer.resize(size.width() * size.height() * sizeof(mColor)); m_activeBuffer.resize(size.width() * size.height() * sizeof(mColor));
@ -479,6 +489,10 @@ void CoreController::start() {
void CoreController::stop() { void CoreController::stop() {
setSync(false); setSync(false);
#ifdef ENABLE_DEBUGGERS #ifdef ENABLE_DEBUGGERS
if (m_malController) {
m_malController->stop();
}
detachDebugger(); detachDebugger();
#endif #endif
setPaused(false); setPaused(false);

View File

@ -40,6 +40,7 @@ namespace QGBA {
class ConfigController; class ConfigController;
class InputController; class InputController;
class LogController; class LogController;
class MemoryAccessLogController;
class MultiplayerController; class MultiplayerController;
class Override; class Override;
@ -113,6 +114,8 @@ public:
void detachDebugger(); void detachDebugger();
void attachDebuggerModule(mDebuggerModule*, bool interrupt = true); void attachDebuggerModule(mDebuggerModule*, bool interrupt = true);
void detachDebuggerModule(mDebuggerModule*); void detachDebuggerModule(mDebuggerModule*);
std::weak_ptr<MemoryAccessLogController> memoryAccessLogController();
#endif #endif
void setMultiplayerController(MultiplayerController*); void setMultiplayerController(MultiplayerController*);
@ -326,6 +329,10 @@ private:
GBASIODolphin m_dolphin; GBASIODolphin m_dolphin;
#endif #endif
#ifdef ENABLE_DEBUGGERS
std::shared_ptr<MemoryAccessLogController> m_malController;
#endif
mVideoLogContext* m_vl = nullptr; mVideoLogContext* m_vl = nullptr;
VFile* m_vlVf = nullptr; VFile* m_vlVf = nullptr;

View File

@ -0,0 +1,118 @@
/* Copyright (c) 2013-2025 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 "MemoryAccessLogController.h"
#include "GBAApp.h"
#include "LogController.h"
#include "utils.h"
#include "VFileDevice.h"
using namespace QGBA;
MemoryAccessLogController::MemoryAccessLogController(CoreController* controller, QObject* parent)
: QObject(parent)
, m_controller(controller)
{
mCore* core = m_controller->thread()->core;
const mCoreMemoryBlock* info;
size_t nBlocks = core->listMemoryBlocks(core, &info);
for (size_t i = 0; i < nBlocks; ++i) {
if (!(info[i].flags & mCORE_MEMORY_MAPPED)) {
continue;
}
m_regions.append({
QString::fromUtf8(info[i].longName),
QString::fromUtf8(info[i].internalName)
});
}
}
MemoryAccessLogController::~MemoryAccessLogController() {
stop();
}
bool MemoryAccessLogController::canExport() const {
return m_regionMapping.contains("cart0");
}
void MemoryAccessLogController::updateRegion(const QString& internalName, bool checked) {
if (checked) {
m_watchedRegions += internalName;
} else {
m_watchedRegions -= internalName;
}
if (!m_active) {
return;
}
m_regionMapping[internalName] = mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, internalName.toUtf8().constData(), activeFlags());
emit regionMappingChanged(internalName, checked);
}
void MemoryAccessLogController::setFile(const QString& path) {
m_path = path;
}
void MemoryAccessLogController::start(bool loadExisting, bool logExtra) {
int flags = O_CREAT | O_RDWR;
if (!loadExisting) {
flags |= O_TRUNC;
}
VFile* vf = VFileDevice::open(m_path, flags);
if (!vf) {
LOG(QT, ERROR) << tr("Failed to open memory log file");
return;
}
m_logExtra = logExtra;
mDebuggerAccessLoggerInit(&m_logger);
CoreController::Interrupter interrupter(m_controller);
m_controller->attachDebuggerModule(&m_logger.d);
if (!mDebuggerAccessLoggerOpen(&m_logger, vf, flags)) {
mDebuggerAccessLoggerDeinit(&m_logger);
LOG(QT, ERROR) << tr("Failed to open memory log file");
return;
}
m_active = true;
emit loggingChanged(true);
for (const auto& region : m_watchedRegions) {
m_regionMapping[region] = mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, region.toUtf8().constData(), activeFlags());
}
interrupter.resume();
}
void MemoryAccessLogController::stop() {
if (!m_active) {
return;
}
CoreController::Interrupter interrupter(m_controller);
m_controller->detachDebuggerModule(&m_logger.d);
mDebuggerAccessLoggerDeinit(&m_logger);
emit loggingChanged(false);
interrupter.resume();
m_active = false;
}
mDebuggerAccessLogRegionFlags MemoryAccessLogController::activeFlags() const {
mDebuggerAccessLogRegionFlags loggerFlags = 0;
if (m_logExtra) {
loggerFlags = mDebuggerAccessLogRegionFlagsFillHasExBlock(loggerFlags);
}
return loggerFlags;
}
void MemoryAccessLogController::exportFile(const QString& filename) {
VFile* vf = VFileDevice::open(filename, O_CREAT | O_TRUNC | O_WRONLY);
if (!vf) {
// log error
return;
}
CoreController::Interrupter interrupter(m_controller);
mDebuggerAccessLoggerCreateShadowFile(&m_logger, m_regionMapping[QString("cart0")], vf, 0);
vf->close(vf);
}

View File

@ -0,0 +1,66 @@
/* Copyright (c) 2013-2025 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/. */
#pragma once
#include <QSet>
#include <QVector>
#include <memory>
#include "CoreController.h"
#include <mgba/internal/debugger/access-logger.h>
namespace QGBA {
class MemoryAccessLogController : public QObject {
Q_OBJECT
public:
struct Region {
QString longName;
QString internalName;
};
MemoryAccessLogController(CoreController* controller, QObject* parent = nullptr);
~MemoryAccessLogController();
QVector<Region> listRegions() const { return m_regions; }
QSet<QString> watchedRegions() const { return m_watchedRegions; }
bool canExport() const;
mPlatform platform() const { return m_controller->platform(); }
QString file() const { return m_path; }
bool active() const { return m_active; }
public slots:
void updateRegion(const QString& internalName, bool enable);
void setFile(const QString& path);
void start(bool loadExisting, bool logExtra);
void stop();
void exportFile(const QString& filename);
signals:
void loggingChanged(bool active);
void regionMappingChanged(const QString& internalName, bool active);
private:
bool m_logExtra = false;
QString m_path;
CoreController* m_controller;
QSet<QString> m_watchedRegions;
QHash<QString, int> m_regionMapping;
QVector<Region> m_regions;
struct mDebuggerAccessLogger m_logger{};
bool m_active = false;
mDebuggerAccessLogRegionFlags activeFlags() const;
};
}

View File

@ -9,104 +9,66 @@
#include "GBAApp.h" #include "GBAApp.h"
#include "LogController.h" #include "LogController.h"
#include "MemoryAccessLogController.h"
#include "utils.h" #include "utils.h"
#include "VFileDevice.h" #include "VFileDevice.h"
using namespace QGBA; using namespace QGBA;
MemoryAccessLogView::MemoryAccessLogView(std::shared_ptr<CoreController> controller, QWidget* parent) MemoryAccessLogView::MemoryAccessLogView(std::weak_ptr<MemoryAccessLogController> controller, QWidget* parent)
: QWidget(parent) : QWidget(parent)
, m_controller(std::move(controller)) , m_controller(controller)
{ {
m_ui.setupUi(this); m_ui.setupUi(this);
std::shared_ptr<MemoryAccessLogController> controllerPtr = m_controller.lock();
connect(m_ui.browse, &QAbstractButton::clicked, this, &MemoryAccessLogView::selectFile); connect(m_ui.browse, &QAbstractButton::clicked, this, &MemoryAccessLogView::selectFile);
connect(m_ui.exportButton, &QAbstractButton::clicked, this, &MemoryAccessLogView::exportFile); connect(m_ui.exportButton, &QAbstractButton::clicked, this, &MemoryAccessLogView::exportFile);
connect(this, &MemoryAccessLogView::loggingChanged, m_ui.start, &QWidget::setDisabled); connect(controllerPtr.get(), &MemoryAccessLogController::regionMappingChanged, this, &MemoryAccessLogView::updateRegion);
connect(this, &MemoryAccessLogView::loggingChanged, m_ui.stop, &QWidget::setEnabled); connect(controllerPtr.get(), &MemoryAccessLogController::loggingChanged, this, &MemoryAccessLogView::handleStartStop);
connect(this, &MemoryAccessLogView::loggingChanged, m_ui.filename, &QWidget::setDisabled);
connect(this, &MemoryAccessLogView::loggingChanged, m_ui.browse, &QWidget::setDisabled);
mCore* core = m_controller->thread()->core; bool active = controllerPtr->active();
const mCoreMemoryBlock* info; auto watchedRegions = controllerPtr->watchedRegions();
size_t nBlocks = core->listMemoryBlocks(core, &info);
QVBoxLayout* regionBox = static_cast<QVBoxLayout*>(m_ui.regionBox->layout()); QVBoxLayout* regionBox = static_cast<QVBoxLayout*>(m_ui.regionBox->layout());
for (size_t i = 0; i < nBlocks; ++i) { for (const auto& info : controllerPtr->listRegions()) {
if (!(info[i].flags & mCORE_MEMORY_MAPPED)) { QCheckBox* region = new QCheckBox(info.longName);
continue;
}
QCheckBox* region = new QCheckBox(QString::fromUtf8(info[i].longName));
regionBox->addWidget(region); regionBox->addWidget(region);
QString name(QString::fromUtf8(info[i].internalName)); QString name(info.internalName);
m_regionBoxes[name] = region; m_regionBoxes[name] = region;
connect(region, &QAbstractButton::toggled, this, [this, name](bool checked) { connect(region, &QAbstractButton::toggled, this, [this, name](bool checked) {
updateRegion(name, checked); std::shared_ptr<MemoryAccessLogController> controllerPtr = m_controller.lock();
if (!controllerPtr) {
return;
}
controllerPtr->updateRegion(name, checked);
}); });
} }
handleStartStop(active);
} }
MemoryAccessLogView::~MemoryAccessLogView() { void MemoryAccessLogView::updateRegion(const QString& internalName, bool) {
stop();
}
void MemoryAccessLogView::updateRegion(const QString& internalName, bool checked) {
if (checked) {
m_watchedRegions += internalName;
} else {
m_watchedRegions -= internalName;
}
if (!m_active) {
return;
}
m_regionBoxes[internalName]->setEnabled(false); m_regionBoxes[internalName]->setEnabled(false);
m_regionMapping[internalName] = mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, internalName.toUtf8().constData(), activeFlags());
} }
void MemoryAccessLogView::start() { void MemoryAccessLogView::start() {
int flags = O_CREAT | O_RDWR; std::shared_ptr<MemoryAccessLogController> controllerPtr = m_controller.lock();
if (!m_ui.loadExisting->isChecked()) { if (!controllerPtr) {
flags |= O_TRUNC;
}
VFile* vf = VFileDevice::open(m_ui.filename->text(), flags);
if (!vf) {
// log error
return; return;
} }
mDebuggerAccessLoggerInit(&m_logger); controllerPtr->setFile(m_ui.filename->text());
CoreController::Interrupter interrupter(m_controller); controllerPtr->start(m_ui.loadExisting->isChecked(), m_ui.logExtra->isChecked());
m_controller->attachDebuggerModule(&m_logger.d);
if (!mDebuggerAccessLoggerOpen(&m_logger, vf, flags)) {
mDebuggerAccessLoggerDeinit(&m_logger);
LOG(QT, ERROR) << tr("Failed to open memory log file");
return;
}
m_active = true;
emit loggingChanged(true);
for (const auto& region : m_watchedRegions) {
m_regionBoxes[region]->setEnabled(false);
m_regionMapping[region] = mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, region.toUtf8().constData(), activeFlags());
}
interrupter.resume();
if (m_watchedRegions.contains(QString("cart0"))) {
m_ui.exportButton->setEnabled(true);
}
} }
void MemoryAccessLogView::stop() { void MemoryAccessLogView::stop() {
if (!m_active) { std::shared_ptr<MemoryAccessLogController> controllerPtr = m_controller.lock();
if (!controllerPtr) {
return; return;
} }
CoreController::Interrupter interrupter(m_controller); controllerPtr->stop();
m_controller->detachDebuggerModule(&m_logger.d); for (const auto& region : controllerPtr->watchedRegions()) {
mDebuggerAccessLoggerDeinit(&m_logger);
emit loggingChanged(false);
interrupter.resume();
for (const auto& region : m_watchedRegions) {
m_regionBoxes[region]->setEnabled(true); m_regionBoxes[region]->setEnabled(true);
} }
m_ui.exportButton->setEnabled(false); m_ui.exportButton->setEnabled(false);
@ -119,30 +81,41 @@ void MemoryAccessLogView::selectFile() {
} }
} }
mDebuggerAccessLogRegionFlags MemoryAccessLogView::activeFlags() const {
mDebuggerAccessLogRegionFlags loggerFlags = 0;
if (m_ui.logExtra->isChecked()) {
loggerFlags = mDebuggerAccessLogRegionFlagsFillHasExBlock(loggerFlags);
}
return loggerFlags;
}
void MemoryAccessLogView::exportFile() { void MemoryAccessLogView::exportFile() {
if (!m_regionMapping.contains("cart0")) { std::shared_ptr<MemoryAccessLogController> controllerPtr = m_controller.lock();
if (!controllerPtr) {
return;
}
if (!controllerPtr->canExport()) {
return; return;
} }
QString filename = GBAApp::app()->getSaveFileName(this, tr("Select access log file"), romFilters(false, m_controller->platform(), true)); QString filename = GBAApp::app()->getSaveFileName(this, tr("Select access log file"), romFilters(false, controllerPtr->platform(), true));
if (filename.isEmpty()) { if (filename.isEmpty()) {
return; return;
} }
VFile* vf = VFileDevice::open(filename, O_CREAT | O_TRUNC | O_WRONLY); controllerPtr->exportFile(filename);
if (!vf) { }
// log error
void MemoryAccessLogView::handleStartStop(bool start) {
std::shared_ptr<MemoryAccessLogController> controllerPtr = m_controller.lock();
if (!controllerPtr) {
return; return;
} }
m_ui.filename->setText(controllerPtr->file());
CoreController::Interrupter interrupter(m_controller); auto watchedRegions = controllerPtr->watchedRegions();
mDebuggerAccessLoggerCreateShadowFile(&m_logger, m_regionMapping[QString("cart0")], vf, 0); for (const auto& region : watchedRegions) {
vf->close(vf); m_regionBoxes[region]->setDisabled(start);
m_regionBoxes[region]->setChecked(true);
}
if (watchedRegions.contains(QString("cart0"))) {
m_ui.exportButton->setEnabled(start);
}
m_ui.start->setDisabled(start);
m_ui.stop->setEnabled(start);
m_ui.filename->setDisabled(start);
m_ui.browse->setDisabled(start);
} }

View File

@ -18,12 +18,14 @@
namespace QGBA { namespace QGBA {
class MemoryAccessLogController;
class MemoryAccessLogView : public QWidget { class MemoryAccessLogView : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
MemoryAccessLogView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); MemoryAccessLogView(std::weak_ptr<MemoryAccessLogController> controller, QWidget* parent = nullptr);
~MemoryAccessLogView(); ~MemoryAccessLogView() = default;
private slots: private slots:
void updateRegion(const QString& internalName, bool enable); void updateRegion(const QString& internalName, bool enable);
@ -34,20 +36,13 @@ private slots:
void exportFile(); void exportFile();
signals: void handleStartStop(bool start);
void loggingChanged(bool active);
private: private:
Ui::MemoryAccessLogView m_ui; Ui::MemoryAccessLogView m_ui;
std::shared_ptr<CoreController> m_controller; std::weak_ptr<MemoryAccessLogController> m_controller;
QSet<QString> m_watchedRegions;
QHash<QString, QCheckBox*> m_regionBoxes; QHash<QString, QCheckBox*> m_regionBoxes;
QHash<QString, int> m_regionMapping;
struct mDebuggerAccessLogger m_logger{};
bool m_active = false;
mDebuggerAccessLogRegionFlags activeFlags() const;
}; };
} }

View File

@ -1767,7 +1767,12 @@ void Window::setupMenu(QMenuBar* menubar) {
addGameAction(tr("View &I/O registers..."), "ioViewer", openControllerTView<IOViewer>(), "stateViews"); addGameAction(tr("View &I/O registers..."), "ioViewer", openControllerTView<IOViewer>(), "stateViews");
#ifdef ENABLE_DEBUGGERS #ifdef ENABLE_DEBUGGERS
addGameAction(tr("Log memory &accesses..."), "memoryAccessView", openControllerTView<MemoryAccessLogView>(), "tools"); addGameAction(tr("Log memory &accesses..."), "memoryAccessView", [this]() {
std::weak_ptr<MemoryAccessLogController> controller = m_controller->memoryAccessLogController();
MemoryAccessLogView* view = new MemoryAccessLogView(controller);
connect(m_controller.get(), &CoreController::stopping, view, &QWidget::close);
openView(view);
}, "tools");
#endif #endif
#if defined(USE_FFMPEG) && defined(M_CORE_GBA) #if defined(USE_FFMPEG) && defined(M_CORE_GBA)