mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-23 22:29:39 -06:00
Move DolphinQt2 to DolphinQt
This commit is contained in:
336
Source/Core/DolphinQt/Debugger/BreakpointWidget.cpp
Normal file
336
Source/Core/DolphinQt/Debugger/BreakpointWidget.cpp
Normal file
@ -0,0 +1,336 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Debugger/BreakpointWidget.h"
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QTableWidget>
|
||||
#include <QToolBar>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IniFile.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/PowerPC/BreakPoints.h"
|
||||
#include "Core/PowerPC/PPCSymbolDB.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
||||
#include "DolphinQt/Debugger/NewBreakpointDialog.h"
|
||||
#include "DolphinQt/QtUtils/ActionHelper.h"
|
||||
#include "DolphinQt/Resources.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
BreakpointWidget::BreakpointWidget(QWidget* parent) : QDockWidget(parent)
|
||||
{
|
||||
setWindowTitle(tr("Breakpoints"));
|
||||
setObjectName(QStringLiteral("breakpoints"));
|
||||
|
||||
setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
restoreGeometry(settings.value(QStringLiteral("breakpointwidget/geometry")).toByteArray());
|
||||
setFloating(settings.value(QStringLiteral("breakpointwidget/floating")).toBool());
|
||||
|
||||
CreateWidgets();
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, [this](Core::State state) {
|
||||
if (!Settings::Instance().IsDebugModeEnabled())
|
||||
return;
|
||||
|
||||
bool is_initialised = state != Core::State::Uninitialized;
|
||||
m_new->setEnabled(is_initialised);
|
||||
m_load->setEnabled(is_initialised);
|
||||
m_save->setEnabled(is_initialised);
|
||||
if (!is_initialised)
|
||||
{
|
||||
PowerPC::breakpoints.Clear();
|
||||
PowerPC::memchecks.Clear();
|
||||
Update();
|
||||
}
|
||||
});
|
||||
|
||||
connect(&Settings::Instance(), &Settings::BreakpointsVisibilityChanged,
|
||||
[this](bool visible) { setHidden(!visible); });
|
||||
|
||||
connect(&Settings::Instance(), &Settings::DebugModeToggled, [this](bool enabled) {
|
||||
setHidden(!enabled || !Settings::Instance().IsBreakpointsVisible());
|
||||
});
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ThemeChanged, this, &BreakpointWidget::UpdateIcons);
|
||||
UpdateIcons();
|
||||
|
||||
setHidden(!Settings::Instance().IsBreakpointsVisible() ||
|
||||
!Settings::Instance().IsDebugModeEnabled());
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
BreakpointWidget::~BreakpointWidget()
|
||||
{
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
settings.setValue(QStringLiteral("breakpointwidget/geometry"), saveGeometry());
|
||||
settings.setValue(QStringLiteral("breakpointwidget/floating"), isFloating());
|
||||
}
|
||||
|
||||
void BreakpointWidget::CreateWidgets()
|
||||
{
|
||||
m_toolbar = new QToolBar;
|
||||
m_toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
|
||||
m_table = new QTableWidget;
|
||||
m_table->setColumnCount(5);
|
||||
m_table->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
m_table->verticalHeader()->hide();
|
||||
|
||||
connect(m_table, &QTableWidget::itemClicked, [this](QTableWidgetItem* item) {
|
||||
if (m_table->selectedItems()[0]->row() == item->row() &&
|
||||
Core::GetState() == Core::State::Paused)
|
||||
{
|
||||
auto address = m_table->selectedItems()[0]->data(Qt::UserRole).toUInt();
|
||||
emit SelectedBreakpoint(address);
|
||||
}
|
||||
});
|
||||
|
||||
auto* layout = new QVBoxLayout;
|
||||
|
||||
layout->addWidget(m_toolbar);
|
||||
layout->addWidget(m_table);
|
||||
|
||||
m_new = AddAction(m_toolbar, tr("New"), this, &BreakpointWidget::OnNewBreakpoint);
|
||||
m_delete = AddAction(m_toolbar, tr("Delete"), this, &BreakpointWidget::OnDelete);
|
||||
m_clear = AddAction(m_toolbar, tr("Clear"), this, &BreakpointWidget::OnClear);
|
||||
|
||||
m_load = AddAction(m_toolbar, tr("Load"), this, &BreakpointWidget::OnLoad);
|
||||
m_save = AddAction(m_toolbar, tr("Save"), this, &BreakpointWidget::OnSave);
|
||||
|
||||
m_new->setEnabled(false);
|
||||
m_load->setEnabled(false);
|
||||
m_save->setEnabled(false);
|
||||
|
||||
QWidget* widget = new QWidget;
|
||||
widget->setLayout(layout);
|
||||
|
||||
setWidget(widget);
|
||||
}
|
||||
|
||||
void BreakpointWidget::UpdateIcons()
|
||||
{
|
||||
m_new->setIcon(Resources::GetScaledThemeIcon("debugger_add_breakpoint"));
|
||||
m_delete->setIcon(Resources::GetScaledThemeIcon("debugger_delete"));
|
||||
m_clear->setIcon(Resources::GetScaledThemeIcon("debugger_clear"));
|
||||
m_load->setIcon(Resources::GetScaledThemeIcon("debugger_load"));
|
||||
m_save->setIcon(Resources::GetScaledThemeIcon("debugger_save"));
|
||||
}
|
||||
|
||||
void BreakpointWidget::closeEvent(QCloseEvent*)
|
||||
{
|
||||
Settings::Instance().SetBreakpointsVisible(false);
|
||||
}
|
||||
|
||||
void BreakpointWidget::Update()
|
||||
{
|
||||
m_table->clear();
|
||||
|
||||
m_table->setHorizontalHeaderLabels(
|
||||
{tr("Active"), tr("Type"), tr("Function"), tr("Address"), tr("Flags")});
|
||||
|
||||
int i = 0;
|
||||
m_table->setRowCount(i);
|
||||
|
||||
auto create_item = [](const QString string = QStringLiteral("")) {
|
||||
QTableWidgetItem* item = new QTableWidgetItem(string);
|
||||
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||
return item;
|
||||
};
|
||||
|
||||
// Breakpoints
|
||||
for (const auto& bp : PowerPC::breakpoints.GetBreakPoints())
|
||||
{
|
||||
m_table->setRowCount(i + 1);
|
||||
|
||||
auto* active = create_item(bp.is_enabled ? tr("on") : QString());
|
||||
|
||||
active->setData(Qt::UserRole, bp.address);
|
||||
|
||||
m_table->setItem(i, 0, active);
|
||||
m_table->setItem(i, 1, create_item(QStringLiteral("BP")));
|
||||
|
||||
if (g_symbolDB.GetSymbolFromAddr(bp.address))
|
||||
{
|
||||
m_table->setItem(i, 2,
|
||||
create_item(QString::fromStdString(g_symbolDB.GetDescription(bp.address))));
|
||||
}
|
||||
|
||||
m_table->setItem(i, 3,
|
||||
create_item(QStringLiteral("%1").arg(bp.address, 8, 16, QLatin1Char('0'))));
|
||||
|
||||
m_table->setItem(i, 4, create_item());
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// Memory Breakpoints
|
||||
for (const auto& mbp : PowerPC::memchecks.GetMemChecks())
|
||||
{
|
||||
m_table->setRowCount(i + 1);
|
||||
auto* active = create_item(mbp.break_on_hit || mbp.log_on_hit ? tr("on") : QString());
|
||||
active->setData(Qt::UserRole, mbp.start_address);
|
||||
|
||||
m_table->setItem(i, 0, active);
|
||||
m_table->setItem(i, 1, create_item(QStringLiteral("MBP")));
|
||||
|
||||
if (g_symbolDB.GetSymbolFromAddr(mbp.start_address))
|
||||
{
|
||||
m_table->setItem(
|
||||
i, 2, create_item(QString::fromStdString(g_symbolDB.GetDescription(mbp.start_address))));
|
||||
}
|
||||
|
||||
if (mbp.is_ranged)
|
||||
{
|
||||
m_table->setItem(i, 3,
|
||||
create_item(QStringLiteral("%1 - %2")
|
||||
.arg(mbp.start_address, 8, 16, QLatin1Char('0'))
|
||||
.arg(mbp.end_address, 8, 16, QLatin1Char('0'))));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_table->setItem(
|
||||
i, 3, create_item(QStringLiteral("%1").arg(mbp.start_address, 8, 16, QLatin1Char('0'))));
|
||||
}
|
||||
|
||||
QString flags;
|
||||
|
||||
if (mbp.is_break_on_read)
|
||||
flags.append(QStringLiteral("r"));
|
||||
|
||||
if (mbp.is_break_on_write)
|
||||
flags.append(QStringLiteral("w"));
|
||||
|
||||
m_table->setItem(i, 4, create_item(flags));
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void BreakpointWidget::OnDelete()
|
||||
{
|
||||
if (m_table->selectedItems().size() == 0)
|
||||
return;
|
||||
|
||||
auto address = m_table->selectedItems()[0]->data(Qt::UserRole).toUInt();
|
||||
|
||||
PowerPC::breakpoints.Remove(address);
|
||||
Settings::Instance().blockSignals(true);
|
||||
PowerPC::memchecks.Remove(address);
|
||||
Settings::Instance().blockSignals(false);
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void BreakpointWidget::OnClear()
|
||||
{
|
||||
PowerPC::debug_interface.ClearAllBreakpoints();
|
||||
Settings::Instance().blockSignals(true);
|
||||
PowerPC::debug_interface.ClearAllMemChecks();
|
||||
Settings::Instance().blockSignals(false);
|
||||
|
||||
m_table->setRowCount(0);
|
||||
Update();
|
||||
}
|
||||
|
||||
void BreakpointWidget::OnNewBreakpoint()
|
||||
{
|
||||
NewBreakpointDialog* dialog = new NewBreakpointDialog(this);
|
||||
dialog->exec();
|
||||
}
|
||||
|
||||
void BreakpointWidget::OnLoad()
|
||||
{
|
||||
IniFile ini;
|
||||
if (!ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini",
|
||||
false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BreakPoints::TBreakPointsStr new_bps;
|
||||
if (ini.GetLines("BreakPoints", &new_bps, false))
|
||||
{
|
||||
PowerPC::breakpoints.Clear();
|
||||
PowerPC::breakpoints.AddFromStrings(new_bps);
|
||||
}
|
||||
|
||||
MemChecks::TMemChecksStr new_mcs;
|
||||
if (ini.GetLines("MemoryBreakPoints", &new_mcs, false))
|
||||
{
|
||||
PowerPC::memchecks.Clear();
|
||||
Settings::Instance().blockSignals(true);
|
||||
PowerPC::memchecks.AddFromStrings(new_mcs);
|
||||
Settings::Instance().blockSignals(false);
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void BreakpointWidget::OnSave()
|
||||
{
|
||||
IniFile ini;
|
||||
ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini",
|
||||
false);
|
||||
ini.SetLines("BreakPoints", PowerPC::breakpoints.GetStrings());
|
||||
ini.SetLines("MemoryBreakPoints", PowerPC::memchecks.GetStrings());
|
||||
ini.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini");
|
||||
}
|
||||
|
||||
void BreakpointWidget::AddBP(u32 addr)
|
||||
{
|
||||
PowerPC::breakpoints.Add(addr);
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void BreakpointWidget::AddAddressMBP(u32 addr, bool on_read, bool on_write, bool do_log,
|
||||
bool do_break)
|
||||
{
|
||||
TMemCheck check;
|
||||
|
||||
check.start_address = addr;
|
||||
check.end_address = addr;
|
||||
check.is_ranged = false;
|
||||
check.is_break_on_read = on_read;
|
||||
check.is_break_on_write = on_write;
|
||||
check.log_on_hit = do_log;
|
||||
check.break_on_hit = do_break;
|
||||
|
||||
Settings::Instance().blockSignals(true);
|
||||
PowerPC::memchecks.Add(check);
|
||||
Settings::Instance().blockSignals(false);
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void BreakpointWidget::AddRangedMBP(u32 from, u32 to, bool on_read, bool on_write, bool do_log,
|
||||
bool do_break)
|
||||
{
|
||||
TMemCheck check;
|
||||
|
||||
check.start_address = from;
|
||||
check.end_address = to;
|
||||
check.is_ranged = true;
|
||||
check.is_break_on_read = on_read;
|
||||
check.is_break_on_write = on_write;
|
||||
check.log_on_hit = do_log;
|
||||
check.break_on_hit = do_break;
|
||||
|
||||
Settings::Instance().blockSignals(true);
|
||||
PowerPC::memchecks.Add(check);
|
||||
Settings::Instance().blockSignals(false);
|
||||
|
||||
Update();
|
||||
}
|
55
Source/Core/DolphinQt/Debugger/BreakpointWidget.h
Normal file
55
Source/Core/DolphinQt/Debugger/BreakpointWidget.h
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDockWidget>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class QAction;
|
||||
class QTableWidget;
|
||||
class QToolBar;
|
||||
class QCloseEvent;
|
||||
|
||||
class BreakpointWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit BreakpointWidget(QWidget* parent = nullptr);
|
||||
~BreakpointWidget();
|
||||
|
||||
void AddBP(u32 addr);
|
||||
void AddAddressMBP(u32 addr, bool on_read = true, bool on_write = true, bool do_log = true,
|
||||
bool do_break = true);
|
||||
void AddRangedMBP(u32 from, u32 to, bool do_read = true, bool do_write = true, bool do_log = true,
|
||||
bool do_break = true);
|
||||
void Update();
|
||||
|
||||
signals:
|
||||
void BreakpointsChanged();
|
||||
void SelectedBreakpoint(u32 address);
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
|
||||
void OnDelete();
|
||||
void OnClear();
|
||||
void OnNewBreakpoint();
|
||||
void OnLoad();
|
||||
void OnSave();
|
||||
|
||||
void UpdateIcons();
|
||||
|
||||
QToolBar* m_toolbar;
|
||||
QTableWidget* m_table;
|
||||
QAction* m_new;
|
||||
QAction* m_delete;
|
||||
QAction* m_clear;
|
||||
QAction* m_load;
|
||||
QAction* m_save;
|
||||
};
|
563
Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp
Normal file
563
Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp
Normal file
@ -0,0 +1,563 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Debugger/CodeViewWidget.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QHeaderView>
|
||||
#include <QInputDialog>
|
||||
#include <QKeyEvent>
|
||||
#include <QMenu>
|
||||
#include <QMouseEvent>
|
||||
#include <QResizeEvent>
|
||||
#include <QScrollBar>
|
||||
#include <QTableWidgetItem>
|
||||
#include <QWheelEvent>
|
||||
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Debugger/PPCDebugInterface.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
#include "Core/PowerPC/PPCAnalyst.h"
|
||||
#include "Core/PowerPC/PPCSymbolDB.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "DolphinQt/QtUtils/ActionHelper.h"
|
||||
#include "DolphinQt/Resources.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
constexpr size_t VALID_BRANCH_LENGTH = 10;
|
||||
|
||||
CodeViewWidget::CodeViewWidget()
|
||||
{
|
||||
setColumnCount(5);
|
||||
setShowGrid(false);
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
verticalScrollBar()->setHidden(true);
|
||||
|
||||
for (int i = 0; i < columnCount(); i++)
|
||||
{
|
||||
horizontalHeader()->setSectionResizeMode(i, QHeaderView::Fixed);
|
||||
}
|
||||
|
||||
verticalHeader()->hide();
|
||||
horizontalHeader()->hide();
|
||||
horizontalHeader()->setStretchLastSection(true);
|
||||
|
||||
setFont(Settings::Instance().GetDebugFont());
|
||||
|
||||
Update();
|
||||
|
||||
connect(this, &CodeViewWidget::customContextMenuRequested, this, &CodeViewWidget::OnContextMenu);
|
||||
connect(this, &CodeViewWidget::itemSelectionChanged, this, &CodeViewWidget::OnSelectionChanged);
|
||||
connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &QWidget::setFont);
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this] {
|
||||
m_address = PC;
|
||||
Update();
|
||||
});
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ThemeChanged, this, &CodeViewWidget::Update);
|
||||
}
|
||||
|
||||
static u32 GetBranchFromAddress(u32 addr)
|
||||
{
|
||||
std::string disasm = PowerPC::debug_interface.Disassemble(addr);
|
||||
size_t pos = disasm.find("->0x");
|
||||
|
||||
if (pos == std::string::npos)
|
||||
return 0;
|
||||
|
||||
std::string hex = disasm.substr(pos + 2);
|
||||
return std::stoul(hex, nullptr, 16);
|
||||
}
|
||||
|
||||
void CodeViewWidget::Update()
|
||||
{
|
||||
if (m_updating)
|
||||
return;
|
||||
|
||||
m_updating = true;
|
||||
|
||||
clearSelection();
|
||||
if (rowCount() == 0)
|
||||
setRowCount(1);
|
||||
|
||||
// Calculate (roughly) how many rows will fit in our table
|
||||
int rows = std::round((height() / static_cast<float>(rowHeight(0))) - 0.25);
|
||||
|
||||
setRowCount(rows);
|
||||
|
||||
for (int i = 0; i < rows; i++)
|
||||
setRowHeight(i, 24);
|
||||
|
||||
u32 pc = PowerPC::ppcState.pc;
|
||||
|
||||
if (Core::GetState() != Core::State::Paused && PowerPC::debug_interface.IsBreakpoint(pc))
|
||||
Core::SetState(Core::State::Paused);
|
||||
|
||||
const bool dark_theme = qApp->palette().color(QPalette::Base).valueF() < 0.5;
|
||||
|
||||
for (int i = 0; i < rowCount(); i++)
|
||||
{
|
||||
u32 addr = m_address - ((rowCount() / 2) * 4) + i * 4;
|
||||
u32 color = PowerPC::debug_interface.GetColor(addr);
|
||||
auto* bp_item = new QTableWidgetItem;
|
||||
auto* addr_item = new QTableWidgetItem(QStringLiteral("%1").arg(addr, 8, 16, QLatin1Char('0')));
|
||||
|
||||
std::string disas = PowerPC::debug_interface.Disassemble(addr);
|
||||
auto split = disas.find('\t');
|
||||
|
||||
std::string ins = (split == std::string::npos ? disas : disas.substr(0, split));
|
||||
std::string param = (split == std::string::npos ? "" : disas.substr(split + 1));
|
||||
std::string desc = PowerPC::debug_interface.GetDescription(addr);
|
||||
|
||||
auto* ins_item = new QTableWidgetItem(QString::fromStdString(ins));
|
||||
auto* param_item = new QTableWidgetItem(QString::fromStdString(param));
|
||||
auto* description_item = new QTableWidgetItem(QString::fromStdString(desc));
|
||||
|
||||
for (auto* item : {bp_item, addr_item, ins_item, param_item, description_item})
|
||||
{
|
||||
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||
item->setData(Qt::UserRole, addr);
|
||||
|
||||
if (addr == pc && item != bp_item)
|
||||
{
|
||||
item->setBackground(QColor(Qt::green));
|
||||
item->setForeground(QColor(Qt::black));
|
||||
}
|
||||
else if (color != 0xFFFFFF)
|
||||
{
|
||||
item->setBackground(dark_theme ? QColor(color).darker(240) : QColor(color));
|
||||
}
|
||||
}
|
||||
|
||||
// look for hex strings to decode branches
|
||||
std::string hex_str;
|
||||
size_t pos = param.find("0x");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
hex_str = param.substr(pos);
|
||||
}
|
||||
|
||||
if (hex_str.length() == VALID_BRANCH_LENGTH && desc != "---")
|
||||
{
|
||||
description_item->setText(tr("--> %1").arg(QString::fromStdString(
|
||||
PowerPC::debug_interface.GetDescription(GetBranchFromAddress(addr)))));
|
||||
param_item->setForeground(Qt::magenta);
|
||||
}
|
||||
|
||||
if (ins == "blr")
|
||||
ins_item->setForeground(dark_theme ? QColor(0xa0FFa0) : Qt::darkGreen);
|
||||
|
||||
if (PowerPC::debug_interface.IsBreakpoint(addr))
|
||||
{
|
||||
bp_item->setData(Qt::DecorationRole,
|
||||
Resources::GetScaledThemeIcon("debugger_breakpoint").pixmap(QSize(24, 24)));
|
||||
}
|
||||
|
||||
setItem(i, 0, bp_item);
|
||||
setItem(i, 1, addr_item);
|
||||
setItem(i, 2, ins_item);
|
||||
setItem(i, 3, param_item);
|
||||
setItem(i, 4, description_item);
|
||||
|
||||
if (addr == GetAddress())
|
||||
{
|
||||
selectRow(addr_item->row());
|
||||
}
|
||||
}
|
||||
|
||||
resizeColumnsToContents();
|
||||
setColumnWidth(0, 24 + 5);
|
||||
|
||||
g_symbolDB.FillInCallers();
|
||||
|
||||
repaint();
|
||||
m_updating = false;
|
||||
}
|
||||
|
||||
u32 CodeViewWidget::GetAddress() const
|
||||
{
|
||||
return m_address;
|
||||
}
|
||||
|
||||
void CodeViewWidget::SetAddress(u32 address, SetAddressUpdate update)
|
||||
{
|
||||
if (m_address == address)
|
||||
return;
|
||||
|
||||
m_address = address;
|
||||
if (update == SetAddressUpdate::WithUpdate)
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeViewWidget::ReplaceAddress(u32 address, ReplaceWith replace)
|
||||
{
|
||||
PowerPC::debug_interface.UnsetPatch(address);
|
||||
PowerPC::debug_interface.SetPatch(address, replace == ReplaceWith::BLR ? 0x4e800020 : 0x60000000);
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnContextMenu()
|
||||
{
|
||||
QMenu* menu = new QMenu(this);
|
||||
|
||||
bool running = Core::GetState() != Core::State::Uninitialized;
|
||||
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
bool has_symbol = g_symbolDB.GetSymbolFromAddr(addr);
|
||||
|
||||
auto* follow_branch_action =
|
||||
AddAction(menu, tr("Follow &branch"), this, &CodeViewWidget::OnFollowBranch);
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
AddAction(menu, tr("&Copy address"), this, &CodeViewWidget::OnCopyAddress);
|
||||
auto* copy_address_action =
|
||||
AddAction(menu, tr("Copy &function"), this, &CodeViewWidget::OnCopyFunction);
|
||||
auto* copy_line_action =
|
||||
AddAction(menu, tr("Copy code &line"), this, &CodeViewWidget::OnCopyCode);
|
||||
auto* copy_hex_action = AddAction(menu, tr("Copy &hex"), this, &CodeViewWidget::OnCopyHex);
|
||||
menu->addSeparator();
|
||||
|
||||
auto* symbol_rename_action =
|
||||
AddAction(menu, tr("&Rename symbol"), this, &CodeViewWidget::OnRenameSymbol);
|
||||
auto* symbol_size_action =
|
||||
AddAction(menu, tr("Set symbol &size"), this, &CodeViewWidget::OnSetSymbolSize);
|
||||
auto* symbol_end_action =
|
||||
AddAction(menu, tr("Set symbol &end address"), this, &CodeViewWidget::OnSetSymbolEndAddress);
|
||||
menu->addSeparator();
|
||||
|
||||
AddAction(menu, tr("Run &To Here"), this, &CodeViewWidget::OnRunToHere);
|
||||
auto* function_action =
|
||||
AddAction(menu, tr("&Add function"), this, &CodeViewWidget::OnAddFunction);
|
||||
auto* ppc_action = AddAction(menu, tr("PPC vs Host"), this, &CodeViewWidget::OnPPCComparison);
|
||||
auto* insert_blr_action = AddAction(menu, tr("&Insert blr"), this, &CodeViewWidget::OnInsertBLR);
|
||||
auto* insert_nop_action = AddAction(menu, tr("Insert &nop"), this, &CodeViewWidget::OnInsertNOP);
|
||||
auto* replace_action =
|
||||
AddAction(menu, tr("Re&place instruction"), this, &CodeViewWidget::OnReplaceInstruction);
|
||||
auto* restore_action =
|
||||
AddAction(menu, tr("Restore instruction"), this, &CodeViewWidget::OnRestoreInstruction);
|
||||
|
||||
follow_branch_action->setEnabled(running && GetBranchFromAddress(addr));
|
||||
|
||||
for (auto* action : {copy_address_action, copy_line_action, copy_hex_action, function_action,
|
||||
ppc_action, insert_blr_action, insert_nop_action, replace_action})
|
||||
action->setEnabled(running);
|
||||
|
||||
for (auto* action : {symbol_rename_action, symbol_size_action, symbol_end_action})
|
||||
action->setEnabled(has_symbol);
|
||||
|
||||
restore_action->setEnabled(running && PowerPC::debug_interface.HasEnabledPatch(addr));
|
||||
|
||||
menu->exec(QCursor::pos());
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnCopyAddress()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
QApplication::clipboard()->setText(QStringLiteral("%1").arg(addr, 8, 16, QLatin1Char('0')));
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnCopyCode()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
QApplication::clipboard()->setText(
|
||||
QString::fromStdString(PowerPC::debug_interface.Disassemble(addr)));
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnCopyFunction()
|
||||
{
|
||||
const u32 address = GetContextAddress();
|
||||
|
||||
const Common::Symbol* symbol = g_symbolDB.GetSymbolFromAddr(address);
|
||||
if (!symbol)
|
||||
return;
|
||||
|
||||
std::string text = symbol->name + "\r\n";
|
||||
// we got a function
|
||||
const u32 start = symbol->address;
|
||||
const u32 end = start + symbol->size;
|
||||
for (u32 addr = start; addr != end; addr += 4)
|
||||
{
|
||||
const std::string disasm = PowerPC::debug_interface.Disassemble(addr);
|
||||
text += StringFromFormat("%08x: ", addr) + disasm + "\r\n";
|
||||
}
|
||||
|
||||
QApplication::clipboard()->setText(QString::fromStdString(text));
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnCopyHex()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
const u32 instruction = PowerPC::debug_interface.ReadInstruction(addr);
|
||||
|
||||
QApplication::clipboard()->setText(
|
||||
QStringLiteral("%1").arg(instruction, 8, 16, QLatin1Char('0')));
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnRunToHere()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
PowerPC::debug_interface.SetBreakpoint(addr);
|
||||
PowerPC::debug_interface.RunToBreakpoint();
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnPPCComparison()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
emit RequestPPCComparison(addr);
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnAddFunction()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
g_symbolDB.AddFunction(addr);
|
||||
emit SymbolsChanged();
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnInsertBLR()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
ReplaceAddress(addr, ReplaceWith::BLR);
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnInsertNOP()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
ReplaceAddress(addr, ReplaceWith::NOP);
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnFollowBranch()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
u32 branch_addr = GetBranchFromAddress(addr);
|
||||
|
||||
if (!branch_addr)
|
||||
return;
|
||||
|
||||
SetAddress(branch_addr, SetAddressUpdate::WithUpdate);
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnRenameSymbol()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
Common::Symbol* symbol = g_symbolDB.GetSymbolFromAddr(addr);
|
||||
|
||||
if (!symbol)
|
||||
return;
|
||||
|
||||
bool good;
|
||||
QString name =
|
||||
QInputDialog::getText(this, tr("Rename symbol"), tr("Symbol name:"), QLineEdit::Normal,
|
||||
QString::fromStdString(symbol->name), &good);
|
||||
|
||||
if (good && !name.isEmpty())
|
||||
{
|
||||
symbol->Rename(name.toStdString());
|
||||
emit SymbolsChanged();
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnSelectionChanged()
|
||||
{
|
||||
if (m_address == PowerPC::ppcState.pc)
|
||||
{
|
||||
setStyleSheet(
|
||||
QStringLiteral("QTableView::item:selected {background-color: #00FF00; color: #000000;}"));
|
||||
}
|
||||
else if (!styleSheet().isEmpty())
|
||||
{
|
||||
setStyleSheet(QStringLiteral(""));
|
||||
}
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnSetSymbolSize()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
Common::Symbol* symbol = g_symbolDB.GetSymbolFromAddr(addr);
|
||||
|
||||
if (!symbol)
|
||||
return;
|
||||
|
||||
bool good;
|
||||
int size =
|
||||
QInputDialog::getInt(this, tr("Rename symbol"),
|
||||
tr("Set symbol size (%1):").arg(QString::fromStdString(symbol->name)),
|
||||
symbol->size, 1, 0xFFFF, 1, &good);
|
||||
|
||||
if (!good)
|
||||
return;
|
||||
|
||||
PPCAnalyst::ReanalyzeFunction(symbol->address, *symbol, size);
|
||||
emit SymbolsChanged();
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnSetSymbolEndAddress()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
Common::Symbol* symbol = g_symbolDB.GetSymbolFromAddr(addr);
|
||||
|
||||
if (!symbol)
|
||||
return;
|
||||
|
||||
bool good;
|
||||
QString name = QInputDialog::getText(
|
||||
this, tr("Set symbol end address"),
|
||||
tr("Symbol (%1) end address:").arg(QString::fromStdString(symbol->name)), QLineEdit::Normal,
|
||||
QStringLiteral("%1").arg(addr + symbol->size, 8, 16, QLatin1Char('0')), &good);
|
||||
|
||||
u32 address = name.toUInt(&good, 16);
|
||||
|
||||
if (!good)
|
||||
return;
|
||||
|
||||
PPCAnalyst::ReanalyzeFunction(symbol->address, *symbol, address - symbol->address);
|
||||
emit SymbolsChanged();
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnReplaceInstruction()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
if (!PowerPC::HostIsInstructionRAMAddress(addr))
|
||||
return;
|
||||
|
||||
const PowerPC::TryReadInstResult read_result = PowerPC::TryReadInstruction(addr);
|
||||
if (!read_result.valid)
|
||||
return;
|
||||
|
||||
bool good;
|
||||
QString name = QInputDialog::getText(
|
||||
this, tr("Change instruction"), tr("New instruction:"), QLineEdit::Normal,
|
||||
QStringLiteral("%1").arg(read_result.hex, 8, 16, QLatin1Char('0')), &good);
|
||||
|
||||
u32 code = name.toUInt(&good, 16);
|
||||
|
||||
if (good)
|
||||
{
|
||||
PowerPC::debug_interface.UnsetPatch(addr);
|
||||
PowerPC::debug_interface.SetPatch(addr, code);
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void CodeViewWidget::OnRestoreInstruction()
|
||||
{
|
||||
const u32 addr = GetContextAddress();
|
||||
|
||||
PowerPC::debug_interface.UnsetPatch(addr);
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeViewWidget::resizeEvent(QResizeEvent*)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeViewWidget::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
switch (event->key())
|
||||
{
|
||||
case Qt::Key_Up:
|
||||
m_address -= 3 * sizeof(u32);
|
||||
Update();
|
||||
return;
|
||||
case Qt::Key_Down:
|
||||
m_address += 3 * sizeof(u32);
|
||||
Update();
|
||||
return;
|
||||
case Qt::Key_PageUp:
|
||||
m_address -= rowCount() * sizeof(u32);
|
||||
Update();
|
||||
return;
|
||||
case Qt::Key_PageDown:
|
||||
m_address += rowCount() * sizeof(u32);
|
||||
Update();
|
||||
return;
|
||||
default:
|
||||
QWidget::keyPressEvent(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CodeViewWidget::wheelEvent(QWheelEvent* event)
|
||||
{
|
||||
int delta = event->delta() > 0 ? -1 : 1;
|
||||
|
||||
m_address += delta * 3 * sizeof(u32);
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeViewWidget::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
auto* item = itemAt(event->pos());
|
||||
if (item == nullptr)
|
||||
return;
|
||||
|
||||
const u32 addr = item->data(Qt::UserRole).toUInt();
|
||||
|
||||
m_context_address = addr;
|
||||
|
||||
switch (event->button())
|
||||
{
|
||||
case Qt::LeftButton:
|
||||
if (column(item) == 0)
|
||||
ToggleBreakpoint();
|
||||
else
|
||||
SetAddress(addr, SetAddressUpdate::WithUpdate);
|
||||
|
||||
Update();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CodeViewWidget::ToggleBreakpoint()
|
||||
{
|
||||
if (PowerPC::debug_interface.IsBreakpoint(GetContextAddress()))
|
||||
PowerPC::breakpoints.Remove(GetContextAddress());
|
||||
else
|
||||
PowerPC::breakpoints.Add(GetContextAddress());
|
||||
|
||||
emit BreakpointsChanged();
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeViewWidget::AddBreakpoint()
|
||||
{
|
||||
PowerPC::breakpoints.Add(GetContextAddress());
|
||||
|
||||
emit BreakpointsChanged();
|
||||
Update();
|
||||
}
|
||||
|
||||
u32 CodeViewWidget::GetContextAddress() const
|
||||
{
|
||||
return m_context_address;
|
||||
}
|
79
Source/Core/DolphinQt/Debugger/CodeViewWidget.h
Normal file
79
Source/Core/DolphinQt/Debugger/CodeViewWidget.h
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QTableWidget>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class QKeyEvent;
|
||||
class QMouseEvent;
|
||||
class QResizeEvent;
|
||||
|
||||
class CodeViewWidget : public QTableWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class SetAddressUpdate
|
||||
{
|
||||
WithUpdate,
|
||||
WithoutUpdate
|
||||
};
|
||||
|
||||
explicit CodeViewWidget();
|
||||
|
||||
u32 GetAddress() const;
|
||||
u32 GetContextAddress() const;
|
||||
void SetAddress(u32 address, SetAddressUpdate update);
|
||||
|
||||
void Update();
|
||||
|
||||
void ToggleBreakpoint();
|
||||
void AddBreakpoint();
|
||||
signals:
|
||||
void RequestPPCComparison(u32 addr);
|
||||
void SymbolsChanged();
|
||||
void BreakpointsChanged();
|
||||
|
||||
private:
|
||||
enum class ReplaceWith
|
||||
{
|
||||
BLR,
|
||||
NOP
|
||||
};
|
||||
|
||||
void ReplaceAddress(u32 address, ReplaceWith replace);
|
||||
|
||||
void resizeEvent(QResizeEvent*) override;
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void wheelEvent(QWheelEvent* event) override;
|
||||
|
||||
void OnContextMenu();
|
||||
|
||||
void OnFollowBranch();
|
||||
void OnCopyAddress();
|
||||
void OnCopyFunction();
|
||||
void OnCopyCode();
|
||||
void OnCopyHex();
|
||||
void OnRenameSymbol();
|
||||
void OnSelectionChanged();
|
||||
void OnSetSymbolSize();
|
||||
void OnSetSymbolEndAddress();
|
||||
void OnRunToHere();
|
||||
void OnAddFunction();
|
||||
void OnPPCComparison();
|
||||
void OnInsertBLR();
|
||||
void OnInsertNOP();
|
||||
void OnReplaceInstruction();
|
||||
void OnRestoreInstruction();
|
||||
|
||||
bool m_updating = false;
|
||||
|
||||
u32 m_address = 0;
|
||||
u32 m_context_address = 0;
|
||||
};
|
497
Source/Core/DolphinQt/Debugger/CodeWidget.cpp
Normal file
497
Source/Core/DolphinQt/Debugger/CodeWidget.cpp
Normal file
@ -0,0 +1,497 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Debugger/CodeWidget.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QSplitter>
|
||||
#include <QTableWidget>
|
||||
#include <QWidget>
|
||||
|
||||
#include "Common/Event.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Debugger/Debugger_SymbolMap.h"
|
||||
#include "Core/HW/CPU.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
#include "Core/PowerPC/PPCSymbolDB.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "DolphinQt/Host.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
CodeWidget::CodeWidget(QWidget* parent) : QDockWidget(parent)
|
||||
{
|
||||
setWindowTitle(tr("Code"));
|
||||
setObjectName(QStringLiteral("code"));
|
||||
|
||||
setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
restoreGeometry(settings.value(QStringLiteral("codewidget/geometry")).toByteArray());
|
||||
setFloating(settings.value(QStringLiteral("codewidget/floating")).toBool());
|
||||
|
||||
connect(&Settings::Instance(), &Settings::CodeVisibilityChanged,
|
||||
[this](bool visible) { setHidden(!visible); });
|
||||
|
||||
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, [this] {
|
||||
if (Core::GetState() == Core::State::Paused)
|
||||
SetAddress(PowerPC::ppcState.pc, CodeViewWidget::SetAddressUpdate::WithoutUpdate);
|
||||
Update();
|
||||
});
|
||||
|
||||
connect(Host::GetInstance(), &Host::NotifyMapLoaded, this, &CodeWidget::UpdateSymbols);
|
||||
|
||||
connect(&Settings::Instance(), &Settings::DebugModeToggled,
|
||||
[this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsCodeVisible()); });
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &CodeWidget::Update);
|
||||
|
||||
setHidden(!Settings::Instance().IsCodeVisible() || !Settings::Instance().IsDebugModeEnabled());
|
||||
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
||||
m_code_splitter->restoreState(
|
||||
settings.value(QStringLiteral("codewidget/codesplitter")).toByteArray());
|
||||
m_box_splitter->restoreState(
|
||||
settings.value(QStringLiteral("codewidget/boxsplitter")).toByteArray());
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
CodeWidget::~CodeWidget()
|
||||
{
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
settings.setValue(QStringLiteral("codewidget/geometry"), saveGeometry());
|
||||
settings.setValue(QStringLiteral("codewidget/floating"), isFloating());
|
||||
settings.setValue(QStringLiteral("codewidget/codesplitter"), m_code_splitter->saveState());
|
||||
settings.setValue(QStringLiteral("codewidget/boxplitter"), m_box_splitter->saveState());
|
||||
}
|
||||
|
||||
void CodeWidget::closeEvent(QCloseEvent*)
|
||||
{
|
||||
Settings::Instance().SetCodeVisible(false);
|
||||
}
|
||||
|
||||
void CodeWidget::CreateWidgets()
|
||||
{
|
||||
auto* layout = new QGridLayout;
|
||||
|
||||
m_search_address = new QLineEdit;
|
||||
m_search_symbols = new QLineEdit;
|
||||
m_code_view = new CodeViewWidget;
|
||||
|
||||
m_search_address->setPlaceholderText(tr("Search Address"));
|
||||
m_search_symbols->setPlaceholderText(tr("Filter Symbols"));
|
||||
|
||||
// Callstack
|
||||
auto* callstack_box = new QGroupBox(tr("Callstack"));
|
||||
auto* callstack_layout = new QVBoxLayout;
|
||||
m_callstack_list = new QListWidget;
|
||||
|
||||
callstack_box->setLayout(callstack_layout);
|
||||
callstack_layout->addWidget(m_callstack_list);
|
||||
|
||||
// Symbols
|
||||
auto* symbols_box = new QGroupBox(tr("Symbols"));
|
||||
auto* symbols_layout = new QVBoxLayout;
|
||||
m_symbols_list = new QListWidget;
|
||||
|
||||
symbols_box->setLayout(symbols_layout);
|
||||
symbols_layout->addWidget(m_symbols_list);
|
||||
|
||||
// Function calls
|
||||
auto* function_calls_box = new QGroupBox(tr("Function calls"));
|
||||
auto* function_calls_layout = new QVBoxLayout;
|
||||
m_function_calls_list = new QListWidget;
|
||||
|
||||
function_calls_box->setLayout(function_calls_layout);
|
||||
function_calls_layout->addWidget(m_function_calls_list);
|
||||
|
||||
// Function callers
|
||||
auto* function_callers_box = new QGroupBox(tr("Function callers"));
|
||||
auto* function_callers_layout = new QVBoxLayout;
|
||||
m_function_callers_list = new QListWidget;
|
||||
|
||||
function_callers_box->setLayout(function_callers_layout);
|
||||
function_callers_layout->addWidget(m_function_callers_list);
|
||||
|
||||
m_box_splitter = new QSplitter(Qt::Vertical);
|
||||
|
||||
m_box_splitter->addWidget(callstack_box);
|
||||
m_box_splitter->addWidget(symbols_box);
|
||||
m_box_splitter->addWidget(function_calls_box);
|
||||
m_box_splitter->addWidget(function_callers_box);
|
||||
|
||||
m_code_splitter = new QSplitter(Qt::Horizontal);
|
||||
|
||||
m_code_splitter->addWidget(m_box_splitter);
|
||||
m_code_splitter->addWidget(m_code_view);
|
||||
|
||||
layout->addWidget(m_search_address, 0, 0);
|
||||
layout->addWidget(m_search_symbols, 0, 1);
|
||||
layout->addWidget(m_code_splitter, 1, 0, -1, -1);
|
||||
|
||||
QWidget* widget = new QWidget(this);
|
||||
widget->setLayout(layout);
|
||||
setWidget(widget);
|
||||
}
|
||||
|
||||
void CodeWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_search_address, &QLineEdit::textChanged, this, &CodeWidget::OnSearchAddress);
|
||||
connect(m_search_symbols, &QLineEdit::textChanged, this, &CodeWidget::OnSearchSymbols);
|
||||
|
||||
connect(m_symbols_list, &QListWidget::itemClicked, this, &CodeWidget::OnSelectSymbol);
|
||||
connect(m_callstack_list, &QListWidget::itemSelectionChanged, this,
|
||||
&CodeWidget::OnSelectCallstack);
|
||||
connect(m_function_calls_list, &QListWidget::itemSelectionChanged, this,
|
||||
&CodeWidget::OnSelectFunctionCalls);
|
||||
connect(m_function_callers_list, &QListWidget::itemSelectionChanged, this,
|
||||
&CodeWidget::OnSelectFunctionCallers);
|
||||
|
||||
connect(m_code_view, &CodeViewWidget::SymbolsChanged, this, &CodeWidget::UpdateSymbols);
|
||||
connect(m_code_view, &CodeViewWidget::BreakpointsChanged, this,
|
||||
[this] { emit BreakpointsChanged(); });
|
||||
|
||||
connect(m_code_view, &CodeViewWidget::RequestPPCComparison, this,
|
||||
&CodeWidget::RequestPPCComparison);
|
||||
}
|
||||
|
||||
void CodeWidget::OnSearchAddress()
|
||||
{
|
||||
bool good = true;
|
||||
u32 address = m_search_address->text().toUInt(&good, 16);
|
||||
|
||||
QPalette palette;
|
||||
QFont font;
|
||||
|
||||
if (!good && !m_search_address->text().isEmpty())
|
||||
{
|
||||
font.setBold(true);
|
||||
palette.setColor(QPalette::Text, Qt::red);
|
||||
}
|
||||
|
||||
m_search_address->setPalette(palette);
|
||||
m_search_address->setFont(font);
|
||||
|
||||
if (good)
|
||||
m_code_view->SetAddress(address, CodeViewWidget::SetAddressUpdate::WithUpdate);
|
||||
|
||||
Update();
|
||||
|
||||
m_search_address->setFocus();
|
||||
}
|
||||
|
||||
void CodeWidget::OnSearchSymbols()
|
||||
{
|
||||
m_symbol_filter = m_search_symbols->text();
|
||||
UpdateSymbols();
|
||||
}
|
||||
|
||||
void CodeWidget::OnSelectSymbol()
|
||||
{
|
||||
const auto items = m_symbols_list->selectedItems();
|
||||
if (items.isEmpty())
|
||||
return;
|
||||
|
||||
const u32 address = items[0]->data(Qt::UserRole).toUInt();
|
||||
const Common::Symbol* symbol = g_symbolDB.GetSymbolFromAddr(address);
|
||||
|
||||
m_code_view->SetAddress(address, CodeViewWidget::SetAddressUpdate::WithUpdate);
|
||||
UpdateCallstack();
|
||||
UpdateFunctionCalls(symbol);
|
||||
UpdateFunctionCallers(symbol);
|
||||
|
||||
m_code_view->setFocus();
|
||||
}
|
||||
|
||||
void CodeWidget::OnSelectCallstack()
|
||||
{
|
||||
const auto items = m_callstack_list->selectedItems();
|
||||
if (items.isEmpty())
|
||||
return;
|
||||
|
||||
m_code_view->SetAddress(items[0]->data(Qt::UserRole).toUInt(),
|
||||
CodeViewWidget::SetAddressUpdate::WithUpdate);
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeWidget::OnSelectFunctionCalls()
|
||||
{
|
||||
const auto items = m_function_calls_list->selectedItems();
|
||||
if (items.isEmpty())
|
||||
return;
|
||||
|
||||
m_code_view->SetAddress(items[0]->data(Qt::UserRole).toUInt(),
|
||||
CodeViewWidget::SetAddressUpdate::WithUpdate);
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeWidget::OnSelectFunctionCallers()
|
||||
{
|
||||
const auto items = m_function_callers_list->selectedItems();
|
||||
if (items.isEmpty())
|
||||
return;
|
||||
|
||||
m_code_view->SetAddress(items[0]->data(Qt::UserRole).toUInt(),
|
||||
CodeViewWidget::SetAddressUpdate::WithUpdate);
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeWidget::SetAddress(u32 address, CodeViewWidget::SetAddressUpdate update)
|
||||
{
|
||||
m_code_view->SetAddress(address, update);
|
||||
}
|
||||
|
||||
void CodeWidget::Update()
|
||||
{
|
||||
const Common::Symbol* symbol = g_symbolDB.GetSymbolFromAddr(m_code_view->GetAddress());
|
||||
|
||||
UpdateCallstack();
|
||||
|
||||
m_code_view->Update();
|
||||
m_code_view->setFocus();
|
||||
|
||||
if (!symbol)
|
||||
return;
|
||||
|
||||
UpdateFunctionCalls(symbol);
|
||||
UpdateFunctionCallers(symbol);
|
||||
}
|
||||
|
||||
void CodeWidget::UpdateCallstack()
|
||||
{
|
||||
if (Core::GetState() == Core::State::Starting)
|
||||
return;
|
||||
|
||||
m_callstack_list->clear();
|
||||
|
||||
std::vector<Dolphin_Debugger::CallstackEntry> stack;
|
||||
|
||||
bool success = Dolphin_Debugger::GetCallstack(stack);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
m_callstack_list->addItem(tr("Invalid callstack"));
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& frame : stack)
|
||||
{
|
||||
auto* item =
|
||||
new QListWidgetItem(QString::fromStdString(frame.Name.substr(0, frame.Name.length() - 1)));
|
||||
item->setData(Qt::UserRole, frame.vAddress);
|
||||
|
||||
m_callstack_list->addItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
void CodeWidget::UpdateSymbols()
|
||||
{
|
||||
QString selection = m_symbols_list->selectedItems().isEmpty() ?
|
||||
QStringLiteral("") :
|
||||
m_symbols_list->selectedItems()[0]->text();
|
||||
m_symbols_list->clear();
|
||||
|
||||
for (const auto& symbol : g_symbolDB.Symbols())
|
||||
{
|
||||
QString name = QString::fromStdString(symbol.second.name);
|
||||
|
||||
auto* item = new QListWidgetItem(name);
|
||||
if (name == selection)
|
||||
item->setSelected(true);
|
||||
|
||||
// Disable non-function symbols as you can't do anything with them.
|
||||
if (symbol.second.type != Common::Symbol::Type::Function)
|
||||
item->setFlags(Qt::NoItemFlags);
|
||||
|
||||
item->setData(Qt::UserRole, symbol.second.address);
|
||||
|
||||
if (name.indexOf(m_symbol_filter) != -1)
|
||||
m_symbols_list->addItem(item);
|
||||
}
|
||||
|
||||
m_symbols_list->sortItems();
|
||||
}
|
||||
|
||||
void CodeWidget::UpdateFunctionCalls(const Common::Symbol* symbol)
|
||||
{
|
||||
m_function_calls_list->clear();
|
||||
|
||||
for (const auto& call : symbol->calls)
|
||||
{
|
||||
const u32 addr = call.function;
|
||||
const Common::Symbol* call_symbol = g_symbolDB.GetSymbolFromAddr(addr);
|
||||
|
||||
if (call_symbol)
|
||||
{
|
||||
auto* item = new QListWidgetItem(
|
||||
QString::fromStdString(StringFromFormat("> %s (%08x)", call_symbol->name.c_str(), addr)));
|
||||
item->setData(Qt::UserRole, addr);
|
||||
|
||||
m_function_calls_list->addItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CodeWidget::UpdateFunctionCallers(const Common::Symbol* symbol)
|
||||
{
|
||||
m_function_callers_list->clear();
|
||||
|
||||
for (const auto& caller : symbol->callers)
|
||||
{
|
||||
const u32 addr = caller.call_address;
|
||||
const Common::Symbol* caller_symbol = g_symbolDB.GetSymbolFromAddr(addr);
|
||||
|
||||
if (caller_symbol)
|
||||
{
|
||||
auto* item = new QListWidgetItem(QString::fromStdString(
|
||||
StringFromFormat("< %s (%08x)", caller_symbol->name.c_str(), addr)));
|
||||
item->setData(Qt::UserRole, addr);
|
||||
|
||||
m_function_callers_list->addItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CodeWidget::Step()
|
||||
{
|
||||
if (!CPU::IsStepping())
|
||||
return;
|
||||
|
||||
Common::Event sync_event;
|
||||
|
||||
PowerPC::CoreMode old_mode = PowerPC::GetMode();
|
||||
PowerPC::SetMode(PowerPC::CoreMode::Interpreter);
|
||||
PowerPC::breakpoints.ClearAllTemporary();
|
||||
CPU::StepOpcode(&sync_event);
|
||||
sync_event.WaitFor(std::chrono::milliseconds(20));
|
||||
PowerPC::SetMode(old_mode);
|
||||
Core::DisplayMessage(tr("Step successful!").toStdString(), 2000);
|
||||
// Will get a UpdateDisasmDialog(), don't update the GUI here.
|
||||
}
|
||||
|
||||
void CodeWidget::StepOver()
|
||||
{
|
||||
if (!CPU::IsStepping())
|
||||
return;
|
||||
|
||||
UGeckoInstruction inst = PowerPC::HostRead_Instruction(PC);
|
||||
if (inst.LK)
|
||||
{
|
||||
PowerPC::breakpoints.ClearAllTemporary();
|
||||
PowerPC::breakpoints.Add(PC + 4, true);
|
||||
CPU::EnableStepping(false);
|
||||
Core::DisplayMessage(tr("Step over in progress...").toStdString(), 2000);
|
||||
}
|
||||
else
|
||||
{
|
||||
Step();
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true on a rfi, blr or on a bclr that evaluates to true.
|
||||
static bool WillInstructionReturn(UGeckoInstruction inst)
|
||||
{
|
||||
// Is a rfi instruction
|
||||
if (inst.hex == 0x4C000064u)
|
||||
return true;
|
||||
bool counter = (inst.BO_2 >> 2 & 1) != 0 || (CTR != 0) != ((inst.BO_2 >> 1 & 1) != 0);
|
||||
bool condition = inst.BO_2 >> 4 != 0 || PowerPC::GetCRBit(inst.BI_2) == (inst.BO_2 >> 3 & 1);
|
||||
bool isBclr = inst.OPCD_7 == 0b010011 && (inst.hex >> 1 & 0b10000) != 0;
|
||||
return isBclr && counter && condition && !inst.LK_3;
|
||||
}
|
||||
|
||||
void CodeWidget::StepOut()
|
||||
{
|
||||
if (!CPU::IsStepping())
|
||||
return;
|
||||
|
||||
CPU::PauseAndLock(true, false);
|
||||
PowerPC::breakpoints.ClearAllTemporary();
|
||||
|
||||
// Keep stepping until the next return instruction or timeout after five seconds
|
||||
using clock = std::chrono::steady_clock;
|
||||
clock::time_point timeout = clock::now() + std::chrono::seconds(5);
|
||||
PowerPC::CoreMode old_mode = PowerPC::GetMode();
|
||||
PowerPC::SetMode(PowerPC::CoreMode::Interpreter);
|
||||
|
||||
// Loop until either the current instruction is a return instruction with no Link flag
|
||||
// or a breakpoint is detected so it can step at the breakpoint. If the PC is currently
|
||||
// on a breakpoint, skip it.
|
||||
UGeckoInstruction inst = PowerPC::HostRead_Instruction(PC);
|
||||
do
|
||||
{
|
||||
if (WillInstructionReturn(inst))
|
||||
{
|
||||
PowerPC::SingleStep();
|
||||
break;
|
||||
}
|
||||
|
||||
if (inst.LK)
|
||||
{
|
||||
// Step over branches
|
||||
u32 next_pc = PC + 4;
|
||||
do
|
||||
{
|
||||
PowerPC::SingleStep();
|
||||
} while (PC != next_pc && clock::now() < timeout &&
|
||||
!PowerPC::breakpoints.IsAddressBreakPoint(PC));
|
||||
}
|
||||
else
|
||||
{
|
||||
PowerPC::SingleStep();
|
||||
}
|
||||
|
||||
inst = PowerPC::HostRead_Instruction(PC);
|
||||
} while (clock::now() < timeout && !PowerPC::breakpoints.IsAddressBreakPoint(PC));
|
||||
|
||||
PowerPC::SetMode(old_mode);
|
||||
CPU::PauseAndLock(false, false);
|
||||
|
||||
emit Host::GetInstance()->UpdateDisasmDialog();
|
||||
|
||||
if (PowerPC::breakpoints.IsAddressBreakPoint(PC))
|
||||
Core::DisplayMessage(tr("Breakpoint encountered! Step out aborted.").toStdString(), 2000);
|
||||
else if (clock::now() >= timeout)
|
||||
Core::DisplayMessage(tr("Step out timed out!").toStdString(), 2000);
|
||||
else
|
||||
Core::DisplayMessage(tr("Step out successful!").toStdString(), 2000);
|
||||
}
|
||||
|
||||
void CodeWidget::Skip()
|
||||
{
|
||||
PC += 4;
|
||||
ShowPC();
|
||||
}
|
||||
|
||||
void CodeWidget::ShowPC()
|
||||
{
|
||||
m_code_view->SetAddress(PC, CodeViewWidget::SetAddressUpdate::WithUpdate);
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeWidget::SetPC()
|
||||
{
|
||||
PC = m_code_view->GetAddress();
|
||||
Update();
|
||||
}
|
||||
|
||||
void CodeWidget::ToggleBreakpoint()
|
||||
{
|
||||
m_code_view->ToggleBreakpoint();
|
||||
}
|
||||
|
||||
void CodeWidget::AddBreakpoint()
|
||||
{
|
||||
m_code_view->AddBreakpoint();
|
||||
}
|
76
Source/Core/DolphinQt/Debugger/CodeWidget.h
Normal file
76
Source/Core/DolphinQt/Debugger/CodeWidget.h
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QString>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "DolphinQt/Debugger/CodeViewWidget.h"
|
||||
|
||||
class QCloseEvent;
|
||||
class QLineEdit;
|
||||
class QSplitter;
|
||||
class QListWidget;
|
||||
class QTableWidget;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
struct Symbol;
|
||||
}
|
||||
|
||||
class CodeWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CodeWidget(QWidget* parent = nullptr);
|
||||
~CodeWidget();
|
||||
|
||||
void Step();
|
||||
void StepOver();
|
||||
void StepOut();
|
||||
void Skip();
|
||||
void ShowPC();
|
||||
void SetPC();
|
||||
|
||||
void ToggleBreakpoint();
|
||||
void AddBreakpoint();
|
||||
void SetAddress(u32 address, CodeViewWidget::SetAddressUpdate update);
|
||||
|
||||
void Update();
|
||||
void UpdateSymbols();
|
||||
signals:
|
||||
void BreakpointsChanged();
|
||||
void RequestPPCComparison(u32 addr);
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void UpdateCallstack();
|
||||
void UpdateFunctionCalls(const Common::Symbol* symbol);
|
||||
void UpdateFunctionCallers(const Common::Symbol* symbol);
|
||||
|
||||
void OnSearchAddress();
|
||||
void OnSearchSymbols();
|
||||
void OnSelectSymbol();
|
||||
void OnSelectCallstack();
|
||||
void OnSelectFunctionCallers();
|
||||
void OnSelectFunctionCalls();
|
||||
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
|
||||
QLineEdit* m_search_address;
|
||||
QLineEdit* m_search_symbols;
|
||||
|
||||
QListWidget* m_callstack_list;
|
||||
QListWidget* m_symbols_list;
|
||||
QListWidget* m_function_calls_list;
|
||||
QListWidget* m_function_callers_list;
|
||||
CodeViewWidget* m_code_view;
|
||||
QSplitter* m_box_splitter;
|
||||
QSplitter* m_code_splitter;
|
||||
|
||||
QString m_symbol_filter;
|
||||
};
|
207
Source/Core/DolphinQt/Debugger/JITWidget.cpp
Normal file
207
Source/Core/DolphinQt/Debugger/JITWidget.cpp
Normal file
@ -0,0 +1,207 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Debugger/JITWidget.h"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QSplitter>
|
||||
#include <QTableWidget>
|
||||
#include <QTextBrowser>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/GekkoDisassembler.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Core/PowerPC/PPCAnalyst.h"
|
||||
#include "UICommon/Disassembler.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
JITWidget::JITWidget(QWidget* parent) : QDockWidget(parent)
|
||||
{
|
||||
setWindowTitle(tr("JIT Blocks"));
|
||||
setObjectName(QStringLiteral("jitwidget"));
|
||||
|
||||
setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
CreateWidgets();
|
||||
|
||||
restoreGeometry(settings.value(QStringLiteral("jitwidget/geometry")).toByteArray());
|
||||
setFloating(settings.value(QStringLiteral("jitwidget/floating")).toBool());
|
||||
|
||||
m_table_splitter->restoreState(
|
||||
settings.value(QStringLiteral("jitwidget/tablesplitter")).toByteArray());
|
||||
m_asm_splitter->restoreState(
|
||||
settings.value(QStringLiteral("jitwidget/asmsplitter")).toByteArray());
|
||||
|
||||
connect(&Settings::Instance(), &Settings::JITVisibilityChanged,
|
||||
[this](bool visible) { setHidden(!visible); });
|
||||
|
||||
connect(&Settings::Instance(), &Settings::DebugModeToggled,
|
||||
[this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsJITVisible()); });
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &JITWidget::Update);
|
||||
|
||||
setHidden(!Settings::Instance().IsJITVisible() || !Settings::Instance().IsDebugModeEnabled());
|
||||
|
||||
ConnectWidgets();
|
||||
|
||||
#if defined(_M_X86)
|
||||
m_disassembler = GetNewDisassembler("x86");
|
||||
#elif defined(_M_ARM_64)
|
||||
m_disassembler = GetNewDisassembler("aarch64");
|
||||
#else
|
||||
m_disassembler = GetNewDisassembler("UNK");
|
||||
#endif
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
JITWidget::~JITWidget()
|
||||
{
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
settings.setValue(QStringLiteral("jitwidget/geometry"), saveGeometry());
|
||||
settings.setValue(QStringLiteral("jitwidget/floating"), isFloating());
|
||||
settings.setValue(QStringLiteral("jitwidget/tablesplitter"), m_table_splitter->saveState());
|
||||
settings.setValue(QStringLiteral("jitwidget/asmsplitter"), m_asm_splitter->saveState());
|
||||
}
|
||||
|
||||
void JITWidget::CreateWidgets()
|
||||
{
|
||||
m_table_widget = new QTableWidget;
|
||||
|
||||
m_table_widget->setColumnCount(7);
|
||||
m_table_widget->setHorizontalHeaderLabels(
|
||||
{tr("Address"), tr("PPC Size"), tr("Host Size"),
|
||||
// i18n: The symbolic name of a code block
|
||||
tr("Symbol"),
|
||||
// i18n: These are the kinds of flags that a CPU uses (e.g. carry),
|
||||
// not the kinds of flags that represent e.g. countries
|
||||
tr("Flags"),
|
||||
// i18n: The number of times a code block has been executed
|
||||
tr("NumExec"),
|
||||
// i18n: Performance cost, not monetary cost
|
||||
tr("Cost")});
|
||||
|
||||
m_ppc_asm_widget = new QTextBrowser;
|
||||
m_host_asm_widget = new QTextBrowser;
|
||||
|
||||
m_table_splitter = new QSplitter(Qt::Vertical);
|
||||
m_asm_splitter = new QSplitter(Qt::Horizontal);
|
||||
|
||||
m_refresh_button = new QPushButton(tr("Refresh"));
|
||||
|
||||
m_table_splitter->addWidget(m_table_widget);
|
||||
m_table_splitter->addWidget(m_asm_splitter);
|
||||
|
||||
m_asm_splitter->addWidget(m_ppc_asm_widget);
|
||||
m_asm_splitter->addWidget(m_host_asm_widget);
|
||||
|
||||
QWidget* widget = new QWidget;
|
||||
auto* layout = new QVBoxLayout;
|
||||
widget->setLayout(layout);
|
||||
|
||||
layout->addWidget(m_table_splitter);
|
||||
layout->addWidget(m_refresh_button);
|
||||
|
||||
setWidget(widget);
|
||||
}
|
||||
|
||||
void JITWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_refresh_button, &QPushButton::pressed, this, &JITWidget::Update);
|
||||
}
|
||||
|
||||
void JITWidget::Compare(u32 address)
|
||||
{
|
||||
m_address = address;
|
||||
Update();
|
||||
}
|
||||
|
||||
void JITWidget::Update()
|
||||
{
|
||||
if (!m_address)
|
||||
{
|
||||
m_ppc_asm_widget->setHtml(QStringLiteral("<i>%1</i>").arg(tr("(ppc)")));
|
||||
m_host_asm_widget->setHtml(QStringLiteral("<i>%1</i>").arg(tr("(host)")));
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Actually do something with the table (Wx doesn't)
|
||||
|
||||
// Get host side code disassembly
|
||||
u32 host_instructions_count = 0;
|
||||
u32 host_code_size = 0;
|
||||
std::string host_instructions_disasm;
|
||||
host_instructions_disasm =
|
||||
DisassembleBlock(m_disassembler.get(), &m_address, &host_instructions_count, &host_code_size);
|
||||
|
||||
m_host_asm_widget->setHtml(
|
||||
QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(host_instructions_disasm)));
|
||||
|
||||
// == Fill in ppc box
|
||||
u32 ppc_addr = m_address;
|
||||
PPCAnalyst::CodeBuffer code_buffer(32000);
|
||||
PPCAnalyst::BlockStats st;
|
||||
PPCAnalyst::BlockRegStats gpa;
|
||||
PPCAnalyst::BlockRegStats fpa;
|
||||
PPCAnalyst::CodeBlock code_block;
|
||||
PPCAnalyst::PPCAnalyzer analyzer;
|
||||
analyzer.SetOption(PPCAnalyst::PPCAnalyzer::OPTION_CONDITIONAL_CONTINUE);
|
||||
analyzer.SetOption(PPCAnalyst::PPCAnalyzer::OPTION_BRANCH_FOLLOW);
|
||||
|
||||
code_block.m_stats = &st;
|
||||
code_block.m_gpa = &gpa;
|
||||
code_block.m_fpa = &fpa;
|
||||
|
||||
if (analyzer.Analyze(ppc_addr, &code_block, &code_buffer, code_buffer.size()) != 0xFFFFFFFF)
|
||||
{
|
||||
std::ostringstream ppc_disasm;
|
||||
for (u32 i = 0; i < code_block.m_num_instructions; i++)
|
||||
{
|
||||
const PPCAnalyst::CodeOp& op = code_buffer[i];
|
||||
const std::string opcode = Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address);
|
||||
ppc_disasm << std::setfill('0') << std::setw(8) << std::hex << op.address;
|
||||
ppc_disasm << " " << opcode << std::endl;
|
||||
}
|
||||
|
||||
// Add stats to the end of the ppc box since it's generally the shortest.
|
||||
ppc_disasm << std::dec << std::endl;
|
||||
|
||||
// Add some generic analysis
|
||||
if (st.isFirstBlockOfFunction)
|
||||
ppc_disasm << "(first block of function)" << std::endl;
|
||||
if (st.isLastBlockOfFunction)
|
||||
ppc_disasm << "(last block of function)" << std::endl;
|
||||
|
||||
ppc_disasm << st.numCycles << " estimated cycles" << std::endl;
|
||||
|
||||
ppc_disasm << "Num instr: PPC: " << code_block.m_num_instructions
|
||||
<< " Host: " << host_instructions_count << " (blowup: "
|
||||
<< 100 * host_instructions_count / code_block.m_num_instructions - 100 << "%)"
|
||||
<< std::endl;
|
||||
|
||||
ppc_disasm << "Num bytes: PPC: " << code_block.m_num_instructions * 4
|
||||
<< " Host: " << host_code_size
|
||||
<< " (blowup: " << 100 * host_code_size / (4 * code_block.m_num_instructions) - 100
|
||||
<< "%)" << std::endl;
|
||||
|
||||
m_ppc_asm_widget->setHtml(
|
||||
QStringLiteral("<pre>%1</pre>").arg(QString::fromStdString(ppc_disasm.str())));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_host_asm_widget->setHtml(
|
||||
QStringLiteral("<pre>%1</pre>")
|
||||
.arg(QString::fromStdString(StringFromFormat("(non-code address: %08x)", m_address))));
|
||||
m_ppc_asm_widget->setHtml(QStringLiteral("<i>---</i>"));
|
||||
}
|
||||
}
|
||||
|
||||
void JITWidget::closeEvent(QCloseEvent*)
|
||||
{
|
||||
Settings::Instance().SetJITVisible(false);
|
||||
}
|
44
Source/Core/DolphinQt/Debugger/JITWidget.h
Normal file
44
Source/Core/DolphinQt/Debugger/JITWidget.h
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <memory>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class QCloseEvent;
|
||||
class QSplitter;
|
||||
class QTextBrowser;
|
||||
class QTableWidget;
|
||||
class QPushButton;
|
||||
class HostDisassembler;
|
||||
|
||||
class JITWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JITWidget(QWidget* parent = nullptr);
|
||||
~JITWidget();
|
||||
|
||||
void Compare(u32 address);
|
||||
|
||||
private:
|
||||
void Update();
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
|
||||
QTableWidget* m_table_widget;
|
||||
QTextBrowser* m_ppc_asm_widget;
|
||||
QTextBrowser* m_host_asm_widget;
|
||||
QSplitter* m_table_splitter;
|
||||
QSplitter* m_asm_splitter;
|
||||
QPushButton* m_refresh_button;
|
||||
|
||||
std::unique_ptr<HostDisassembler> m_disassembler;
|
||||
u32 m_address = 0;
|
||||
};
|
366
Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp
Normal file
366
Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp
Normal file
@ -0,0 +1,366 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Debugger/MemoryViewWidget.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QHeaderView>
|
||||
#include <QMenu>
|
||||
#include <QMouseEvent>
|
||||
#include <QScrollBar>
|
||||
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
|
||||
#include "Core/Core.h"
|
||||
#include "Core/PowerPC/BreakPoints.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
||||
#include "DolphinQt/QtUtils/ActionHelper.h"
|
||||
#include "DolphinQt/Resources.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
MemoryViewWidget::MemoryViewWidget(QWidget* parent) : QTableWidget(parent)
|
||||
{
|
||||
horizontalHeader()->hide();
|
||||
verticalHeader()->hide();
|
||||
verticalScrollBar()->setHidden(true);
|
||||
setShowGrid(false);
|
||||
|
||||
setFont(Settings::Instance().GetDebugFont());
|
||||
|
||||
connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &QWidget::setFont);
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this] { Update(); });
|
||||
connect(this, &MemoryViewWidget::customContextMenuRequested, this,
|
||||
&MemoryViewWidget::OnContextMenu);
|
||||
connect(&Settings::Instance(), &Settings::ThemeChanged, this, &MemoryViewWidget::Update);
|
||||
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
static int GetColumnCount(MemoryViewWidget::Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MemoryViewWidget::Type::ASCII:
|
||||
case MemoryViewWidget::Type::U8:
|
||||
return 16;
|
||||
case MemoryViewWidget::Type::U16:
|
||||
return 8;
|
||||
case MemoryViewWidget::Type::U32:
|
||||
case MemoryViewWidget::Type::Float32:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryViewWidget::Update()
|
||||
{
|
||||
clearSelection();
|
||||
|
||||
setColumnCount(3 + GetColumnCount(m_type));
|
||||
|
||||
if (rowCount() == 0)
|
||||
setRowCount(1);
|
||||
|
||||
setRowHeight(0, 24);
|
||||
|
||||
// Calculate (roughly) how many rows will fit in our table
|
||||
int rows = std::round((height() / static_cast<float>(rowHeight(0))) - 0.25);
|
||||
|
||||
setRowCount(rows);
|
||||
|
||||
for (int i = 0; i < rows; i++)
|
||||
{
|
||||
setRowHeight(i, 24);
|
||||
|
||||
u32 addr = m_address - ((rowCount() / 2) * 16) + i * 16;
|
||||
|
||||
auto* bp_item = new QTableWidgetItem;
|
||||
bp_item->setFlags(Qt::ItemIsEnabled);
|
||||
bp_item->setData(Qt::UserRole, addr);
|
||||
|
||||
setItem(i, 0, bp_item);
|
||||
|
||||
auto* addr_item = new QTableWidgetItem(QStringLiteral("%1").arg(addr, 8, 16, QLatin1Char('0')));
|
||||
|
||||
addr_item->setData(Qt::UserRole, addr);
|
||||
addr_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||
|
||||
setItem(i, 1, addr_item);
|
||||
|
||||
if (addr == m_address)
|
||||
addr_item->setSelected(true);
|
||||
|
||||
if (Core::GetState() != Core::State::Paused || !PowerPC::HostIsRAMAddress(addr))
|
||||
{
|
||||
for (int c = 2; c < columnCount(); c++)
|
||||
{
|
||||
auto* item = new QTableWidgetItem(QStringLiteral("-"));
|
||||
item->setFlags(Qt::ItemIsEnabled);
|
||||
item->setData(Qt::UserRole, addr);
|
||||
|
||||
setItem(i, c, item);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* description_item =
|
||||
new QTableWidgetItem(QString::fromStdString(PowerPC::debug_interface.GetDescription(addr)));
|
||||
|
||||
description_item->setForeground(Qt::blue);
|
||||
description_item->setFlags(Qt::ItemIsEnabled);
|
||||
|
||||
setItem(i, columnCount() - 1, description_item);
|
||||
|
||||
bool row_breakpoint = true;
|
||||
|
||||
auto update_values = [&](auto value_to_string) {
|
||||
for (int c = 0; c < GetColumnCount(m_type); c++)
|
||||
{
|
||||
auto* hex_item = new QTableWidgetItem;
|
||||
hex_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||
const u32 address = addr + c * (16 / GetColumnCount(m_type));
|
||||
|
||||
if (PowerPC::memchecks.OverlapsMemcheck(address, 16 / GetColumnCount(m_type)))
|
||||
hex_item->setBackground(Qt::red);
|
||||
else
|
||||
row_breakpoint = false;
|
||||
|
||||
setItem(i, 2 + c, hex_item);
|
||||
|
||||
if (PowerPC::HostIsRAMAddress(address))
|
||||
{
|
||||
hex_item->setText(value_to_string(address));
|
||||
hex_item->setData(Qt::UserRole, address);
|
||||
}
|
||||
else
|
||||
{
|
||||
hex_item->setFlags(0);
|
||||
hex_item->setText(QStringLiteral("-"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
switch (m_type)
|
||||
{
|
||||
case Type::U8:
|
||||
update_values([](u32 address) {
|
||||
const u8 value = PowerPC::HostRead_U8(address);
|
||||
return QStringLiteral("%1").arg(value, 2, 16, QLatin1Char('0'));
|
||||
});
|
||||
break;
|
||||
case Type::ASCII:
|
||||
update_values([](u32 address) {
|
||||
const char value = PowerPC::HostRead_U8(address);
|
||||
return std::isprint(value) ? QString{QChar::fromLatin1(value)} : QStringLiteral(".");
|
||||
});
|
||||
break;
|
||||
case Type::U16:
|
||||
update_values([](u32 address) {
|
||||
const u16 value = PowerPC::HostRead_U16(address);
|
||||
return QStringLiteral("%1").arg(value, 4, 16, QLatin1Char('0'));
|
||||
});
|
||||
break;
|
||||
case Type::U32:
|
||||
update_values([](u32 address) {
|
||||
const u32 value = PowerPC::HostRead_U32(address);
|
||||
return QStringLiteral("%1").arg(value, 8, 16, QLatin1Char('0'));
|
||||
});
|
||||
break;
|
||||
case Type::Float32:
|
||||
update_values([](u32 address) { return QString::number(PowerPC::HostRead_F32(address)); });
|
||||
break;
|
||||
}
|
||||
|
||||
if (row_breakpoint)
|
||||
{
|
||||
bp_item->setData(Qt::DecorationRole,
|
||||
Resources::GetScaledThemeIcon("debugger_breakpoint").pixmap(QSize(24, 24)));
|
||||
}
|
||||
}
|
||||
|
||||
setColumnWidth(0, 24 + 5);
|
||||
for (int i = 1; i < columnCount(); i++)
|
||||
{
|
||||
resizeColumnToContents(i);
|
||||
// Add some extra spacing because the default width is too small in most cases
|
||||
setColumnWidth(i, columnWidth(i) * 1.1);
|
||||
}
|
||||
|
||||
viewport()->update();
|
||||
update();
|
||||
}
|
||||
|
||||
void MemoryViewWidget::SetType(Type type)
|
||||
{
|
||||
if (m_type == type)
|
||||
return;
|
||||
|
||||
m_type = type;
|
||||
Update();
|
||||
}
|
||||
|
||||
void MemoryViewWidget::SetBPType(BPType type)
|
||||
{
|
||||
m_bp_type = type;
|
||||
}
|
||||
|
||||
void MemoryViewWidget::SetAddress(u32 address)
|
||||
{
|
||||
if (m_address == address)
|
||||
return;
|
||||
|
||||
m_address = address;
|
||||
Update();
|
||||
}
|
||||
|
||||
void MemoryViewWidget::SetBPLoggingEnabled(bool enabled)
|
||||
{
|
||||
m_do_log = enabled;
|
||||
}
|
||||
|
||||
void MemoryViewWidget::resizeEvent(QResizeEvent*)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
|
||||
void MemoryViewWidget::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
switch (event->key())
|
||||
{
|
||||
case Qt::Key_Up:
|
||||
m_address -= 3 * 16;
|
||||
Update();
|
||||
return;
|
||||
case Qt::Key_Down:
|
||||
m_address += 3 * 16;
|
||||
Update();
|
||||
return;
|
||||
case Qt::Key_PageUp:
|
||||
m_address -= rowCount() * 16;
|
||||
Update();
|
||||
return;
|
||||
case Qt::Key_PageDown:
|
||||
m_address += rowCount() * 16;
|
||||
Update();
|
||||
return;
|
||||
default:
|
||||
QWidget::keyPressEvent(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u32 MemoryViewWidget::GetContextAddress() const
|
||||
{
|
||||
return m_context_address;
|
||||
}
|
||||
|
||||
void MemoryViewWidget::ToggleRowBreakpoint(bool row)
|
||||
{
|
||||
TMemCheck check;
|
||||
|
||||
const u32 addr = row ? GetContextAddress() & 0xFFFFFFF0 : GetContextAddress();
|
||||
const auto length = row ? 16 : (16 / GetColumnCount(m_type));
|
||||
|
||||
if (!PowerPC::memchecks.OverlapsMemcheck(addr, length))
|
||||
{
|
||||
check.start_address = addr;
|
||||
check.end_address = check.start_address + length - 1;
|
||||
check.is_ranged = length > 0;
|
||||
check.is_break_on_read = (m_bp_type == BPType::ReadOnly || m_bp_type == BPType::ReadWrite);
|
||||
check.is_break_on_write = (m_bp_type == BPType::WriteOnly || m_bp_type == BPType::ReadWrite);
|
||||
check.log_on_hit = m_do_log;
|
||||
check.break_on_hit = true;
|
||||
|
||||
PowerPC::memchecks.Add(check);
|
||||
}
|
||||
else
|
||||
{
|
||||
PowerPC::memchecks.Remove(addr);
|
||||
}
|
||||
|
||||
emit BreakpointsChanged();
|
||||
Update();
|
||||
}
|
||||
|
||||
void MemoryViewWidget::ToggleBreakpoint()
|
||||
{
|
||||
ToggleRowBreakpoint(false);
|
||||
}
|
||||
|
||||
void MemoryViewWidget::wheelEvent(QWheelEvent* event)
|
||||
{
|
||||
int delta = event->delta() > 0 ? -1 : 1;
|
||||
|
||||
m_address += delta * 3 * 16;
|
||||
Update();
|
||||
}
|
||||
|
||||
void MemoryViewWidget::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
auto* item = itemAt(event->pos());
|
||||
if (item == nullptr)
|
||||
return;
|
||||
|
||||
const u32 addr = item->data(Qt::UserRole).toUInt();
|
||||
|
||||
m_context_address = addr;
|
||||
|
||||
switch (event->button())
|
||||
{
|
||||
case Qt::LeftButton:
|
||||
if (column(item) == 0)
|
||||
ToggleRowBreakpoint(true);
|
||||
else
|
||||
SetAddress(addr & 0xFFFFFFF0);
|
||||
|
||||
Update();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryViewWidget::OnCopyAddress()
|
||||
{
|
||||
u32 addr = GetContextAddress();
|
||||
QApplication::clipboard()->setText(QStringLiteral("%1").arg(addr, 8, 16, QLatin1Char('0')));
|
||||
}
|
||||
|
||||
void MemoryViewWidget::OnCopyHex()
|
||||
{
|
||||
u32 addr = GetContextAddress();
|
||||
|
||||
const auto length = 16 / GetColumnCount(m_type);
|
||||
|
||||
u64 value = PowerPC::HostRead_U64(addr);
|
||||
|
||||
QApplication::clipboard()->setText(
|
||||
QStringLiteral("%1").arg(value, length * 2, 16, QLatin1Char('0')).left(length * 2));
|
||||
}
|
||||
|
||||
void MemoryViewWidget::OnContextMenu()
|
||||
{
|
||||
auto* menu = new QMenu(this);
|
||||
|
||||
AddAction(menu, tr("Copy Address"), this, &MemoryViewWidget::OnCopyAddress);
|
||||
|
||||
auto* copy_hex = AddAction(menu, tr("Copy Hex"), this, &MemoryViewWidget::OnCopyHex);
|
||||
|
||||
copy_hex->setEnabled(Core::GetState() != Core::State::Uninitialized &&
|
||||
PowerPC::HostIsRAMAddress(GetContextAddress()));
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
AddAction(menu, tr("Toggle Breakpoint"), this, &MemoryViewWidget::ToggleBreakpoint);
|
||||
|
||||
menu->exec(QCursor::pos());
|
||||
}
|
63
Source/Core/DolphinQt/Debugger/MemoryViewWidget.h
Normal file
63
Source/Core/DolphinQt/Debugger/MemoryViewWidget.h
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QTableWidget>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class MemoryViewWidget : public QTableWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class Type
|
||||
{
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
ASCII,
|
||||
Float32
|
||||
};
|
||||
|
||||
enum class BPType
|
||||
{
|
||||
ReadWrite,
|
||||
ReadOnly,
|
||||
WriteOnly
|
||||
};
|
||||
|
||||
explicit MemoryViewWidget(QWidget* parent = nullptr);
|
||||
|
||||
void Update();
|
||||
void ToggleBreakpoint();
|
||||
void ToggleRowBreakpoint(bool row);
|
||||
|
||||
void SetType(Type type);
|
||||
void SetBPType(BPType type);
|
||||
void SetAddress(u32 address);
|
||||
|
||||
void SetBPLoggingEnabled(bool enabled);
|
||||
|
||||
u32 GetContextAddress() const;
|
||||
|
||||
void resizeEvent(QResizeEvent*) override;
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void wheelEvent(QWheelEvent* event) override;
|
||||
|
||||
signals:
|
||||
void BreakpointsChanged();
|
||||
|
||||
private:
|
||||
void OnContextMenu();
|
||||
void OnCopyAddress();
|
||||
void OnCopyHex();
|
||||
|
||||
Type m_type = Type::U8;
|
||||
BPType m_bp_type = BPType::ReadWrite;
|
||||
bool m_do_log = true;
|
||||
u32 m_context_address;
|
||||
u32 m_address = 0;
|
||||
};
|
588
Source/Core/DolphinQt/Debugger/MemoryWidget.cpp
Normal file
588
Source/Core/DolphinQt/Debugger/MemoryWidget.cpp
Normal file
@ -0,0 +1,588 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Debugger/MemoryWidget.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QScrollArea>
|
||||
#include <QSpacerItem>
|
||||
#include <QSplitter>
|
||||
#include <QTableWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/File.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/HW/DSP.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "DolphinQt/Debugger/MemoryViewWidget.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
MemoryWidget::MemoryWidget(QWidget* parent) : QDockWidget(parent)
|
||||
{
|
||||
setWindowTitle(tr("Memory"));
|
||||
setObjectName(QStringLiteral("memory"));
|
||||
|
||||
setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
|
||||
CreateWidgets();
|
||||
|
||||
QSettings& settings = Settings::GetQSettings();
|
||||
|
||||
restoreGeometry(settings.value(QStringLiteral("memorywidget/geometry")).toByteArray());
|
||||
setFloating(settings.value(QStringLiteral("memorywidget/floating")).toBool());
|
||||
m_splitter->restoreState(settings.value(QStringLiteral("codewidget/splitter")).toByteArray());
|
||||
|
||||
connect(&Settings::Instance(), &Settings::MemoryVisibilityChanged,
|
||||
[this](bool visible) { setHidden(!visible); });
|
||||
|
||||
connect(&Settings::Instance(), &Settings::DebugModeToggled,
|
||||
[this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsMemoryVisible()); });
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, &MemoryWidget::Update);
|
||||
|
||||
setHidden(!Settings::Instance().IsCodeVisible() || !Settings::Instance().IsDebugModeEnabled());
|
||||
|
||||
LoadSettings();
|
||||
|
||||
ConnectWidgets();
|
||||
Update();
|
||||
OnTypeChanged();
|
||||
}
|
||||
|
||||
MemoryWidget::~MemoryWidget()
|
||||
{
|
||||
QSettings& settings = Settings::GetQSettings();
|
||||
|
||||
settings.setValue(QStringLiteral("memorywidget/geometry"), saveGeometry());
|
||||
settings.setValue(QStringLiteral("memorywidget/floating"), isFloating());
|
||||
settings.setValue(QStringLiteral("memorywidget/splitter"), m_splitter->saveState());
|
||||
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
void MemoryWidget::CreateWidgets()
|
||||
{
|
||||
auto* layout = new QHBoxLayout;
|
||||
|
||||
//// Sidebar
|
||||
|
||||
// Search
|
||||
m_search_address = new QLineEdit;
|
||||
m_data_edit = new QLineEdit;
|
||||
m_set_value = new QPushButton(tr("Set &Value"));
|
||||
|
||||
m_search_address->setPlaceholderText(tr("Search Address"));
|
||||
m_data_edit->setPlaceholderText(tr("Value"));
|
||||
|
||||
// Dump
|
||||
m_dump_mram = new QPushButton(tr("Dump &MRAM"));
|
||||
m_dump_exram = new QPushButton(tr("Dump &ExRAM"));
|
||||
m_dump_fake_vmem = new QPushButton(tr("Dump &FakeVMEM"));
|
||||
|
||||
// Search Options
|
||||
auto* search_group = new QGroupBox(tr("Search"));
|
||||
auto* search_layout = new QVBoxLayout;
|
||||
search_group->setLayout(search_layout);
|
||||
|
||||
m_find_next = new QPushButton(tr("Find &Next"));
|
||||
m_find_previous = new QPushButton(tr("Find &Previous"));
|
||||
m_find_ascii = new QRadioButton(tr("ASCII"));
|
||||
m_find_hex = new QRadioButton(tr("Hex"));
|
||||
m_result_label = new QLabel;
|
||||
|
||||
search_layout->addWidget(m_find_next);
|
||||
search_layout->addWidget(m_find_previous);
|
||||
search_layout->addWidget(m_result_label);
|
||||
|
||||
// Data Type
|
||||
auto* datatype_group = new QGroupBox(tr("Data Type"));
|
||||
auto* datatype_layout = new QVBoxLayout;
|
||||
datatype_group->setLayout(datatype_layout);
|
||||
|
||||
m_type_u8 = new QRadioButton(tr("U&8"));
|
||||
m_type_u16 = new QRadioButton(tr("U&16"));
|
||||
m_type_u32 = new QRadioButton(tr("U&32"));
|
||||
m_type_ascii = new QRadioButton(tr("ASCII"));
|
||||
m_type_float = new QRadioButton(tr("Float"));
|
||||
|
||||
datatype_layout->addWidget(m_type_u8);
|
||||
datatype_layout->addWidget(m_type_u16);
|
||||
datatype_layout->addWidget(m_type_u32);
|
||||
datatype_layout->addWidget(m_type_ascii);
|
||||
datatype_layout->addWidget(m_type_float);
|
||||
|
||||
// MBP options
|
||||
auto* bp_group = new QGroupBox(tr("Memory breakpoint options"));
|
||||
auto* bp_layout = new QVBoxLayout;
|
||||
bp_group->setLayout(bp_layout);
|
||||
|
||||
// i18n: This string is used for a radio button that represents the type of
|
||||
// memory breakpoint that gets triggered when a read operation or write operation occurs.
|
||||
// The string is not a command to read and write something or to allow reading and writing.
|
||||
m_bp_read_write = new QRadioButton(tr("Read and write"));
|
||||
// i18n: This string is used for a radio button that represents the type of
|
||||
// memory breakpoint that gets triggered when a read operation occurs.
|
||||
// The string does not mean "read-only" in the sense that something cannot be written to.
|
||||
m_bp_read_only = new QRadioButton(tr("Read only"));
|
||||
// i18n: This string is used for a radio button that represents the type of
|
||||
// memory breakpoint that gets triggered when a write operation occurs.
|
||||
// The string does not mean "write-only" in the sense that something cannot be read from.
|
||||
m_bp_write_only = new QRadioButton(tr("Write only"));
|
||||
m_bp_log_check = new QCheckBox(tr("Log"));
|
||||
|
||||
bp_layout->addWidget(m_bp_read_write);
|
||||
bp_layout->addWidget(m_bp_read_only);
|
||||
bp_layout->addWidget(m_bp_write_only);
|
||||
bp_layout->addWidget(m_bp_log_check);
|
||||
|
||||
// Sidebar
|
||||
auto* sidebar = new QWidget;
|
||||
auto* sidebar_layout = new QVBoxLayout;
|
||||
sidebar->setLayout(sidebar_layout);
|
||||
|
||||
sidebar_layout->addWidget(m_search_address);
|
||||
sidebar_layout->addWidget(m_data_edit);
|
||||
sidebar_layout->addWidget(m_find_ascii);
|
||||
sidebar_layout->addWidget(m_find_hex);
|
||||
sidebar_layout->addWidget(m_set_value);
|
||||
sidebar_layout->addItem(new QSpacerItem(1, 32));
|
||||
sidebar_layout->addWidget(m_dump_mram);
|
||||
sidebar_layout->addWidget(m_dump_exram);
|
||||
sidebar_layout->addWidget(m_dump_fake_vmem);
|
||||
sidebar_layout->addWidget(search_group);
|
||||
sidebar_layout->addWidget(datatype_group);
|
||||
sidebar_layout->addWidget(bp_group);
|
||||
sidebar_layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding));
|
||||
|
||||
// Splitter
|
||||
m_splitter = new QSplitter(Qt::Horizontal);
|
||||
|
||||
auto* sidebar_scroll = new QScrollArea;
|
||||
sidebar_scroll->setWidget(sidebar);
|
||||
sidebar_scroll->setWidgetResizable(true);
|
||||
sidebar_scroll->setFixedWidth(250);
|
||||
|
||||
m_memory_view = new MemoryViewWidget(this);
|
||||
|
||||
m_splitter->addWidget(m_memory_view);
|
||||
m_splitter->addWidget(sidebar_scroll);
|
||||
|
||||
layout->addWidget(m_splitter);
|
||||
|
||||
auto* widget = new QWidget;
|
||||
widget->setLayout(layout);
|
||||
setWidget(widget);
|
||||
}
|
||||
|
||||
void MemoryWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_search_address, &QLineEdit::textChanged, this, &MemoryWidget::OnSearchAddress);
|
||||
|
||||
connect(m_data_edit, &QLineEdit::textChanged, this, &MemoryWidget::ValidateSearchValue);
|
||||
connect(m_find_ascii, &QRadioButton::toggled, this, &MemoryWidget::ValidateSearchValue);
|
||||
connect(m_find_hex, &QRadioButton::toggled, this, &MemoryWidget::ValidateSearchValue);
|
||||
|
||||
connect(m_set_value, &QPushButton::pressed, this, &MemoryWidget::OnSetValue);
|
||||
|
||||
connect(m_dump_mram, &QPushButton::pressed, this, &MemoryWidget::OnDumpMRAM);
|
||||
connect(m_dump_exram, &QPushButton::pressed, this, &MemoryWidget::OnDumpExRAM);
|
||||
connect(m_dump_fake_vmem, &QPushButton::pressed, this, &MemoryWidget::OnDumpFakeVMEM);
|
||||
|
||||
connect(m_find_next, &QPushButton::pressed, this, &MemoryWidget::OnFindNextValue);
|
||||
connect(m_find_previous, &QPushButton::pressed, this, &MemoryWidget::OnFindPreviousValue);
|
||||
|
||||
for (auto* radio : {m_type_u8, m_type_u16, m_type_u32, m_type_ascii, m_type_float})
|
||||
connect(radio, &QRadioButton::toggled, this, &MemoryWidget::OnTypeChanged);
|
||||
|
||||
for (auto* radio : {m_bp_read_write, m_bp_read_only, m_bp_write_only})
|
||||
connect(radio, &QRadioButton::toggled, this, &MemoryWidget::OnBPTypeChanged);
|
||||
|
||||
connect(m_bp_log_check, &QCheckBox::toggled, this, &MemoryWidget::OnBPLogChanged);
|
||||
connect(m_memory_view, &MemoryViewWidget::BreakpointsChanged, this,
|
||||
&MemoryWidget::BreakpointsChanged);
|
||||
}
|
||||
|
||||
void MemoryWidget::closeEvent(QCloseEvent*)
|
||||
{
|
||||
Settings::Instance().SetMemoryVisible(false);
|
||||
}
|
||||
|
||||
void MemoryWidget::Update()
|
||||
{
|
||||
m_memory_view->Update();
|
||||
update();
|
||||
}
|
||||
|
||||
void MemoryWidget::LoadSettings()
|
||||
{
|
||||
QSettings& settings = Settings::GetQSettings();
|
||||
|
||||
const bool search_ascii =
|
||||
settings.value(QStringLiteral("memorywidget/searchascii"), true).toBool();
|
||||
const bool search_hex = settings.value(QStringLiteral("memorywidget/searchhex"), false).toBool();
|
||||
|
||||
m_find_ascii->setChecked(search_ascii);
|
||||
m_find_hex->setChecked(search_hex);
|
||||
|
||||
const bool type_u8 = settings.value(QStringLiteral("memorywidget/typeu8"), true).toBool();
|
||||
const bool type_u16 = settings.value(QStringLiteral("memorywidget/typeu16"), false).toBool();
|
||||
const bool type_u32 = settings.value(QStringLiteral("memorywidget/typeu32"), false).toBool();
|
||||
const bool type_float = settings.value(QStringLiteral("memorywidget/typefloat"), false).toBool();
|
||||
const bool type_ascii = settings.value(QStringLiteral("memorywidget/typeascii"), false).toBool();
|
||||
|
||||
m_type_u8->setChecked(type_u8);
|
||||
m_type_u16->setChecked(type_u16);
|
||||
m_type_u32->setChecked(type_u32);
|
||||
m_type_float->setChecked(type_float);
|
||||
m_type_ascii->setChecked(type_ascii);
|
||||
|
||||
bool bp_rw = settings.value(QStringLiteral("memorywidget/bpreadwrite"), true).toBool();
|
||||
bool bp_r = settings.value(QStringLiteral("memorywidget/bpread"), false).toBool();
|
||||
bool bp_w = settings.value(QStringLiteral("memorywidget/bpwrite"), false).toBool();
|
||||
bool bp_log = settings.value(QStringLiteral("memorywidget/bplog"), true).toBool();
|
||||
|
||||
m_bp_read_write->setChecked(bp_rw);
|
||||
m_bp_read_only->setChecked(bp_r);
|
||||
m_bp_write_only->setChecked(bp_w);
|
||||
m_bp_log_check->setChecked(bp_log);
|
||||
}
|
||||
|
||||
void MemoryWidget::SaveSettings()
|
||||
{
|
||||
QSettings& settings = Settings::GetQSettings();
|
||||
|
||||
settings.setValue(QStringLiteral("memorywidget/searchascii"), m_find_ascii->isChecked());
|
||||
settings.setValue(QStringLiteral("memorywidget/searchhex"), m_find_hex->isChecked());
|
||||
|
||||
settings.setValue(QStringLiteral("memorywidget/typeu8"), m_type_u8->isChecked());
|
||||
settings.setValue(QStringLiteral("memorywidget/typeu16"), m_type_u16->isChecked());
|
||||
settings.setValue(QStringLiteral("memorywidget/typeu32"), m_type_u32->isChecked());
|
||||
settings.setValue(QStringLiteral("memorywidget/typeascii"), m_type_ascii->isChecked());
|
||||
settings.setValue(QStringLiteral("memorywidget/typefloat"), m_type_float->isChecked());
|
||||
|
||||
settings.setValue(QStringLiteral("memorywidget/bpreadwrite"), m_bp_read_write->isChecked());
|
||||
settings.setValue(QStringLiteral("memorywidget/bpread"), m_bp_read_only->isChecked());
|
||||
settings.setValue(QStringLiteral("memorywidget/bpwrite"), m_bp_write_only->isChecked());
|
||||
settings.setValue(QStringLiteral("memorywidget/bplog"), m_bp_log_check->isChecked());
|
||||
}
|
||||
|
||||
void MemoryWidget::OnTypeChanged()
|
||||
{
|
||||
MemoryViewWidget::Type type;
|
||||
|
||||
if (m_type_u8->isChecked())
|
||||
type = MemoryViewWidget::Type::U8;
|
||||
else if (m_type_u16->isChecked())
|
||||
type = MemoryViewWidget::Type::U16;
|
||||
else if (m_type_u32->isChecked())
|
||||
type = MemoryViewWidget::Type::U32;
|
||||
else if (m_type_ascii->isChecked())
|
||||
type = MemoryViewWidget::Type::ASCII;
|
||||
else
|
||||
type = MemoryViewWidget::Type::Float32;
|
||||
|
||||
m_memory_view->SetType(type);
|
||||
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
void MemoryWidget::OnBPLogChanged()
|
||||
{
|
||||
m_memory_view->SetBPLoggingEnabled(m_bp_log_check->isChecked());
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
void MemoryWidget::OnBPTypeChanged()
|
||||
{
|
||||
bool read_write = m_bp_read_write->isChecked();
|
||||
bool read_only = m_bp_read_only->isChecked();
|
||||
|
||||
MemoryViewWidget::BPType type;
|
||||
|
||||
if (read_write)
|
||||
type = MemoryViewWidget::BPType::ReadWrite;
|
||||
else if (read_only)
|
||||
type = MemoryViewWidget::BPType::ReadOnly;
|
||||
else
|
||||
type = MemoryViewWidget::BPType::WriteOnly;
|
||||
|
||||
m_memory_view->SetBPType(type);
|
||||
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
void MemoryWidget::OnSearchAddress()
|
||||
{
|
||||
bool good;
|
||||
u32 addr = m_search_address->text().toUInt(&good, 16);
|
||||
|
||||
QFont font;
|
||||
QPalette palette;
|
||||
|
||||
if (good || m_search_address->text().isEmpty())
|
||||
{
|
||||
if (good)
|
||||
m_memory_view->SetAddress(addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
font.setBold(true);
|
||||
palette.setColor(QPalette::Text, Qt::red);
|
||||
}
|
||||
|
||||
m_search_address->setFont(font);
|
||||
m_search_address->setPalette(palette);
|
||||
}
|
||||
|
||||
void MemoryWidget::ValidateSearchValue()
|
||||
{
|
||||
QFont font;
|
||||
QPalette palette;
|
||||
|
||||
if (m_find_hex->isChecked() && !m_data_edit->text().isEmpty())
|
||||
{
|
||||
bool good;
|
||||
m_data_edit->text().toULongLong(&good, 16);
|
||||
|
||||
if (!good)
|
||||
{
|
||||
font.setBold(true);
|
||||
palette.setColor(QPalette::Text, Qt::red);
|
||||
}
|
||||
}
|
||||
|
||||
m_data_edit->setFont(font);
|
||||
m_data_edit->setPalette(palette);
|
||||
}
|
||||
|
||||
void MemoryWidget::OnSetValue()
|
||||
{
|
||||
bool good_address;
|
||||
u32 addr = m_search_address->text().toUInt(&good_address, 16);
|
||||
|
||||
if (!good_address)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Bad address provided."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_data_edit->text().isEmpty())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("No value provided."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_find_ascii->isChecked())
|
||||
{
|
||||
const QByteArray bytes = m_data_edit->text().toUtf8();
|
||||
|
||||
for (char c : bytes)
|
||||
PowerPC::HostWrite_U8(static_cast<u8>(c), addr++);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool good_value;
|
||||
u64 value = m_data_edit->text().toULongLong(&good_value, 16);
|
||||
|
||||
if (!good_value)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Bad value provided."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (value == static_cast<u8>(value))
|
||||
{
|
||||
PowerPC::HostWrite_U8(static_cast<u8>(value), addr);
|
||||
}
|
||||
else if (value == static_cast<u16>(value))
|
||||
{
|
||||
PowerPC::HostWrite_U16(static_cast<u16>(value), addr);
|
||||
}
|
||||
else if (value == static_cast<u32>(value))
|
||||
{
|
||||
PowerPC::HostWrite_U32(static_cast<u32>(value), addr);
|
||||
}
|
||||
else
|
||||
PowerPC::HostWrite_U64(value, addr);
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
static void DumpArray(const std::string& filename, const u8* data, size_t length)
|
||||
{
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
File::IOFile f(filename, "wb");
|
||||
|
||||
if (!f)
|
||||
{
|
||||
QMessageBox::critical(
|
||||
nullptr, QObject::tr("Error"),
|
||||
QObject::tr("Failed to dump %1: Can't open file").arg(QString::fromStdString(filename)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!f.WriteBytes(data, length))
|
||||
{
|
||||
QMessageBox::critical(nullptr, QObject::tr("Error"),
|
||||
QObject::tr("Failed to dump %1: Failed to write to file")
|
||||
.arg(QString::fromStdString(filename)));
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryWidget::OnDumpMRAM()
|
||||
{
|
||||
DumpArray(File::GetUserPath(F_RAMDUMP_IDX), Memory::m_pRAM, Memory::REALRAM_SIZE);
|
||||
}
|
||||
|
||||
void MemoryWidget::OnDumpExRAM()
|
||||
{
|
||||
if (SConfig::GetInstance().bWii)
|
||||
{
|
||||
DumpArray(File::GetUserPath(F_ARAMDUMP_IDX), Memory::m_pEXRAM, Memory::EXRAM_SIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
DumpArray(File::GetUserPath(F_ARAMDUMP_IDX), DSP::GetARAMPtr(), DSP::ARAM_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryWidget::OnDumpFakeVMEM()
|
||||
{
|
||||
DumpArray(File::GetUserPath(F_FAKEVMEMDUMP_IDX), Memory::m_pFakeVMEM, Memory::FAKEVMEM_SIZE);
|
||||
}
|
||||
|
||||
std::vector<u8> MemoryWidget::GetValueData() const
|
||||
{
|
||||
std::vector<u8> search_for; // Series of bytes we want to look for
|
||||
|
||||
if (m_find_ascii->isChecked())
|
||||
{
|
||||
const QByteArray bytes = m_data_edit->text().toUtf8();
|
||||
search_for.assign(bytes.begin(), bytes.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
bool good;
|
||||
u64 value = m_data_edit->text().toULongLong(&good, 16);
|
||||
|
||||
if (!good)
|
||||
return {};
|
||||
|
||||
int size;
|
||||
|
||||
if (value == static_cast<u8>(value))
|
||||
size = sizeof(u8);
|
||||
else if (value == static_cast<u16>(value))
|
||||
size = sizeof(u16);
|
||||
else if (value == static_cast<u32>(value))
|
||||
size = sizeof(u32);
|
||||
else
|
||||
size = sizeof(u64);
|
||||
|
||||
for (int i = size - 1; i >= 0; i--)
|
||||
search_for.push_back((value >> (i * 8)) & 0xFF);
|
||||
}
|
||||
|
||||
return search_for;
|
||||
}
|
||||
|
||||
void MemoryWidget::FindValue(bool next)
|
||||
{
|
||||
std::vector<u8> search_for = GetValueData();
|
||||
|
||||
if (search_for.empty())
|
||||
{
|
||||
m_result_label->setText(tr("No Value Given"));
|
||||
return;
|
||||
}
|
||||
|
||||
const u8* ram_ptr = nullptr;
|
||||
std::size_t ram_size = 0;
|
||||
u32 base_address = 0;
|
||||
|
||||
if (m_type_u16->isChecked())
|
||||
{
|
||||
ram_ptr = DSP::GetARAMPtr();
|
||||
ram_size = DSP::ARAM_SIZE;
|
||||
base_address = 0x0c005000;
|
||||
}
|
||||
else if (Memory::m_pRAM)
|
||||
{
|
||||
ram_ptr = Memory::m_pRAM;
|
||||
ram_size = Memory::REALRAM_SIZE;
|
||||
base_address = 0x80000000;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_result_label->setText(tr("Memory Not Ready"));
|
||||
return;
|
||||
}
|
||||
|
||||
u32 addr = 0;
|
||||
|
||||
if (!m_search_address->text().isEmpty())
|
||||
addr = m_search_address->text().toUInt(nullptr, 16) + 1;
|
||||
|
||||
if (addr >= base_address)
|
||||
addr -= base_address;
|
||||
|
||||
if (addr >= ram_size - search_for.size())
|
||||
{
|
||||
m_result_label->setText(tr("Address Out of Range"));
|
||||
return;
|
||||
}
|
||||
|
||||
const u8* ptr;
|
||||
const u8* end;
|
||||
|
||||
if (next)
|
||||
{
|
||||
end = &ram_ptr[ram_size - search_for.size() + 1];
|
||||
ptr = std::search(&ram_ptr[addr], end, search_for.begin(), search_for.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
end = &ram_ptr[addr + search_for.size() - 1];
|
||||
ptr = std::find_end(ram_ptr, end, search_for.begin(), search_for.end());
|
||||
}
|
||||
|
||||
if (ptr != end)
|
||||
{
|
||||
m_result_label->setText(tr("Match Found"));
|
||||
|
||||
u32 offset = static_cast<u32>(ptr - ram_ptr) + base_address;
|
||||
|
||||
m_search_address->setText(QStringLiteral("%1").arg(offset, 8, 16, QLatin1Char('0')));
|
||||
|
||||
m_memory_view->SetAddress(offset);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_result_label->setText(tr("No Match"));
|
||||
}
|
||||
|
||||
void MemoryWidget::OnFindNextValue()
|
||||
{
|
||||
FindValue(true);
|
||||
}
|
||||
|
||||
void MemoryWidget::OnFindPreviousValue()
|
||||
{
|
||||
FindValue(false);
|
||||
}
|
88
Source/Core/DolphinQt/Debugger/MemoryWidget.h
Normal file
88
Source/Core/DolphinQt/Debugger/MemoryWidget.h
Normal file
@ -0,0 +1,88 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QDockWidget>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class MemoryViewWidget;
|
||||
class QCheckBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QRadioButton;
|
||||
class QSplitter;
|
||||
|
||||
class MemoryWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MemoryWidget(QWidget* parent = nullptr);
|
||||
~MemoryWidget();
|
||||
|
||||
void Update();
|
||||
signals:
|
||||
void BreakpointsChanged();
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
|
||||
void OnTypeChanged();
|
||||
void OnBPLogChanged();
|
||||
void OnBPTypeChanged();
|
||||
|
||||
void OnSearchAddress();
|
||||
void OnFindNextValue();
|
||||
void OnFindPreviousValue();
|
||||
void ValidateSearchValue();
|
||||
|
||||
void OnSetValue();
|
||||
|
||||
void OnDumpMRAM();
|
||||
void OnDumpExRAM();
|
||||
void OnDumpFakeVMEM();
|
||||
|
||||
std::vector<u8> GetValueData() const;
|
||||
|
||||
void FindValue(bool next);
|
||||
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
|
||||
MemoryViewWidget* m_memory_view;
|
||||
QSplitter* m_splitter;
|
||||
QLineEdit* m_search_address;
|
||||
QLineEdit* m_data_edit;
|
||||
QPushButton* m_set_value;
|
||||
QPushButton* m_dump_mram;
|
||||
QPushButton* m_dump_exram;
|
||||
QPushButton* m_dump_fake_vmem;
|
||||
|
||||
// Search
|
||||
QPushButton* m_find_next;
|
||||
QPushButton* m_find_previous;
|
||||
QRadioButton* m_find_ascii;
|
||||
QRadioButton* m_find_hex;
|
||||
QLabel* m_result_label;
|
||||
|
||||
// Datatypes
|
||||
QRadioButton* m_type_u8;
|
||||
QRadioButton* m_type_u16;
|
||||
QRadioButton* m_type_u32;
|
||||
QRadioButton* m_type_ascii;
|
||||
QRadioButton* m_type_float;
|
||||
|
||||
// Breakpoint options
|
||||
QRadioButton* m_bp_read_write;
|
||||
QRadioButton* m_bp_read_only;
|
||||
QRadioButton* m_bp_write_only;
|
||||
QCheckBox* m_bp_log_check;
|
||||
};
|
197
Source/Core/DolphinQt/Debugger/NewBreakpointDialog.cpp
Normal file
197
Source/Core/DolphinQt/Debugger/NewBreakpointDialog.cpp
Normal file
@ -0,0 +1,197 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Debugger/NewBreakpointDialog.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QRadioButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "DolphinQt/Debugger/BreakpointWidget.h"
|
||||
|
||||
NewBreakpointDialog::NewBreakpointDialog(BreakpointWidget* parent)
|
||||
: QDialog(parent), m_parent(parent)
|
||||
{
|
||||
setWindowTitle(tr("New Breakpoint"));
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
||||
OnBPTypeChanged();
|
||||
OnAddressTypeChanged();
|
||||
}
|
||||
|
||||
void NewBreakpointDialog::CreateWidgets()
|
||||
{
|
||||
m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
|
||||
// Instruction BP
|
||||
m_instruction_bp = new QRadioButton(tr("Instruction Breakpoint"));
|
||||
m_instruction_bp->setChecked(true);
|
||||
m_instruction_box = new QGroupBox;
|
||||
m_instruction_address = new QLineEdit;
|
||||
|
||||
auto* instruction_layout = new QHBoxLayout;
|
||||
m_instruction_box->setLayout(instruction_layout);
|
||||
instruction_layout->addWidget(new QLabel(tr("Address:")));
|
||||
instruction_layout->addWidget(m_instruction_address);
|
||||
|
||||
// Memory BP
|
||||
m_memory_bp = new QRadioButton(tr("Memory Breakpoint"));
|
||||
m_memory_box = new QGroupBox;
|
||||
m_memory_use_address = new QRadioButton(tr("Address"));
|
||||
m_memory_use_address->setChecked(true);
|
||||
// i18n: A range of memory addresses
|
||||
m_memory_use_range = new QRadioButton(tr("Range"));
|
||||
m_memory_address_from = new QLineEdit;
|
||||
m_memory_address_to = new QLineEdit;
|
||||
m_memory_address_from_label = new QLabel; // Set by OnAddressTypeChanged
|
||||
m_memory_address_to_label = new QLabel(tr("To:"));
|
||||
// i18n: This is a selectable condition when adding a breakpoint
|
||||
m_memory_on_read = new QRadioButton(tr("Read"));
|
||||
// i18n: This is a selectable condition when adding a breakpoint
|
||||
m_memory_on_write = new QRadioButton(tr("Write"));
|
||||
// i18n: This is a selectable condition when adding a breakpoint
|
||||
m_memory_on_read_and_write = new QRadioButton(tr("Read or Write"));
|
||||
m_memory_on_write->setChecked(true);
|
||||
// i18n: This is a selectable action when adding a breakpoint
|
||||
m_memory_do_log = new QRadioButton(tr("Write to Log"));
|
||||
// i18n: This is a selectable action when adding a breakpoint
|
||||
m_memory_do_break = new QRadioButton(tr("Break"));
|
||||
// i18n: This is a selectable action when adding a breakpoint
|
||||
m_memory_do_log_and_break = new QRadioButton(tr("Write to Log and Break"));
|
||||
m_memory_do_log_and_break->setChecked(true);
|
||||
|
||||
auto* memory_layout = new QGridLayout;
|
||||
m_memory_box->setLayout(memory_layout);
|
||||
memory_layout->addWidget(m_memory_use_address, 0, 0);
|
||||
memory_layout->addWidget(m_memory_use_range, 0, 3);
|
||||
memory_layout->addWidget(m_memory_address_from_label, 1, 0);
|
||||
memory_layout->addWidget(m_memory_address_from, 1, 1);
|
||||
memory_layout->addWidget(m_memory_address_to_label, 1, 2);
|
||||
memory_layout->addWidget(m_memory_address_to, 1, 3);
|
||||
QGroupBox* condition_box = new QGroupBox(tr("Condition"));
|
||||
auto* condition_layout = new QHBoxLayout;
|
||||
condition_box->setLayout(condition_layout);
|
||||
|
||||
memory_layout->addWidget(condition_box, 2, 0, 1, -1);
|
||||
condition_layout->addWidget(m_memory_on_read);
|
||||
condition_layout->addWidget(m_memory_on_write);
|
||||
condition_layout->addWidget(m_memory_on_read_and_write);
|
||||
|
||||
QGroupBox* action_box = new QGroupBox(tr("Action"));
|
||||
auto* action_layout = new QHBoxLayout;
|
||||
action_box->setLayout(action_layout);
|
||||
memory_layout->addWidget(action_box, 3, 0, 1, -1);
|
||||
action_layout->addWidget(m_memory_do_log);
|
||||
action_layout->addWidget(m_memory_do_break);
|
||||
action_layout->addWidget(m_memory_do_log_and_break);
|
||||
|
||||
auto* layout = new QVBoxLayout;
|
||||
|
||||
layout->addWidget(m_instruction_bp);
|
||||
layout->addWidget(m_instruction_box);
|
||||
layout->addWidget(m_memory_bp);
|
||||
layout->addWidget(m_memory_box);
|
||||
layout->addWidget(m_buttons);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void NewBreakpointDialog::ConnectWidgets()
|
||||
{
|
||||
connect(m_buttons, &QDialogButtonBox::accepted, this, &NewBreakpointDialog::accept);
|
||||
connect(m_buttons, &QDialogButtonBox::rejected, this, &NewBreakpointDialog::reject);
|
||||
|
||||
connect(m_instruction_bp, &QRadioButton::toggled, this, &NewBreakpointDialog::OnBPTypeChanged);
|
||||
connect(m_memory_bp, &QRadioButton::toggled, this, &NewBreakpointDialog::OnBPTypeChanged);
|
||||
|
||||
connect(m_memory_use_address, &QRadioButton::toggled, this,
|
||||
&NewBreakpointDialog::OnAddressTypeChanged);
|
||||
connect(m_memory_use_range, &QRadioButton::toggled, this,
|
||||
&NewBreakpointDialog::OnAddressTypeChanged);
|
||||
}
|
||||
|
||||
void NewBreakpointDialog::OnBPTypeChanged()
|
||||
{
|
||||
m_instruction_box->setEnabled(m_instruction_bp->isChecked());
|
||||
m_memory_box->setEnabled(m_memory_bp->isChecked());
|
||||
}
|
||||
|
||||
void NewBreakpointDialog::OnAddressTypeChanged()
|
||||
{
|
||||
bool ranged = m_memory_use_range->isChecked();
|
||||
|
||||
m_memory_address_to->setHidden(!ranged);
|
||||
m_memory_address_to_label->setHidden(!ranged);
|
||||
|
||||
m_memory_address_from_label->setText(ranged ? tr("From:") : tr("Address:"));
|
||||
}
|
||||
|
||||
void NewBreakpointDialog::accept()
|
||||
{
|
||||
auto invalid_input = [this](QString field) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Invalid input for the field \"%1\"").arg(field));
|
||||
};
|
||||
|
||||
bool instruction = m_instruction_bp->isChecked();
|
||||
bool ranged = m_memory_use_range->isChecked();
|
||||
|
||||
// Triggers
|
||||
bool on_read = m_memory_on_read->isChecked() || m_memory_on_read_and_write->isChecked();
|
||||
bool on_write = m_memory_on_write->isChecked() || m_memory_on_read_and_write->isChecked();
|
||||
|
||||
// Actions
|
||||
bool do_log = m_memory_do_log->isChecked() || m_memory_do_log_and_break->isChecked();
|
||||
bool do_break = m_memory_do_break->isChecked() || m_memory_do_log_and_break->isChecked();
|
||||
|
||||
bool good;
|
||||
|
||||
if (instruction)
|
||||
{
|
||||
u32 address = m_instruction_address->text().toUInt(&good, 16);
|
||||
|
||||
if (!good)
|
||||
{
|
||||
invalid_input(tr("Address"));
|
||||
return;
|
||||
}
|
||||
|
||||
m_parent->AddBP(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 from = m_memory_address_from->text().toUInt(&good, 16);
|
||||
|
||||
if (!good)
|
||||
{
|
||||
invalid_input(ranged ? tr("From") : tr("Address"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ranged)
|
||||
{
|
||||
u32 to = m_memory_address_to->text().toUInt(&good, 16);
|
||||
if (!good)
|
||||
{
|
||||
invalid_input(tr("To"));
|
||||
return;
|
||||
}
|
||||
|
||||
m_parent->AddRangedMBP(from, to, on_read, on_write, do_log, do_break);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_parent->AddAddressMBP(from, on_read, on_write, do_log, do_break);
|
||||
}
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
}
|
57
Source/Core/DolphinQt/Debugger/NewBreakpointDialog.h
Normal file
57
Source/Core/DolphinQt/Debugger/NewBreakpointDialog.h
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class BreakpointWidget;
|
||||
class QCheckBox;
|
||||
class QDialogButtonBox;
|
||||
class QGroupBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QRadioButton;
|
||||
|
||||
class NewBreakpointDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit NewBreakpointDialog(BreakpointWidget* parent);
|
||||
|
||||
void accept() override;
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
|
||||
void OnBPTypeChanged();
|
||||
void OnAddressTypeChanged();
|
||||
|
||||
// Instruction BPs
|
||||
QRadioButton* m_instruction_bp;
|
||||
QGroupBox* m_instruction_box;
|
||||
QLineEdit* m_instruction_address;
|
||||
|
||||
// Memory BPs
|
||||
QRadioButton* m_memory_bp;
|
||||
QRadioButton* m_memory_use_address;
|
||||
QRadioButton* m_memory_use_range;
|
||||
QGroupBox* m_memory_box;
|
||||
QLabel* m_memory_address_from_label;
|
||||
QLineEdit* m_memory_address_from;
|
||||
QLabel* m_memory_address_to_label;
|
||||
QLineEdit* m_memory_address_to;
|
||||
QRadioButton* m_memory_on_read;
|
||||
QRadioButton* m_memory_on_read_and_write;
|
||||
QRadioButton* m_memory_on_write;
|
||||
QRadioButton* m_memory_do_log;
|
||||
QRadioButton* m_memory_do_break;
|
||||
QRadioButton* m_memory_do_log_and_break;
|
||||
|
||||
QDialogButtonBox* m_buttons;
|
||||
BreakpointWidget* m_parent;
|
||||
};
|
133
Source/Core/DolphinQt/Debugger/RegisterColumn.cpp
Normal file
133
Source/Core/DolphinQt/Debugger/RegisterColumn.cpp
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Debugger/RegisterColumn.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
RegisterColumn::RegisterColumn(RegisterType type, std::function<u64()> get,
|
||||
std::function<void(u64)> set)
|
||||
: m_type(type), m_get_register(std::move(get)), m_set_register(std::move(set))
|
||||
{
|
||||
RefreshValue();
|
||||
Update();
|
||||
|
||||
setFlags(m_set_register == nullptr ? flags() ^ Qt::ItemIsEditable :
|
||||
Qt::ItemIsEditable | Qt::ItemIsEnabled);
|
||||
setData(DATA_TYPE, static_cast<quint32>(type));
|
||||
}
|
||||
|
||||
RegisterDisplay RegisterColumn::GetDisplay() const
|
||||
{
|
||||
return m_display;
|
||||
}
|
||||
|
||||
void RegisterColumn::SetDisplay(RegisterDisplay display)
|
||||
{
|
||||
m_display = display;
|
||||
Update();
|
||||
}
|
||||
|
||||
void RegisterColumn::RefreshValue()
|
||||
{
|
||||
QBrush brush = QPalette().brush(QPalette::Text);
|
||||
|
||||
if (m_value != m_get_register())
|
||||
{
|
||||
m_value = m_get_register();
|
||||
brush.setColor(Qt::red);
|
||||
}
|
||||
|
||||
setForeground(brush);
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
u64 RegisterColumn::GetValue() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void RegisterColumn::SetValue()
|
||||
{
|
||||
u64 value = 0;
|
||||
|
||||
bool valid = false;
|
||||
|
||||
switch (m_display)
|
||||
{
|
||||
case RegisterDisplay::Hex:
|
||||
value = text().toULongLong(&valid, 16);
|
||||
break;
|
||||
case RegisterDisplay::SInt32:
|
||||
value = text().toInt(&valid);
|
||||
break;
|
||||
case RegisterDisplay::UInt32:
|
||||
value = text().toUInt(&valid);
|
||||
break;
|
||||
case RegisterDisplay::Float:
|
||||
{
|
||||
float f = text().toFloat(&valid);
|
||||
std::memcpy(&value, &f, sizeof(u32));
|
||||
break;
|
||||
}
|
||||
case RegisterDisplay::Double:
|
||||
{
|
||||
double f = text().toDouble(&valid);
|
||||
std::memcpy(&value, &f, sizeof(u64));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
QMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("Invalid input provided"));
|
||||
else
|
||||
m_set_register(value);
|
||||
|
||||
RefreshValue();
|
||||
}
|
||||
|
||||
void RegisterColumn::Update()
|
||||
{
|
||||
QString text;
|
||||
|
||||
switch (m_display)
|
||||
{
|
||||
case RegisterDisplay::Hex:
|
||||
text = QStringLiteral("%1").arg(m_value,
|
||||
(m_type == RegisterType::ibat || m_type == RegisterType::dbat ||
|
||||
m_type == RegisterType::fpr ||
|
||||
m_type == RegisterType::tb ?
|
||||
sizeof(u64) :
|
||||
sizeof(u32)) *
|
||||
2,
|
||||
16, QLatin1Char('0'));
|
||||
break;
|
||||
case RegisterDisplay::SInt32:
|
||||
text = QString::number(static_cast<qint32>(m_value));
|
||||
break;
|
||||
case RegisterDisplay::UInt32:
|
||||
text = QString::number(static_cast<quint32>(m_value));
|
||||
break;
|
||||
case RegisterDisplay::Float:
|
||||
{
|
||||
float tmp;
|
||||
std::memcpy(&tmp, &m_value, sizeof(float));
|
||||
text = QString::number(tmp);
|
||||
break;
|
||||
}
|
||||
case RegisterDisplay::Double:
|
||||
{
|
||||
double tmp;
|
||||
std::memcpy(&tmp, &m_value, sizeof(double));
|
||||
text = QString::number(tmp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setText(text);
|
||||
}
|
72
Source/Core/DolphinQt/Debugger/RegisterColumn.h
Normal file
72
Source/Core/DolphinQt/Debugger/RegisterColumn.h
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QTableWidgetItem>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
enum class RegisterType
|
||||
{
|
||||
gpr, // General purpose registers, int (r0-r31)
|
||||
fpr, // Floating point registers, double (f0-f31)
|
||||
ibat, // Instruction BATs (IBAT0-IBAT7)
|
||||
dbat, // Data BATs (DBAT0-DBAT7)
|
||||
tb, // Time base register
|
||||
pc, // Program counter
|
||||
lr, // Link register
|
||||
ctr, // Decremented and incremented by branch and count instructions
|
||||
cr, // Condition register
|
||||
xer, // Integer exception register
|
||||
fpscr, // Floating point status and control register
|
||||
msr, // Machine state register
|
||||
srr, // Machine status save/restore register (SRR0 - SRR1)
|
||||
sr, // Segment register (SR0 - SR15)
|
||||
gqr, // Graphics quantization registers (GQR0 - GQR7)
|
||||
exceptions, // Keeps track of currently triggered exceptions
|
||||
int_mask, // ???
|
||||
int_cause, // ???
|
||||
dsisr, // Defines the cause of data / alignment exceptions
|
||||
dar, // Data adress register
|
||||
pt_hashmask // ???
|
||||
};
|
||||
|
||||
enum class RegisterDisplay
|
||||
{
|
||||
Hex,
|
||||
SInt32,
|
||||
UInt32,
|
||||
Float,
|
||||
Double
|
||||
};
|
||||
|
||||
constexpr int DATA_TYPE = Qt::UserRole;
|
||||
|
||||
class RegisterColumn : public QTableWidgetItem
|
||||
{
|
||||
public:
|
||||
explicit RegisterColumn(RegisterType type, std::function<u64()> get,
|
||||
std::function<void(u64)> set);
|
||||
|
||||
void RefreshValue();
|
||||
|
||||
RegisterDisplay GetDisplay() const;
|
||||
void SetDisplay(RegisterDisplay display);
|
||||
u64 GetValue() const;
|
||||
void SetValue();
|
||||
|
||||
private:
|
||||
void Update();
|
||||
|
||||
RegisterType m_type;
|
||||
|
||||
std::function<u64()> m_get_register;
|
||||
std::function<void(u64)> m_set_register;
|
||||
|
||||
u64 m_value;
|
||||
RegisterDisplay m_display = RegisterDisplay::Hex;
|
||||
};
|
362
Source/Core/DolphinQt/Debugger/RegisterWidget.cpp
Normal file
362
Source/Core/DolphinQt/Debugger/RegisterWidget.cpp
Normal file
@ -0,0 +1,362 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Debugger/RegisterWidget.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QMenu>
|
||||
#include <QTableWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/Core.h"
|
||||
#include "Core/HW/ProcessorInterface.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "DolphinQt/Host.h"
|
||||
#include "DolphinQt/QtUtils/ActionHelper.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
RegisterWidget::RegisterWidget(QWidget* parent) : QDockWidget(parent)
|
||||
{
|
||||
setWindowTitle(tr("Registers"));
|
||||
setObjectName(QStringLiteral("registers"));
|
||||
setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
restoreGeometry(settings.value(QStringLiteral("registerwidget/geometry")).toByteArray());
|
||||
setFloating(settings.value(QStringLiteral("registerwidget/floating")).toBool());
|
||||
|
||||
CreateWidgets();
|
||||
PopulateTable();
|
||||
ConnectWidgets();
|
||||
|
||||
connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, [this] {
|
||||
if (Settings::Instance().IsDebugModeEnabled() && Core::GetState() == Core::State::Paused)
|
||||
{
|
||||
m_updating = true;
|
||||
emit UpdateTable();
|
||||
m_updating = false;
|
||||
}
|
||||
});
|
||||
|
||||
connect(&Settings::Instance(), &Settings::RegistersVisibilityChanged,
|
||||
[this](bool visible) { setHidden(!visible); });
|
||||
|
||||
connect(&Settings::Instance(), &Settings::DebugModeToggled, [this](bool enabled) {
|
||||
setHidden(!enabled || !Settings::Instance().IsRegistersVisible());
|
||||
});
|
||||
|
||||
setHidden(!Settings::Instance().IsRegistersVisible() ||
|
||||
!Settings::Instance().IsDebugModeEnabled());
|
||||
}
|
||||
|
||||
RegisterWidget::~RegisterWidget()
|
||||
{
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
settings.setValue(QStringLiteral("registerwidget/geometry"), saveGeometry());
|
||||
settings.setValue(QStringLiteral("registerwidget/floating"), isFloating());
|
||||
}
|
||||
|
||||
void RegisterWidget::closeEvent(QCloseEvent*)
|
||||
{
|
||||
Settings::Instance().SetRegistersVisible(false);
|
||||
}
|
||||
|
||||
void RegisterWidget::CreateWidgets()
|
||||
{
|
||||
m_table = new QTableWidget;
|
||||
|
||||
m_table->setColumnCount(9);
|
||||
|
||||
m_table->verticalHeader()->setVisible(false);
|
||||
m_table->verticalHeader()->setDefaultSectionSize(24);
|
||||
m_table->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_table->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
m_table->setFont(Settings::Instance().GetDebugFont());
|
||||
|
||||
QStringList empty_list;
|
||||
|
||||
for (auto i = 0; i < 9; i++)
|
||||
empty_list << QStringLiteral("");
|
||||
|
||||
m_table->setHorizontalHeaderLabels(empty_list);
|
||||
|
||||
QWidget* widget = new QWidget;
|
||||
auto* layout = new QVBoxLayout;
|
||||
layout->addWidget(m_table);
|
||||
widget->setLayout(layout);
|
||||
|
||||
setWidget(widget);
|
||||
}
|
||||
|
||||
void RegisterWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_table, &QTableWidget::customContextMenuRequested, this,
|
||||
&RegisterWidget::ShowContextMenu);
|
||||
connect(m_table, &QTableWidget::itemChanged, this, &RegisterWidget::OnItemChanged);
|
||||
}
|
||||
|
||||
void RegisterWidget::OnItemChanged(QTableWidgetItem* item)
|
||||
{
|
||||
if (!item->data(DATA_TYPE).isNull() && !m_updating)
|
||||
static_cast<RegisterColumn*>(item)->SetValue();
|
||||
}
|
||||
|
||||
void RegisterWidget::ShowContextMenu()
|
||||
{
|
||||
QMenu* menu = new QMenu(this);
|
||||
|
||||
auto variant = m_table->currentItem()->data(DATA_TYPE);
|
||||
|
||||
if (!variant.isNull())
|
||||
{
|
||||
auto* item = static_cast<RegisterColumn*>(m_table->currentItem());
|
||||
auto type = static_cast<RegisterType>(item->data(DATA_TYPE).toInt());
|
||||
auto display = item->GetDisplay();
|
||||
|
||||
AddAction(menu, tr("Add to &watch"), this,
|
||||
[this, item] { emit RequestMemoryBreakpoint(item->GetValue()); });
|
||||
menu->addAction(tr("View &memory"));
|
||||
menu->addAction(tr("View &code"));
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
QActionGroup* group = new QActionGroup(menu);
|
||||
group->setExclusive(true);
|
||||
|
||||
auto* view_hex = menu->addAction(tr("Hexadecimal"));
|
||||
auto* view_int = menu->addAction(tr("Signed Integer"));
|
||||
auto* view_uint = menu->addAction(tr("Unsigned Integer"));
|
||||
// i18n: A floating point number
|
||||
auto* view_float = menu->addAction(tr("Float"));
|
||||
// i18n: A double precision floating point number
|
||||
auto* view_double = menu->addAction(tr("Double"));
|
||||
|
||||
for (auto* action : {view_hex, view_int, view_uint, view_float, view_double})
|
||||
{
|
||||
action->setCheckable(true);
|
||||
action->setVisible(false);
|
||||
action->setActionGroup(group);
|
||||
}
|
||||
|
||||
switch (display)
|
||||
{
|
||||
case RegisterDisplay::Hex:
|
||||
view_hex->setChecked(true);
|
||||
break;
|
||||
case RegisterDisplay::SInt32:
|
||||
view_int->setChecked(true);
|
||||
break;
|
||||
case RegisterDisplay::UInt32:
|
||||
view_uint->setChecked(true);
|
||||
break;
|
||||
case RegisterDisplay::Float:
|
||||
view_float->setChecked(true);
|
||||
break;
|
||||
case RegisterDisplay::Double:
|
||||
view_double->setChecked(true);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case RegisterType::gpr:
|
||||
view_hex->setVisible(true);
|
||||
view_int->setVisible(true);
|
||||
view_uint->setVisible(true);
|
||||
view_float->setVisible(true);
|
||||
break;
|
||||
case RegisterType::fpr:
|
||||
view_hex->setVisible(true);
|
||||
view_double->setVisible(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
connect(view_hex, &QAction::triggered, [this, item] {
|
||||
m_updating = true;
|
||||
item->SetDisplay(RegisterDisplay::Hex);
|
||||
m_updating = false;
|
||||
});
|
||||
|
||||
connect(view_int, &QAction::triggered, [this, item] {
|
||||
m_updating = true;
|
||||
item->SetDisplay(RegisterDisplay::SInt32);
|
||||
m_updating = false;
|
||||
});
|
||||
|
||||
connect(view_uint, &QAction::triggered, [this, item] {
|
||||
m_updating = true;
|
||||
item->SetDisplay(RegisterDisplay::UInt32);
|
||||
m_updating = false;
|
||||
});
|
||||
|
||||
connect(view_float, &QAction::triggered, [this, item] {
|
||||
m_updating = true;
|
||||
item->SetDisplay(RegisterDisplay::Float);
|
||||
m_updating = false;
|
||||
});
|
||||
|
||||
connect(view_double, &QAction::triggered, [this, item] {
|
||||
m_updating = true;
|
||||
item->SetDisplay(RegisterDisplay::Double);
|
||||
m_updating = false;
|
||||
});
|
||||
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
AddAction(menu, tr("Update"), this, [this] { emit RequestTableUpdate(); });
|
||||
|
||||
menu->exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void RegisterWidget::PopulateTable()
|
||||
{
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
// General purpose registers (int)
|
||||
AddRegister(i, 0, RegisterType::gpr, "r" + std::to_string(i), [i] { return GPR(i); },
|
||||
[i](u64 value) { GPR(i) = value; });
|
||||
|
||||
// Floating point registers (double)
|
||||
AddRegister(i, 2, RegisterType::fpr, "f" + std::to_string(i), [i] { return riPS0(i); },
|
||||
[i](u64 value) { riPS0(i) = value; });
|
||||
|
||||
AddRegister(i, 4, RegisterType::fpr, "", [i] { return riPS1(i); },
|
||||
[i](u64 value) { riPS1(i) = value; });
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
// IBAT registers
|
||||
AddRegister(i, 5, RegisterType::ibat, "IBAT" + std::to_string(i),
|
||||
[i] {
|
||||
return (static_cast<u64>(PowerPC::ppcState.spr[SPR_IBAT0U + i * 2]) << 32) +
|
||||
PowerPC::ppcState.spr[SPR_IBAT0L + i * 2];
|
||||
},
|
||||
nullptr);
|
||||
// DBAT registers
|
||||
AddRegister(i + 8, 5, RegisterType::dbat, "DBAT" + std::to_string(i),
|
||||
[i] {
|
||||
return (static_cast<u64>(PowerPC::ppcState.spr[SPR_DBAT0U + i * 2]) << 32) +
|
||||
PowerPC::ppcState.spr[SPR_DBAT0L + i * 2];
|
||||
},
|
||||
nullptr);
|
||||
// Graphics quantization registers
|
||||
AddRegister(i + 16, 7, RegisterType::gqr, "GQR" + std::to_string(i),
|
||||
[i] { return PowerPC::ppcState.spr[SPR_GQR0 + i]; }, nullptr);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
// SR registers
|
||||
AddRegister(i, 7, RegisterType::sr, "SR" + std::to_string(i),
|
||||
[i] { return PowerPC::ppcState.sr[i]; },
|
||||
[i](u64 value) { PowerPC::ppcState.sr[i] = value; });
|
||||
}
|
||||
|
||||
// Special registers
|
||||
// TB
|
||||
AddRegister(16, 5, RegisterType::tb, "TB", PowerPC::ReadFullTimeBaseValue, nullptr);
|
||||
|
||||
// PC
|
||||
AddRegister(17, 5, RegisterType::pc, "PC", [] { return PowerPC::ppcState.pc; },
|
||||
[](u64 value) { PowerPC::ppcState.pc = value; });
|
||||
|
||||
// LR
|
||||
AddRegister(18, 5, RegisterType::lr, "LR", [] { return PowerPC::ppcState.spr[SPR_LR]; },
|
||||
[](u64 value) { PowerPC::ppcState.spr[SPR_LR] = value; });
|
||||
|
||||
// CTR
|
||||
AddRegister(19, 5, RegisterType::ctr, "CTR", [] { return PowerPC::ppcState.spr[SPR_CTR]; },
|
||||
[](u64 value) { PowerPC::ppcState.spr[SPR_CTR] = value; });
|
||||
|
||||
// CR
|
||||
AddRegister(20, 5, RegisterType::cr, "CR", [] { return PowerPC::GetCR(); },
|
||||
[](u64 value) { PowerPC::SetCR(value); });
|
||||
|
||||
// XER
|
||||
AddRegister(21, 5, RegisterType::xer, "XER", [] { return PowerPC::GetXER().Hex; },
|
||||
[](u64 value) { PowerPC::SetXER(UReg_XER(value)); });
|
||||
|
||||
// FPSCR
|
||||
AddRegister(22, 5, RegisterType::fpscr, "FPSCR", [] { return PowerPC::ppcState.fpscr.Hex; },
|
||||
[](u64 value) { PowerPC::ppcState.fpscr = static_cast<u32>(value); });
|
||||
|
||||
// MSR
|
||||
AddRegister(23, 5, RegisterType::msr, "MSR", [] { return PowerPC::ppcState.msr.Hex; },
|
||||
[](u64 value) { PowerPC::ppcState.msr.Hex = value; });
|
||||
|
||||
// SRR 0-1
|
||||
AddRegister(24, 5, RegisterType::srr, "SRR0", [] { return PowerPC::ppcState.spr[SPR_SRR0]; },
|
||||
[](u64 value) { PowerPC::ppcState.spr[SPR_SRR0] = value; });
|
||||
AddRegister(25, 5, RegisterType::srr, "SRR1", [] { return PowerPC::ppcState.spr[SPR_SRR1]; },
|
||||
[](u64 value) { PowerPC::ppcState.spr[SPR_SRR1] = value; });
|
||||
|
||||
// Exceptions
|
||||
AddRegister(26, 5, RegisterType::exceptions, "Exceptions",
|
||||
[] { return PowerPC::ppcState.Exceptions; },
|
||||
[](u64 value) { PowerPC::ppcState.Exceptions = value; });
|
||||
|
||||
// Int Mask
|
||||
AddRegister(27, 5, RegisterType::int_mask, "Int Mask",
|
||||
[] { return ProcessorInterface::GetMask(); }, nullptr);
|
||||
|
||||
// Int Cause
|
||||
AddRegister(28, 5, RegisterType::int_cause, "Int Cause",
|
||||
[] { return ProcessorInterface::GetCause(); }, nullptr);
|
||||
|
||||
// DSISR
|
||||
AddRegister(29, 5, RegisterType::dsisr, "DSISR", [] { return PowerPC::ppcState.spr[SPR_DSISR]; },
|
||||
[](u64 value) { PowerPC::ppcState.spr[SPR_DSISR] = value; });
|
||||
// DAR
|
||||
AddRegister(30, 5, RegisterType::dar, "DAR", [] { return PowerPC::ppcState.spr[SPR_DAR]; },
|
||||
[](u64 value) { PowerPC::ppcState.spr[SPR_DAR] = value; });
|
||||
|
||||
// Hash Mask
|
||||
AddRegister(
|
||||
31, 5, RegisterType::pt_hashmask, "Hash Mask",
|
||||
[] { return (PowerPC::ppcState.pagetable_hashmask << 6) | PowerPC::ppcState.pagetable_base; },
|
||||
nullptr);
|
||||
|
||||
emit RequestTableUpdate();
|
||||
m_table->resizeColumnsToContents();
|
||||
}
|
||||
|
||||
void RegisterWidget::AddRegister(int row, int column, RegisterType type, std::string register_name,
|
||||
std::function<u64()> get_reg, std::function<void(u64)> set_reg)
|
||||
{
|
||||
auto* value = new RegisterColumn(type, std::move(get_reg), std::move(set_reg));
|
||||
|
||||
if (m_table->rowCount() <= row)
|
||||
m_table->setRowCount(row + 1);
|
||||
|
||||
bool has_label = !register_name.empty();
|
||||
|
||||
if (has_label)
|
||||
{
|
||||
auto* label = new QTableWidgetItem(QString::fromStdString(register_name));
|
||||
label->setFlags(Qt::ItemIsEnabled);
|
||||
|
||||
QFont label_font = label->font();
|
||||
label_font.setBold(true);
|
||||
label->setFont(label_font);
|
||||
|
||||
m_table->setItem(row, column, label);
|
||||
m_table->setItem(row, column + 1, value);
|
||||
m_table->item(row, column + 1)->setTextAlignment(Qt::AlignRight);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_table->setItem(row, column, value);
|
||||
m_table->item(row, column)->setTextAlignment(Qt::AlignRight);
|
||||
}
|
||||
|
||||
connect(this, &RegisterWidget::UpdateTable, [value] { value->RefreshValue(); });
|
||||
}
|
47
Source/Core/DolphinQt/Debugger/RegisterWidget.h
Normal file
47
Source/Core/DolphinQt/Debugger/RegisterWidget.h
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QDockWidget>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "DolphinQt/Debugger/RegisterColumn.h"
|
||||
|
||||
class QTableWidget;
|
||||
class QCloseEvent;
|
||||
|
||||
class RegisterWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RegisterWidget(QWidget* parent = nullptr);
|
||||
~RegisterWidget();
|
||||
|
||||
signals:
|
||||
void RequestTableUpdate();
|
||||
void RequestMemoryBreakpoint(u32 addr);
|
||||
void UpdateTable();
|
||||
void UpdateValue(QTableWidgetItem* item);
|
||||
void UpdateValueType(QTableWidgetItem* item);
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void PopulateTable();
|
||||
|
||||
void ShowContextMenu();
|
||||
void OnItemChanged(QTableWidgetItem* item);
|
||||
|
||||
void AddRegister(int row, int column, RegisterType type, std::string register_name,
|
||||
std::function<u64()> get_reg, std::function<void(u64)> set_reg);
|
||||
|
||||
QTableWidget* m_table;
|
||||
bool m_updating = false;
|
||||
};
|
327
Source/Core/DolphinQt/Debugger/WatchWidget.cpp
Normal file
327
Source/Core/DolphinQt/Debugger/WatchWidget.cpp
Normal file
@ -0,0 +1,327 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Debugger/WatchWidget.h"
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IniFile.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
||||
#include "DolphinQt/QtUtils/ActionHelper.h"
|
||||
#include "DolphinQt/Resources.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QTableWidget>
|
||||
#include <QToolBar>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
WatchWidget::WatchWidget(QWidget* parent) : QDockWidget(parent)
|
||||
{
|
||||
// i18n: This kind of "watch" is used for watching emulated memory.
|
||||
// It's not related to timekeeping devices.
|
||||
setWindowTitle(tr("Watch"));
|
||||
setObjectName(QStringLiteral("watch"));
|
||||
|
||||
setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
restoreGeometry(settings.value(QStringLiteral("watchwidget/geometry")).toByteArray());
|
||||
setFloating(settings.value(QStringLiteral("watchwidget/floating")).toBool());
|
||||
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, [this](Core::State state) {
|
||||
if (!Settings::Instance().IsDebugModeEnabled())
|
||||
return;
|
||||
|
||||
m_load->setEnabled(Core::IsRunning());
|
||||
m_save->setEnabled(Core::IsRunning());
|
||||
|
||||
if (state != Core::State::Starting)
|
||||
Update();
|
||||
});
|
||||
|
||||
connect(&Settings::Instance(), &Settings::WatchVisibilityChanged,
|
||||
[this](bool visible) { setHidden(!visible); });
|
||||
|
||||
connect(&Settings::Instance(), &Settings::DebugModeToggled,
|
||||
[this](bool enabled) { setHidden(!enabled || !Settings::Instance().IsWatchVisible()); });
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ThemeChanged, this, &WatchWidget::UpdateIcons);
|
||||
UpdateIcons();
|
||||
|
||||
setHidden(!Settings::Instance().IsWatchVisible() || !Settings::Instance().IsDebugModeEnabled());
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
WatchWidget::~WatchWidget()
|
||||
{
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
settings.setValue(QStringLiteral("watchwidget/geometry"), saveGeometry());
|
||||
settings.setValue(QStringLiteral("watchwidget/floating"), isFloating());
|
||||
}
|
||||
|
||||
void WatchWidget::CreateWidgets()
|
||||
{
|
||||
m_toolbar = new QToolBar;
|
||||
m_toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
|
||||
m_table = new QTableWidget;
|
||||
|
||||
m_table->setColumnCount(5);
|
||||
m_table->verticalHeader()->setHidden(true);
|
||||
m_table->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_table->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
|
||||
m_load = AddAction(m_toolbar, tr("Load"), this, &WatchWidget::OnLoad);
|
||||
m_save = AddAction(m_toolbar, tr("Save"), this, &WatchWidget::OnSave);
|
||||
|
||||
m_load->setEnabled(false);
|
||||
m_save->setEnabled(false);
|
||||
|
||||
auto* layout = new QVBoxLayout;
|
||||
layout->addWidget(m_toolbar);
|
||||
layout->addWidget(m_table);
|
||||
|
||||
QWidget* widget = new QWidget;
|
||||
widget->setLayout(layout);
|
||||
|
||||
setWidget(widget);
|
||||
}
|
||||
|
||||
void WatchWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_table, &QTableWidget::customContextMenuRequested, this, &WatchWidget::ShowContextMenu);
|
||||
connect(m_table, &QTableWidget::itemChanged, this, &WatchWidget::OnItemChanged);
|
||||
}
|
||||
|
||||
void WatchWidget::UpdateIcons()
|
||||
{
|
||||
m_load->setIcon(Resources::GetScaledThemeIcon("debugger_load"));
|
||||
m_save->setIcon(Resources::GetScaledThemeIcon("debugger_save"));
|
||||
}
|
||||
|
||||
void WatchWidget::Update()
|
||||
{
|
||||
m_updating = true;
|
||||
|
||||
m_table->clear();
|
||||
|
||||
int size = static_cast<int>(PowerPC::debug_interface.GetWatches().size());
|
||||
|
||||
m_table->setRowCount(size + 1);
|
||||
|
||||
m_table->setHorizontalHeaderLabels({tr("Label"), tr("Address"), tr("Hexadecimal"), tr("Decimal"),
|
||||
// i18n: Data type used in computing
|
||||
tr("String")});
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
auto entry = PowerPC::debug_interface.GetWatch(i);
|
||||
|
||||
auto* label = new QTableWidgetItem(QString::fromStdString(entry.name));
|
||||
auto* address =
|
||||
new QTableWidgetItem(QStringLiteral("%1").arg(entry.address, 8, 16, QLatin1Char('0')));
|
||||
auto* hex = new QTableWidgetItem;
|
||||
auto* decimal = new QTableWidgetItem;
|
||||
auto* string = new QTableWidgetItem;
|
||||
|
||||
QBrush brush = QPalette().brush(QPalette::Text);
|
||||
|
||||
if (!Core::IsRunning() || !PowerPC::HostIsRAMAddress(entry.address))
|
||||
brush.setColor(Qt::red);
|
||||
|
||||
if (Core::IsRunning())
|
||||
{
|
||||
if (PowerPC::HostIsRAMAddress(entry.address))
|
||||
{
|
||||
hex->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U32(entry.address), 8, 16,
|
||||
QLatin1Char('0')));
|
||||
decimal->setText(QString::number(PowerPC::HostRead_U32(entry.address)));
|
||||
string->setText(QString::fromStdString(PowerPC::HostGetString(entry.address, 32)));
|
||||
}
|
||||
}
|
||||
|
||||
address->setForeground(brush);
|
||||
|
||||
int column = 0;
|
||||
|
||||
for (auto* item : {label, address, hex, decimal, string})
|
||||
{
|
||||
item->setData(Qt::UserRole, i);
|
||||
item->setData(Qt::UserRole + 1, column++);
|
||||
}
|
||||
|
||||
string->setFlags(Qt::ItemIsEnabled);
|
||||
|
||||
m_table->setItem(i, 0, label);
|
||||
m_table->setItem(i, 1, address);
|
||||
m_table->setItem(i, 2, hex);
|
||||
m_table->setItem(i, 3, decimal);
|
||||
m_table->setItem(i, 4, string);
|
||||
}
|
||||
|
||||
auto* label = new QTableWidgetItem;
|
||||
label->setData(Qt::UserRole, -1);
|
||||
|
||||
m_table->setItem(size, 0, label);
|
||||
|
||||
for (int i = 1; i < 5; i++)
|
||||
{
|
||||
auto* no_edit = new QTableWidgetItem;
|
||||
no_edit->setFlags(Qt::ItemIsEnabled);
|
||||
m_table->setItem(size, i, no_edit);
|
||||
}
|
||||
|
||||
m_updating = false;
|
||||
}
|
||||
|
||||
void WatchWidget::closeEvent(QCloseEvent*)
|
||||
{
|
||||
Settings::Instance().SetWatchVisible(false);
|
||||
}
|
||||
|
||||
void WatchWidget::OnLoad()
|
||||
{
|
||||
IniFile ini;
|
||||
|
||||
std::vector<std::string> watches;
|
||||
|
||||
if (!ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini",
|
||||
false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ini.GetLines("Watches", &watches, false))
|
||||
{
|
||||
PowerPC::debug_interface.ClearWatches();
|
||||
PowerPC::debug_interface.LoadWatchesFromStrings(watches);
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void WatchWidget::OnSave()
|
||||
{
|
||||
IniFile ini;
|
||||
ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini",
|
||||
false);
|
||||
ini.SetLines("Watches", PowerPC::debug_interface.SaveWatchesToStrings());
|
||||
ini.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini");
|
||||
}
|
||||
|
||||
void WatchWidget::ShowContextMenu()
|
||||
{
|
||||
QMenu* menu = new QMenu(this);
|
||||
|
||||
if (m_table->selectedItems().size())
|
||||
{
|
||||
auto row_variant = m_table->selectedItems()[0]->data(Qt::UserRole);
|
||||
|
||||
if (!row_variant.isNull())
|
||||
{
|
||||
int row = row_variant.toInt();
|
||||
|
||||
if (row >= 0)
|
||||
{
|
||||
// i18n: This kind of "watch" is used for watching emulated memory.
|
||||
// It's not related to timekeeping devices.
|
||||
AddAction(menu, tr("&Delete Watch"), this, [this, row] { DeleteWatch(row); });
|
||||
AddAction(menu, tr("&Add Memory Breakpoint"), this,
|
||||
[this, row] { AddWatchBreakpoint(row); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
AddAction(menu, tr("Update"), this, &WatchWidget::Update);
|
||||
|
||||
menu->exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void WatchWidget::OnItemChanged(QTableWidgetItem* item)
|
||||
{
|
||||
if (m_updating || item->data(Qt::UserRole).isNull())
|
||||
return;
|
||||
|
||||
int row = item->data(Qt::UserRole).toInt();
|
||||
int column = item->data(Qt::UserRole + 1).toInt();
|
||||
|
||||
if (row == -1)
|
||||
{
|
||||
if (!item->text().isEmpty())
|
||||
{
|
||||
AddWatch(item->text(), 0);
|
||||
|
||||
Update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (column)
|
||||
{
|
||||
// Label
|
||||
case 0:
|
||||
if (item->text().isEmpty())
|
||||
DeleteWatch(row);
|
||||
else
|
||||
PowerPC::debug_interface.UpdateWatchName(row, item->text().toStdString());
|
||||
break;
|
||||
// Address
|
||||
// Hexadecimal
|
||||
// Decimal
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
{
|
||||
bool good;
|
||||
quint32 value = item->text().toUInt(&good, column < 3 ? 16 : 10);
|
||||
|
||||
if (good)
|
||||
{
|
||||
if (column == 1)
|
||||
PowerPC::debug_interface.UpdateWatchAddress(row, value);
|
||||
else
|
||||
PowerPC::HostWrite_U32(value, PowerPC::debug_interface.GetWatch(row).address);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Invalid input provided"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void WatchWidget::DeleteWatch(int row)
|
||||
{
|
||||
PowerPC::debug_interface.RemoveWatch(row);
|
||||
Update();
|
||||
}
|
||||
|
||||
void WatchWidget::AddWatchBreakpoint(int row)
|
||||
{
|
||||
emit RequestMemoryBreakpoint(PowerPC::debug_interface.GetWatch(row).address);
|
||||
}
|
||||
|
||||
void WatchWidget::AddWatch(QString name, u32 addr)
|
||||
{
|
||||
PowerPC::debug_interface.SetWatch(addr, name.toStdString());
|
||||
}
|
53
Source/Core/DolphinQt/Debugger/WatchWidget.h
Normal file
53
Source/Core/DolphinQt/Debugger/WatchWidget.h
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDockWidget>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class QAction;
|
||||
class QTableWidget;
|
||||
class QTableWidgetItem;
|
||||
class QToolBar;
|
||||
class QCloseEvent;
|
||||
|
||||
class WatchWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WatchWidget(QWidget* parent = nullptr);
|
||||
~WatchWidget();
|
||||
|
||||
void AddWatch(QString name, u32 addr);
|
||||
signals:
|
||||
void RequestMemoryBreakpoint(u32 addr);
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
|
||||
void OnLoad();
|
||||
void OnSave();
|
||||
|
||||
void Update();
|
||||
|
||||
void ShowContextMenu();
|
||||
void OnItemChanged(QTableWidgetItem* item);
|
||||
void DeleteWatch(int row);
|
||||
void AddWatchBreakpoint(int row);
|
||||
|
||||
void UpdateIcons();
|
||||
|
||||
QAction* m_load;
|
||||
QAction* m_save;
|
||||
QToolBar* m_toolbar;
|
||||
QTableWidget* m_table;
|
||||
|
||||
bool m_updating = false;
|
||||
};
|
Reference in New Issue
Block a user