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
DebuggerConsole.cpp
DebuggerConsoleController.cpp
MemoryAccessLogController.cpp
MemoryAccessLogView.cpp)
endif()

View File

@ -8,6 +8,7 @@
#include "ConfigController.h"
#include "InputController.h"
#include "LogController.h"
#include "MemoryAccessLogController.h"
#include "MultiplayerController.h"
#include "Override.h"
@ -459,6 +460,15 @@ void CoreController::setLogger(LogController* logger) {
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() {
QSize size(screenDimensions());
m_activeBuffer.resize(size.width() * size.height() * sizeof(mColor));
@ -479,6 +489,10 @@ void CoreController::start() {
void CoreController::stop() {
setSync(false);
#ifdef ENABLE_DEBUGGERS
if (m_malController) {
m_malController->stop();
}
detachDebugger();
#endif
setPaused(false);

View File

@ -40,6 +40,7 @@ namespace QGBA {
class ConfigController;
class InputController;
class LogController;
class MemoryAccessLogController;
class MultiplayerController;
class Override;
@ -113,6 +114,8 @@ public:
void detachDebugger();
void attachDebuggerModule(mDebuggerModule*, bool interrupt = true);
void detachDebuggerModule(mDebuggerModule*);
std::weak_ptr<MemoryAccessLogController> memoryAccessLogController();
#endif
void setMultiplayerController(MultiplayerController*);
@ -326,6 +329,10 @@ private:
GBASIODolphin m_dolphin;
#endif
#ifdef ENABLE_DEBUGGERS
std::shared_ptr<MemoryAccessLogController> m_malController;
#endif
mVideoLogContext* m_vl = 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 "LogController.h"
#include "MemoryAccessLogController.h"
#include "utils.h"
#include "VFileDevice.h"
using namespace QGBA;
MemoryAccessLogView::MemoryAccessLogView(std::shared_ptr<CoreController> controller, QWidget* parent)
MemoryAccessLogView::MemoryAccessLogView(std::weak_ptr<MemoryAccessLogController> controller, QWidget* parent)
: QWidget(parent)
, m_controller(std::move(controller))
, m_controller(controller)
{
m_ui.setupUi(this);
std::shared_ptr<MemoryAccessLogController> controllerPtr = m_controller.lock();
connect(m_ui.browse, &QAbstractButton::clicked, this, &MemoryAccessLogView::selectFile);
connect(m_ui.exportButton, &QAbstractButton::clicked, this, &MemoryAccessLogView::exportFile);
connect(this, &MemoryAccessLogView::loggingChanged, m_ui.start, &QWidget::setDisabled);
connect(this, &MemoryAccessLogView::loggingChanged, m_ui.stop, &QWidget::setEnabled);
connect(this, &MemoryAccessLogView::loggingChanged, m_ui.filename, &QWidget::setDisabled);
connect(this, &MemoryAccessLogView::loggingChanged, m_ui.browse, &QWidget::setDisabled);
connect(controllerPtr.get(), &MemoryAccessLogController::regionMappingChanged, this, &MemoryAccessLogView::updateRegion);
connect(controllerPtr.get(), &MemoryAccessLogController::loggingChanged, this, &MemoryAccessLogView::handleStartStop);
mCore* core = m_controller->thread()->core;
const mCoreMemoryBlock* info;
size_t nBlocks = core->listMemoryBlocks(core, &info);
bool active = controllerPtr->active();
auto watchedRegions = controllerPtr->watchedRegions();
QVBoxLayout* regionBox = static_cast<QVBoxLayout*>(m_ui.regionBox->layout());
for (size_t i = 0; i < nBlocks; ++i) {
if (!(info[i].flags & mCORE_MEMORY_MAPPED)) {
continue;
}
QCheckBox* region = new QCheckBox(QString::fromUtf8(info[i].longName));
for (const auto& info : controllerPtr->listRegions()) {
QCheckBox* region = new QCheckBox(info.longName);
regionBox->addWidget(region);
QString name(QString::fromUtf8(info[i].internalName));
QString name(info.internalName);
m_regionBoxes[name] = region;
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() {
stop();
}
void MemoryAccessLogView::updateRegion(const QString& internalName, bool checked) {
if (checked) {
m_watchedRegions += internalName;
} else {
m_watchedRegions -= internalName;
}
if (!m_active) {
return;
}
void MemoryAccessLogView::updateRegion(const QString& internalName, bool) {
m_regionBoxes[internalName]->setEnabled(false);
m_regionMapping[internalName] = mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, internalName.toUtf8().constData(), activeFlags());
}
void MemoryAccessLogView::start() {
int flags = O_CREAT | O_RDWR;
if (!m_ui.loadExisting->isChecked()) {
flags |= O_TRUNC;
}
VFile* vf = VFileDevice::open(m_ui.filename->text(), flags);
if (!vf) {
// log error
std::shared_ptr<MemoryAccessLogController> controllerPtr = m_controller.lock();
if (!controllerPtr) {
return;
}
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_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);
}
controllerPtr->setFile(m_ui.filename->text());
controllerPtr->start(m_ui.loadExisting->isChecked(), m_ui.logExtra->isChecked());
}
void MemoryAccessLogView::stop() {
if (!m_active) {
std::shared_ptr<MemoryAccessLogController> controllerPtr = m_controller.lock();
if (!controllerPtr) {
return;
}
CoreController::Interrupter interrupter(m_controller);
m_controller->detachDebuggerModule(&m_logger.d);
mDebuggerAccessLoggerDeinit(&m_logger);
emit loggingChanged(false);
interrupter.resume();
for (const auto& region : m_watchedRegions) {
controllerPtr->stop();
for (const auto& region : controllerPtr->watchedRegions()) {
m_regionBoxes[region]->setEnabled(true);
}
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() {
if (!m_regionMapping.contains("cart0")) {
std::shared_ptr<MemoryAccessLogController> controllerPtr = m_controller.lock();
if (!controllerPtr) {
return;
}
if (!controllerPtr->canExport()) {
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()) {
return;
}
VFile* vf = VFileDevice::open(filename, O_CREAT | O_TRUNC | O_WRONLY);
if (!vf) {
// log error
controllerPtr->exportFile(filename);
}
void MemoryAccessLogView::handleStartStop(bool start) {
std::shared_ptr<MemoryAccessLogController> controllerPtr = m_controller.lock();
if (!controllerPtr) {
return;
}
m_ui.filename->setText(controllerPtr->file());
CoreController::Interrupter interrupter(m_controller);
mDebuggerAccessLoggerCreateShadowFile(&m_logger, m_regionMapping[QString("cart0")], vf, 0);
vf->close(vf);
auto watchedRegions = controllerPtr->watchedRegions();
for (const auto& region : watchedRegions) {
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 {
class MemoryAccessLogController;
class MemoryAccessLogView : public QWidget {
Q_OBJECT
public:
MemoryAccessLogView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
~MemoryAccessLogView();
MemoryAccessLogView(std::weak_ptr<MemoryAccessLogController> controller, QWidget* parent = nullptr);
~MemoryAccessLogView() = default;
private slots:
void updateRegion(const QString& internalName, bool enable);
@ -34,20 +36,13 @@ private slots:
void exportFile();
signals:
void loggingChanged(bool active);
void handleStartStop(bool start);
private:
Ui::MemoryAccessLogView m_ui;
std::shared_ptr<CoreController> m_controller;
QSet<QString> m_watchedRegions;
std::weak_ptr<MemoryAccessLogController> m_controller;
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");
#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
#if defined(USE_FFMPEG) && defined(M_CORE_GBA)