diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp index 2cd85627b2..9977f3f38b 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp @@ -29,8 +29,10 @@ #include "Core/Core.h" #include "Core/HW/AddressSpace.h" #include "Core/PowerPC/BreakPoints.h" +#include "Core/PowerPC/PPCSymbolDB.h" #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" +#include "DolphinQt/Debugger/EditSymbolDialog.h" #include "DolphinQt/Host.h" #include "DolphinQt/Resources.h" #include "DolphinQt/Settings.h" @@ -196,7 +198,7 @@ private: }; MemoryViewWidget::MemoryViewWidget(Core::System& system, QWidget* parent) - : QWidget(parent), m_system(system) + : QWidget(parent), m_system(system), m_ppc_symbol_db(m_system.GetPPCSymbolDB()) { auto* layout = new QHBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); @@ -220,6 +222,8 @@ MemoryViewWidget::MemoryViewWidget(Core::System& system, QWidget* parent) this->setLayout(layout); connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &MemoryViewWidget::UpdateFont); + connect(Host::GetInstance(), &Host::PPCSymbolsChanged, this, + [this] { UpdateDispatcher(UpdateType::Symbols); }); connect(Host::GetInstance(), &Host::PPCBreakpointsChanged, this, &MemoryViewWidget::UpdateBreakpointTags); connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this] { @@ -347,6 +351,9 @@ void MemoryViewWidget::UpdateDispatcher(UpdateType type) if (Core::GetState(m_system) == Core::State::Paused) GetValues(); UpdateColumns(); + [[fallthrough]]; + case UpdateType::Symbols: + UpdateSymbols(); break; case UpdateType::Auto: // Values were captured on CPU thread while doing a callback. @@ -371,7 +378,7 @@ void MemoryViewWidget::CreateTable() // Span is the number of unique memory values covered in one row. const int data_span = m_bytes_per_row / GetTypeSize(m_type); m_data_columns = m_dual_view ? data_span * 2 : data_span; - const int total_columns = MISC_COLUMNS + m_data_columns; + const int total_columns = MISC_COLUMNS + m_data_columns + (m_show_symbols ? 1 : 0); const int rows = std::round((m_table->height() / static_cast(m_table->rowHeight(0))) - 0.25); @@ -440,6 +447,15 @@ void MemoryViewWidget::CreateTable() m_table->setItem(i, c + MISC_COLUMNS, item.clone()); } + + if (!m_show_symbols) + continue; + + // Symbols + auto* description_item = new QTableWidgetItem(QStringLiteral("-")); + description_item->setFlags(Qt::ItemIsEnabled); + + m_table->setItem(i, m_table->columnCount() - 1, description_item); } // Update column width @@ -500,6 +516,9 @@ void MemoryViewWidget::Update() item->setBackground(Qt::transparent); item->setData(USER_ROLE_VALID_ADDRESS, false); } + + if (m_show_symbols) + m_table->item(i, m_table->columnCount() - 1)->setData(USER_ROLE_CELL_ADDRESS, row_address); } UpdateBreakpointTags(); @@ -576,6 +595,34 @@ void MemoryViewWidget::UpdateColumns() } } +void MemoryViewWidget::UpdateSymbols() +{ + if (!m_show_symbols) + return; + + // Update symbols + for (int i = 0; i < m_table->rowCount(); i++) + { + auto* item = m_table->item(i, m_table->columnCount() - 1); + if (!item) + continue; + + const u32 address = item->data(USER_ROLE_CELL_ADDRESS).toUInt(); + const Common::Note* note = m_ppc_symbol_db.GetNoteFromAddr(address); + + std::string desc; + if (note == nullptr) + desc = m_ppc_symbol_db.GetDescription(address); + else + desc = note->name; + + item->setText(QString::fromStdString(" " + desc)); + } + + if (m_show_symbols) + m_table->resizeColumnToContents(m_table->columnCount() - 1); +} + // Always runs on CPU thread from a callback. void MemoryViewWidget::UpdateOnFrameEnd() { @@ -1059,6 +1106,78 @@ void MemoryViewWidget::OnCopyHex(u32 addr) QStringLiteral("%1").arg(value, sizeof(u64) * 2, 16, QLatin1Char('0')).left(length * 2)); } +void MemoryViewWidget::ShowSymbols(bool enable) +{ + m_show_symbols = enable; + UpdateDispatcher(UpdateType::Full); +} + +void MemoryViewWidget::OnEditSymbol(EditSymbolType type, u32 addr) +{ + // Add Note and Add Region use these values. + std::string name = ""; + std::string object_name = ""; + u32 size = GetTypeSize(m_type); + u32 address = addr; + EditSymbolDialog::Type dialog_type = EditSymbolDialog::Type::Note; + + // Add and edit region are tied to the same context menu action. + if (type == EditSymbolType::EditRegion) + { + // If symbol doesn't exist, it's safe to add a new region. + const Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); + dialog_type = EditSymbolDialog::Type::Symbol; + + if (symbol != nullptr) + { + // Leave the more specialized function editing to code widget. + if (symbol->type != Common::Symbol::Type::Data) + return; + + // Edit data region. + name = symbol->name; + object_name = symbol->object_name; + size = symbol->size; + address = symbol->address; + } + } + else if (type == EditSymbolType::EditNote) + { + const Common::Note* note = m_ppc_symbol_db.GetNoteFromAddr(addr); + if (note == nullptr) + return; + + name = note->name; + size = note->size; + address = note->address; + } + + EditSymbolDialog dialog(this, address, &size, &name, dialog_type); + + if (dialog.exec() != QDialog::Accepted) + return; + + if (dialog.DeleteRequested()) + { + if (type == EditSymbolType::EditRegion) + m_ppc_symbol_db.DeleteFunction(address); + else + m_ppc_symbol_db.DeleteNote(address); + } + else if (type == EditSymbolType::EditRegion) + { + m_ppc_symbol_db.AddKnownSymbol(Core::CPUThreadGuard{m_system}, address, size, name, object_name, + Common::Symbol::Type::Data); + } + else + { + m_ppc_symbol_db.AddKnownNote(address, size, name); + m_ppc_symbol_db.DetermineNoteLayers(); + } + + emit Host::GetInstance()->PPCSymbolsChanged(); +} + void MemoryViewWidget::OnContextMenu(const QPoint& pos) { auto* item_selected = m_table->itemAt(pos); @@ -1089,6 +1208,21 @@ void MemoryViewWidget::OnContextMenu(const QPoint& pos) menu->addSeparator(); + auto* note_add_action = menu->addAction( + tr("Add Note"), this, [this, addr] { OnEditSymbol(EditSymbolType::AddNote, addr); }); + auto* note_edit_action = menu->addAction( + tr("Edit Note"), this, [this, addr] { OnEditSymbol(EditSymbolType::EditNote, addr); }); + menu->addAction(tr("Add or edit region label"), this, + [this, addr] { OnEditSymbol(EditSymbolType::EditRegion, addr); }); + + auto* note = m_ppc_symbol_db.GetNoteFromAddr(addr); + note_edit_action->setEnabled(note != nullptr); + // A note cannot be added ontop of the starting address of another note. + if (note != nullptr && note->address == addr) + note_add_action->setEnabled(false); + + menu->addSeparator(); + menu->addAction(tr("Show in code"), this, [this, addr] { emit ShowCode(addr); }); menu->addSeparator(); diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h index 46643e39a4..102d6e0aed 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h +++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h @@ -24,6 +24,8 @@ class CPUThreadGuard; class System; } // namespace Core +class PPCSymbolDB; + // Captures direct editing of the table. class TableEditDelegate : public QStyledItemDelegate { @@ -76,14 +78,23 @@ public: Full, Addresses, Values, + Symbols, Auto, }; + enum class EditSymbolType + { + AddNote, + EditNote, + EditRegion, + }; + explicit MemoryViewWidget(Core::System& system, QWidget* parent = nullptr); void CreateTable(); void UpdateDispatcher(UpdateType type = UpdateType::Addresses); void Update(); + void UpdateSymbols(); void UpdateOnFrameEnd(); void GetValues(); void UpdateFont(const QFont& font); @@ -98,6 +109,7 @@ public: void SetBPType(BPType type); void SetAddress(u32 address); void SetFocus() const; + void ShowSymbols(bool enable); void SetBPLoggingEnabled(bool enabled); @@ -108,6 +120,7 @@ signals: void ActivateSearch(); private: + void OnEditSymbol(EditSymbolType type, u32 addr); void OnContextMenu(const QPoint& pos); void OnCopyAddress(u32 addr); void OnCopyHex(u32 addr); @@ -116,9 +129,11 @@ private: void UpdateColumns(); void ScrollbarActionTriggered(int action); void ScrollbarSliderReleased(); + std::optional ValueToString(const Core::CPUThreadGuard& guard, u32 address, Type type); Core::System& m_system; + PPCSymbolDB& m_ppc_symbol_db; MemoryViewTable* m_table; QScrollBar* m_scrollbar; @@ -137,6 +152,7 @@ private: int m_alignment = 16; int m_data_columns; bool m_dual_view = false; + bool m_show_symbols = true; std::mutex m_updating; QColor m_highlight_color = QColor(120, 255, 255, 100); diff --git a/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp index 50ecbf5dd9..7e68df9c7a 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,7 @@ #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/HW/AddressSpace.h" +#include "Core/PowerPC/PPCSymbolDB.h" #include "Core/System.h" #include "DolphinQt/Debugger/MemoryViewWidget.h" #include "DolphinQt/Host.h" @@ -41,7 +43,7 @@ using Type = MemoryViewWidget::Type; MemoryWidget::MemoryWidget(Core::System& system, QWidget* parent) - : QDockWidget(parent), m_system(system) + : QDockWidget(parent), m_system(system), m_ppc_symbol_db(system.GetPPCSymbolDB()) { setWindowTitle(tr("Memory")); setObjectName(QStringLiteral("memory")); @@ -247,6 +249,25 @@ void MemoryWidget::CreateWidgets() bp_layout->addWidget(m_bp_log_check); bp_layout->setSpacing(1); + // Notes + m_labels_group = new QGroupBox(tr("Labels")); + auto* symbols_box = new QTabWidget; + m_note_list = new QListWidget; + m_data_list = new QListWidget; + m_symbols_list = new QListWidget; + symbols_box->addTab(m_note_list, tr("Notes")); + symbols_box->addTab(m_data_list, tr("Data")); + symbols_box->addTab(m_symbols_list, tr("Symbols")); + m_symbols_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + auto* labels_layout = new QVBoxLayout; + m_search_labels = new QLineEdit; + m_search_labels->setPlaceholderText(tr("Filter Label List")); + + m_labels_group->setLayout(labels_layout); + labels_layout->addWidget(symbols_box); + labels_layout->addWidget(m_search_labels); + // Sidebar auto* sidebar = new QWidget; auto* sidebar_layout = new QVBoxLayout; @@ -262,8 +283,9 @@ void MemoryWidget::CreateWidgets() &MemoryWidget::OnSetValueFromFile); menubar->addMenu(menu_import); + // View Menu auto* auto_update_action = - menu_views->addAction(tr("Auto update memory values"), this, [this](bool checked) { + menu_views->addAction(tr("&Auto update memory values"), this, [this](bool checked) { m_auto_update_enabled = checked; if (checked) RegisterAfterFrameEventCallback(); @@ -274,14 +296,23 @@ void MemoryWidget::CreateWidgets() auto_update_action->setChecked(true); auto* highlight_update_action = - menu_views->addAction(tr("Highlight recently changed values"), this, + menu_views->addAction(tr("&Highlight recently changed values"), this, [this](bool checked) { m_memory_view->ToggleHighlights(checked); }); highlight_update_action->setCheckable(true); highlight_update_action->setChecked(true); - menu_views->addAction(tr("Highlight color"), this, + menu_views->addAction(tr("Highlight &color"), this, [this] { m_memory_view->SetHighlightColor(); }); + auto* show_notes = + menu_views->addAction(tr("&Show symbols and notes"), this, [this](bool checked) { + m_labels_visible = checked; + m_memory_view->ShowSymbols(checked); + UpdateNotes(); + }); + show_notes->setCheckable(true); + show_notes->setChecked(true); + QMenu* menu_export = new QMenu(tr("&Export"), menubar); menu_export->addAction(tr("Dump &MRAM"), this, &MemoryWidget::OnDumpMRAM); menu_export->addAction(tr("Dump &ExRAM"), this, &MemoryWidget::OnDumpExRAM); @@ -306,6 +337,7 @@ void MemoryWidget::CreateWidgets() sidebar_layout->addWidget(address_space_group); sidebar_layout->addItem(new QSpacerItem(1, 10)); sidebar_layout->addWidget(bp_group); + sidebar_layout->addWidget(m_labels_group); sidebar_layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding)); // Splitter @@ -327,6 +359,7 @@ void MemoryWidget::CreateWidgets() auto* widget = new QWidget; widget->setLayout(layout); setWidget(widget); + UpdateNotes(); } void MemoryWidget::ConnectWidgets() @@ -359,6 +392,12 @@ void MemoryWidget::ConnectWidgets() connect(m_base_check, &QCheckBox::toggled, this, &MemoryWidget::ValidateAndPreviewInputValue); connect(m_bp_log_check, &QCheckBox::toggled, this, &MemoryWidget::OnBPLogChanged); + + for (auto* list : {m_symbols_list, m_data_list, m_note_list}) + connect(list, &QListWidget::itemClicked, this, &MemoryWidget::OnSelectLabel); + + connect(Host::GetInstance(), &Host::PPCSymbolsChanged, this, &MemoryWidget::RefreshLabelBox); + connect(m_search_labels, &QLineEdit::textChanged, this, &MemoryWidget::RefreshLabelBox); connect(m_memory_view, &MemoryViewWidget::ShowCode, this, &MemoryWidget::ShowCode); connect(m_memory_view, &MemoryViewWidget::RequestWatch, this, &MemoryWidget::RequestWatch); connect(m_memory_view, &MemoryViewWidget::ActivateSearch, this, @@ -795,6 +834,100 @@ void MemoryWidget::OnSetValueFromFile() Update(); } +void MemoryWidget::RefreshLabelBox() +{ + if (!m_labels_visible || (m_ppc_symbol_db.Notes().empty() && m_ppc_symbol_db.IsEmpty())) + { + m_labels_group->hide(); + return; + } + + m_labels_group->show(); + + UpdateSymbols(); + UpdateNotes(); +} + +void MemoryWidget::OnSelectLabel() +{ + QList items; + if (m_note_list->isVisible()) + items = m_note_list->selectedItems(); + else if (m_symbols_list->isVisible()) + items = m_symbols_list->selectedItems(); + else if (m_data_list->isVisible()) + items = m_data_list->selectedItems(); + + if (items.isEmpty()) + return; + + const u32 address = items[0]->data(Qt::UserRole).toUInt(); + + SetAddress(address); +} + +void MemoryWidget::UpdateSymbols() +{ + const QString selection = m_symbols_list->selectedItems().isEmpty() ? + QString{} : + m_symbols_list->selectedItems()[0]->text(); + m_symbols_list->clear(); + m_data_list->clear(); + + for (const auto& symbol : m_ppc_symbol_db.Symbols()) + { + QString name = QString::fromStdString(symbol.second.name); + + // If the symbol has an object name, add it to the entry name. + if (!symbol.second.object_name.empty()) + { + name += QString::fromStdString(fmt::format(" ({})", symbol.second.object_name)); + } + + auto* item = new QListWidgetItem(name); + if (name == selection) + item->setSelected(true); + + item->setData(Qt::UserRole, symbol.second.address); + + if (!name.contains(m_search_labels->text(), Qt::CaseInsensitive)) + continue; + + if (symbol.second.type != Common::Symbol::Type::Function) + m_data_list->addItem(item); + else + m_symbols_list->addItem(item); + } + + m_symbols_list->sortItems(); +} + +void MemoryWidget::UpdateNotes() +{ + // Save selection to re-apply. + const QString selection = m_note_list->selectedItems().isEmpty() ? + QStringLiteral("") : + m_note_list->selectedItems()[0]->text(); + m_note_list->clear(); + + for (const auto& note : m_ppc_symbol_db.Notes()) + { + const QString name = QString::fromStdString(note.second.name); + + auto* item = new QListWidgetItem(name); + if (name == selection) + item->setSelected(true); + + item->setData(Qt::UserRole, note.second.address); + + // Filter notes based on the search text. + if (name.contains(m_search_labels->text(), Qt::CaseInsensitive)) + m_note_list->addItem(item); + } + + m_note_list->sortItems(); +} + static void DumpArray(const std::string& filename, const u8* data, size_t length) { if (!data) diff --git a/Source/Core/DolphinQt/Debugger/MemoryWidget.h b/Source/Core/DolphinQt/Debugger/MemoryWidget.h index 6aa1f01aa5..e1b0118844 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryWidget.h +++ b/Source/Core/DolphinQt/Debugger/MemoryWidget.h @@ -14,8 +14,11 @@ class MemoryViewWidget; class QCheckBox; class QComboBox; +class QGroupBox; +class QHideEvent; class QLabel; class QLineEdit; +class QListWidget; class QPushButton; class QRadioButton; class QShowEvent; @@ -27,6 +30,8 @@ class System; class CPUThreadGuard; } // namespace Core +class PPCSymbolDB; + class MemoryWidget : public QDockWidget { Q_OBJECT @@ -66,6 +71,11 @@ private: void OnSetValue(); void OnSetValueFromFile(); + void OnSelectLabel(); + void RefreshLabelBox(); + void UpdateSymbols(); + void UpdateNotes(); + void OnDumpMRAM(); void OnDumpExRAM(); void OnDumpARAM(); @@ -85,6 +95,7 @@ private: void ActivateSearchAddress(); Core::System& m_system; + PPCSymbolDB& m_ppc_symbol_db; MemoryViewWidget* m_memory_view; QSplitter* m_splitter; @@ -115,6 +126,15 @@ private: QRadioButton* m_bp_read_only; QRadioButton* m_bp_write_only; QCheckBox* m_bp_log_check; + + QGroupBox* m_labels_group; + QLineEdit* m_search_labels; + QListWidget* m_symbols_list; + QListWidget* m_data_list; + QListWidget* m_note_list; + QString m_note_filter; + bool m_labels_visible = true; + Common::EventHook m_vi_end_field_event; bool m_auto_update_enabled = true;