| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| // Copyright 2017 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include "DolphinQt2/Debugger/NewBreakpointDialog.h" | ||
|
|
||
| #include <QCheckBox> | ||
| #include <QDialogButtonBox> | ||
| #include <QGridLayout> | ||
| #include <QGroupBox> | ||
| #include <QHBoxLayout> | ||
| #include <QLabel> | ||
| #include <QLineEdit> | ||
| #include <QMessageBox> | ||
| #include <QRadioButton> | ||
| #include <QVBoxLayout> | ||
|
|
||
| #include "DolphinQt2/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); | ||
| 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:")); | ||
| m_memory_on_read = new QRadioButton(tr("Read")); | ||
| m_memory_on_write = new QRadioButton(tr("Write")); | ||
| m_memory_on_read_and_write = new QRadioButton(tr("Read or Write")); | ||
| m_memory_on_write->setChecked(true); | ||
| m_memory_do_log = new QRadioButton(tr("Log")); | ||
| m_memory_do_break = new QRadioButton(tr("Break")); | ||
| m_memory_do_log_and_break = new QRadioButton(tr("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); | ||
| memory_layout->addWidget(new QLabel(tr("On...")), 2, 0); | ||
| memory_layout->addWidget(m_memory_on_read, 2, 1); | ||
| memory_layout->addWidget(m_memory_on_write, 2, 2); | ||
| memory_layout->addWidget(m_memory_on_read_and_write, 2, 3); | ||
| memory_layout->addWidget(new QLabel(tr("Do...")), 3, 0); | ||
| memory_layout->addWidget(m_memory_do_log, 3, 1); | ||
| memory_layout->addWidget(m_memory_do_break, 3, 2); | ||
| memory_layout->addWidget(m_memory_do_log_and_break, 3, 3); | ||
|
|
||
| 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("Bad input provided for %1 field").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(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(); | ||
|
|
||
| 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; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| // Copyright 2017 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include "DolphinQt2/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(set == nullptr ? flags() ^ Qt::ItemIsEditable : flags()); | ||
| 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; | ||
| } | ||
| } | ||
|
|
||
| if (!valid) | ||
| { | ||
| QMessageBox::critical(nullptr, QObject::tr("Invalid input"), | ||
| QObject::tr("Bad input for field")); | ||
| } | ||
| 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 ? | ||
| 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; | ||
| } | ||
| } | ||
|
|
||
| setText(text); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| // 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, // General purpose registers, float (f0-f31) | ||
| ibat, // Instruction BATs (IBAT0-IBAT7) | ||
| dbat, // Data BATs (DBAT0-DBAT7) | ||
| pc, // Program counter | ||
| lr, // Link register | ||
| ctr, // Decremented and incremented by branch and count instructions | ||
| cr, // Condition 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) | ||
| 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 | ||
| }; | ||
|
|
||
| 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; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,338 @@ | ||
| // Copyright 2017 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include "DolphinQt2/Debugger/RegisterWidget.h" | ||
|
|
||
| #include "Core/Core.h" | ||
| #include "Core/HW/ProcessorInterface.h" | ||
| #include "Core/PowerPC/PowerPC.h" | ||
| #include "DolphinQt2/QtUtils/ActionHelper.h" | ||
| #include "DolphinQt2/Settings.h" | ||
|
|
||
| #include <QHeaderView> | ||
| #include <QMenu> | ||
| #include <QSettings> | ||
| #include <QTableWidget> | ||
| #include <QVBoxLayout> | ||
|
|
||
| RegisterWidget::RegisterWidget(QWidget* parent) : QDockWidget(parent) | ||
| { | ||
| setWindowTitle(tr("Registers")); | ||
| setAllowedAreas(Qt::AllDockWidgetAreas); | ||
|
|
||
| QSettings settings; | ||
|
|
||
| restoreGeometry(settings.value(QStringLiteral("registerwidget/geometry")).toByteArray()); | ||
| setFloating(settings.value(QStringLiteral("registerwidget/floating")).toBool()); | ||
|
|
||
| CreateWidgets(); | ||
| PopulateTable(); | ||
| ConnectWidgets(); | ||
|
|
||
| connect(&Settings::Instance(), &Settings::EmulationStateChanged, [this](Core::State state) { | ||
| if (Settings::Instance().IsDebugModeEnabled() && Core::GetState() == Core::State::Paused) | ||
| emit RequestTableUpdate(); | ||
| }); | ||
|
|
||
| connect(this, &RegisterWidget::RequestTableUpdate, [this] { | ||
| 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() | ||
| { | ||
| QSettings settings; | ||
|
|
||
| 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->setContextMenuPolicy(Qt::CustomContextMenu); | ||
| m_table->setSelectionMode(QAbstractItemView::SingleSelection); | ||
|
|
||
| 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); | ||
|
|
||
| if (m_table->selectedItems().size()) | ||
| { | ||
| auto variant = m_table->selectedItems()[0]->data(DATA_TYPE); | ||
|
|
||
| if (!variant.isNull()) | ||
| { | ||
| auto* item = reinterpret_cast<RegisterColumn*>(m_table->selectedItems()[0]); | ||
| 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")); | ||
| auto* view_float = menu->addAction(tr("Float")); | ||
|
|
||
| for (auto* action : {view_hex, view_int, view_uint, view_float}) | ||
| { | ||
| 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; | ||
| } | ||
|
|
||
| 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_float->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; | ||
| }); | ||
|
|
||
| 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; }); | ||
|
|
||
| // General purpose registers (float) | ||
| 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); | ||
| } | ||
|
|
||
| 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 | ||
| // PC | ||
| AddRegister(16, 5, RegisterType::pc, "PC", [] { return PowerPC::ppcState.pc; }, | ||
| [](u64 value) { PowerPC::ppcState.pc = value; }); | ||
|
|
||
| // LR | ||
| AddRegister(17, 5, RegisterType::fpscr, "LR", [] { return PowerPC::ppcState.spr[SPR_LR]; }, | ||
| [](u64 value) { PowerPC::ppcState.spr[SPR_LR] = value; }); | ||
|
|
||
| // CTR | ||
| AddRegister(18, 5, RegisterType::fpscr, "FPSCR", [] { return PowerPC::ppcState.spr[SPR_CTR]; }, | ||
| [](u64 value) { PowerPC::ppcState.spr[SPR_CTR] = value; }); | ||
|
|
||
| // CR | ||
| AddRegister(19, 5, RegisterType::cr, "CR", [] { return GetCR(); }, | ||
| [](u64 value) { SetCR(value); }); | ||
|
|
||
| // FPSCR | ||
| AddRegister(20, 5, RegisterType::fpscr, "FPSCR", [] { return PowerPC::ppcState.fpscr; }, | ||
| [](u64 value) { PowerPC::ppcState.fpscr = value; }); | ||
|
|
||
| // MSR | ||
| AddRegister(21, 5, RegisterType::msr, "MSR", [] { return PowerPC::ppcState.msr; }, | ||
| [](u64 value) { PowerPC::ppcState.msr = value; }); | ||
|
|
||
| // SRR 0-1 | ||
| AddRegister(22, 5, RegisterType::srr, "SRR0", [] { return PowerPC::ppcState.spr[SPR_SRR0]; }, | ||
| [](u64 value) { PowerPC::ppcState.spr[SPR_SRR0] = value; }); | ||
| AddRegister(23, 5, RegisterType::srr, "SRR1", [] { return PowerPC::ppcState.spr[SPR_SRR1]; }, | ||
| [](u64 value) { PowerPC::ppcState.spr[SPR_SRR1] = value; }); | ||
|
|
||
| // Exceptions | ||
| AddRegister(24, 5, RegisterType::exceptions, "Exceptions", | ||
| [] { return PowerPC::ppcState.Exceptions; }, | ||
| [](u64 value) { PowerPC::ppcState.Exceptions = value; }); | ||
|
|
||
| // Int Mask | ||
| AddRegister(25, 5, RegisterType::int_mask, "Int Mask", | ||
| [] { return ProcessorInterface::GetMask(); }, nullptr); | ||
|
|
||
| // Int Cause | ||
| AddRegister(26, 5, RegisterType::int_cause, "Int Cause", | ||
| [] { return ProcessorInterface::GetCause(); }, nullptr); | ||
|
|
||
| // DSISR | ||
| AddRegister(27, 5, RegisterType::dsisr, "DSISR", [] { return PowerPC::ppcState.spr[SPR_DSISR]; }, | ||
| [](u64 value) { PowerPC::ppcState.spr[SPR_DSISR] = value; }); | ||
| // DAR | ||
| AddRegister(28, 5, RegisterType::dar, "DAR", [] { return PowerPC::ppcState.spr[SPR_DAR]; }, | ||
| [](u64 value) { PowerPC::ppcState.spr[SPR_DAR] = value; }); | ||
|
|
||
| // Hash Mask | ||
| AddRegister( | ||
| 29, 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<u32()> get_reg, std::function<void(u32)> set_reg) | ||
| { | ||
| auto* value = new RegisterColumn(type, get_reg, 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); | ||
| } | ||
| else | ||
| { | ||
| m_table->setItem(row, column, value); | ||
| } | ||
|
|
||
| connect(this, &RegisterWidget::UpdateTable, [value] { value->RefreshValue(); }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 "DolphinQt2/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<u32()> get_reg, std::function<void(u32)> set_reg); | ||
|
|
||
| QTableWidget* m_table; | ||
| bool m_updating = false; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,309 @@ | ||
| // Copyright 2017 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include "DolphinQt2/Debugger/WatchWidget.h" | ||
|
|
||
| #include "Common/FileUtil.h" | ||
| #include "Common/IniFile.h" | ||
| #include "Core/ConfigManager.h" | ||
| #include "Core/Core.h" | ||
| #include "Core/PowerPC/PowerPC.h" | ||
| #include "DolphinQt2/QtUtils/ActionHelper.h" | ||
| #include "DolphinQt2/Settings.h" | ||
|
|
||
| #include <QHeaderView> | ||
| #include <QMenu> | ||
| #include <QMessageBox> | ||
| #include <QSettings> | ||
| #include <QTableWidget> | ||
| #include <QToolBar> | ||
| #include <QVBoxLayout> | ||
|
|
||
| WatchWidget::WatchWidget(QWidget* parent) : QDockWidget(parent) | ||
| { | ||
| setWindowTitle(tr("Watch")); | ||
| setAllowedAreas(Qt::AllDockWidgetAreas); | ||
|
|
||
| QSettings settings; | ||
|
|
||
| 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()); }); | ||
|
|
||
| setHidden(!Settings::Instance().IsWatchVisible() || !Settings::Instance().IsDebugModeEnabled()); | ||
|
|
||
| Update(); | ||
| } | ||
|
|
||
| WatchWidget::~WatchWidget() | ||
| { | ||
| QSettings settings; | ||
|
|
||
| settings.setValue(QStringLiteral("watchwidget/geometry"), saveGeometry()); | ||
| settings.setValue(QStringLiteral("watchwidget/floating"), isFloating()); | ||
| } | ||
|
|
||
| void WatchWidget::CreateWidgets() | ||
| { | ||
| m_toolbar = new QToolBar; | ||
| 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::Update() | ||
| { | ||
| m_updating = true; | ||
|
|
||
| m_table->clear(); | ||
|
|
||
| int size = static_cast<int>(PowerPC::watches.GetWatches().size()); | ||
|
|
||
| m_table->setRowCount(size + 1); | ||
|
|
||
| m_table->setHorizontalHeaderLabels( | ||
| {tr("Label"), tr("Address"), tr("Hexadecimal"), tr("Decimal"), tr("String")}); | ||
|
|
||
| for (int i = 0; i < size; i++) | ||
| { | ||
| auto entry = PowerPC::watches.GetWatches().at(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; | ||
|
|
||
| Watches::TWatchesStr watches; | ||
|
|
||
| if (!ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini", | ||
| false)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (ini.GetLines("Watches", &watches, false)) | ||
| { | ||
| PowerPC::watches.Clear(); | ||
| PowerPC::watches.AddFromStrings(watches); | ||
| } | ||
|
|
||
| Update(); | ||
| } | ||
|
|
||
| void WatchWidget::OnSave() | ||
| { | ||
| IniFile ini; | ||
| ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini", | ||
| false); | ||
| ini.SetLines("Watches", PowerPC::watches.GetStrings()); | ||
| 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) | ||
| { | ||
| 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::watches.UpdateName(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::watches.Update(row, value); | ||
| else | ||
| PowerPC::HostWrite_U32(value, PowerPC::watches.GetWatches().at(row).address); | ||
| } | ||
| else | ||
| { | ||
| QMessageBox::critical(this, tr("Error"), tr("Bad input provided")); | ||
| } | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| Update(); | ||
| } | ||
| } | ||
|
|
||
| void WatchWidget::DeleteWatch(int row) | ||
| { | ||
| PowerPC::watches.Remove(PowerPC::watches.GetWatches().at(row).address); | ||
| Update(); | ||
| } | ||
|
|
||
| void WatchWidget::AddWatchBreakpoint(int row) | ||
| { | ||
| emit RequestMemoryBreakpoint(PowerPC::watches.GetWatches().at(row).address); | ||
| } | ||
|
|
||
| void WatchWidget::AddWatch(QString name, u32 addr) | ||
| { | ||
| PowerPC::watches.Add(addr); | ||
| PowerPC::watches.UpdateName(static_cast<int>(PowerPC::watches.GetWatches().size()) - 1, | ||
| name.toStdString()); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| // 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); | ||
|
|
||
| QAction* m_load; | ||
| QAction* m_save; | ||
| QToolBar* m_toolbar; | ||
| QTableWidget* m_table; | ||
|
|
||
| bool m_updating = false; | ||
| }; |