mirror of https://github.com/mgba-emu/mgba.git
Qt: Split MemoryAccessLogView and MemoryAccessLogController
This commit is contained in:
parent
f58f9746d7
commit
a2fda7f441
|
@ -261,6 +261,7 @@ if(ENABLE_DEBUGGERS)
|
|||
DebuggerController.cpp
|
||||
DebuggerConsole.cpp
|
||||
DebuggerConsoleController.cpp
|
||||
MemoryAccessLogController.cpp
|
||||
MemoryAccessLogView.cpp)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue