Move DolphinQt2 to DolphinQt

This commit is contained in:
spycrab
2018-07-07 00:40:15 +02:00
parent 059880bb16
commit 13ba24c5a6
233 changed files with 392 additions and 392 deletions

View 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();
}

View 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;
};

View 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;
}

View 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;
};

View 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();
}

View 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;
};

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

View 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;
};

View 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());
}

View 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;
};

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

View 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;
};

View 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();
}

View 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;
};

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

View 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;
};

View 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(); });
}

View 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;
};

View 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());
}

View 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;
};