From e50fe50daf207860f0375551ed13318d401f1fbd Mon Sep 17 00:00:00 2001 From: Ziemas Date: Tue, 20 May 2025 19:31:02 +0200 Subject: [PATCH] debugger: display iop module list --- pcsx2-qt/CMakeLists.txt | 4 + pcsx2-qt/Debugger/Docking/DockTables.cpp | 3 + pcsx2-qt/Debugger/ModuleModel.cpp | 134 +++++++++++++++++++++++ pcsx2-qt/Debugger/ModuleModel.h | 51 +++++++++ pcsx2-qt/Debugger/ModuleView.cpp | 99 +++++++++++++++++ pcsx2-qt/Debugger/ModuleView.h | 27 +++++ pcsx2-qt/Debugger/ModuleView.ui | 39 +++++++ pcsx2-qt/pcsx2-qt.vcxproj | 7 ++ pcsx2/DebugTools/BiosDebugData.cpp | 11 +- pcsx2/DebugTools/DebugInterface.cpp | 10 ++ pcsx2/DebugTools/DebugInterface.h | 3 + 11 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 pcsx2-qt/Debugger/ModuleModel.cpp create mode 100644 pcsx2-qt/Debugger/ModuleModel.h create mode 100644 pcsx2-qt/Debugger/ModuleView.cpp create mode 100644 pcsx2-qt/Debugger/ModuleView.h create mode 100644 pcsx2-qt/Debugger/ModuleView.ui diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index a62df95399..230292f5fa 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -173,6 +173,10 @@ target_sources(pcsx2-qt PRIVATE Debugger/DisassemblyView.h Debugger/DisassemblyView.ui Debugger/JsonValueWrapper.h + Debugger/ModuleModel.cpp + Debugger/ModuleModel.h + Debugger/ModuleView.cpp + Debugger/ModuleView.h Debugger/RegisterView.cpp Debugger/RegisterView.h Debugger/RegisterView.ui diff --git a/pcsx2-qt/Debugger/Docking/DockTables.cpp b/pcsx2-qt/Debugger/Docking/DockTables.cpp index d37376a106..0ce7ced75e 100644 --- a/pcsx2-qt/Debugger/Docking/DockTables.cpp +++ b/pcsx2-qt/Debugger/Docking/DockTables.cpp @@ -5,6 +5,7 @@ #include "Debugger/DebuggerEvents.h" #include "Debugger/DisassemblyView.h" +#include "Debugger/ModuleView.h" #include "Debugger/RegisterView.h" #include "Debugger/StackView.h" #include "Debugger/ThreadView.h" @@ -49,6 +50,7 @@ const std::map DockTables::DEB DEBUGGER_VIEW(SavedAddressesView, QT_TRANSLATE_NOOP("DebuggerView", "Saved Addresses"), BOTTOM_MIDDLE), DEBUGGER_VIEW(StackView, QT_TRANSLATE_NOOP("DebuggerView", "Stack"), BOTTOM_MIDDLE), DEBUGGER_VIEW(ThreadView, QT_TRANSLATE_NOOP("DebuggerView", "Threads"), BOTTOM_MIDDLE), + DEBUGGER_VIEW(ModuleView, QT_TRANSLATE_NOOP("DebuggerView", "Modules"), BOTTOM_MIDDLE), }; #undef DEBUGGER_VIEW @@ -99,6 +101,7 @@ const std::vector DockTables::DEFAULT_DOCK_LAYOUT {"MemoryView", DefaultDockGroup::BOTTOM}, {"BreakpointView", DefaultDockGroup::BOTTOM}, {"ThreadView", DefaultDockGroup::BOTTOM}, + {"ModuleView", DefaultDockGroup::BOTTOM}, {"StackView", DefaultDockGroup::BOTTOM}, {"SavedAddressesView", DefaultDockGroup::BOTTOM}, {"GlobalVariableTreeView", DefaultDockGroup::BOTTOM}, diff --git a/pcsx2-qt/Debugger/ModuleModel.cpp b/pcsx2-qt/Debugger/ModuleModel.cpp new file mode 100644 index 0000000000..1cb6394cdf --- /dev/null +++ b/pcsx2-qt/Debugger/ModuleModel.cpp @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "ModuleModel.h" + +#include "QtUtils.h" +#include "fmt/format.h" + +ModuleModel::ModuleModel(DebugInterface& cpu, QObject* parent) + : QAbstractTableModel(parent) + , m_cpu(cpu) +{ +} + +int ModuleModel::rowCount(const QModelIndex&) const +{ + return m_cpu.GetModuleList().size(); +} + +int ModuleModel::columnCount(const QModelIndex&) const +{ + return ModuleModel::COLUMN_COUNT; +} + +QVariant ModuleModel::data(const QModelIndex& index, int role) const +{ + const std::vector Modules = m_cpu.GetModuleList(); + + size_t row = static_cast(index.row()); + if (row >= Modules.size()) + return QVariant(); + + const IopMod* mod = &Modules[row]; + + if (role == Qt::DisplayRole) + { + switch (index.column()) + { + case ModuleModel::ModuleColumns::NAME: + return mod->name.c_str(); + case ModuleModel::ModuleColumns::VERSION: + return fmt::format("{}.{}", mod->version >> 8, mod->version & 0xff).c_str(); + case ModuleModel::ModuleColumns::ENTRY: + return QtUtils::FilledQStringFromValue(mod->entry, 16); + case ModuleModel::ModuleColumns::GP: + return QtUtils::FilledQStringFromValue(mod->gp, 16); + case ModuleModel::ModuleColumns::TEXT_SECTION: + { + return QString("[%1 - %2]").arg(QtUtils::FilledQStringFromValue(mod->text_addr, 16), QtUtils::FilledQStringFromValue(mod->text_addr + mod->text_size - 1, 16)); + } + case ModuleModel::ModuleColumns::DATA_SECTION: + { + u32 addr = mod->text_addr + mod->text_size; + return QString("[%1 - %2]").arg(QtUtils::FilledQStringFromValue(addr, 16), QtUtils::FilledQStringFromValue(addr + mod->data_size - 1, 16)); + } + case ModuleModel::ModuleColumns::BSS_SECTION: + { + if (mod->bss_size == 0) + { + return ""; + } + u32 addr = mod->text_addr + mod->text_size + mod->data_size; + return QString("[%1 - %2]").arg(QtUtils::FilledQStringFromValue(addr, 16), QtUtils::FilledQStringFromValue(addr + mod->bss_size - 1, 16)); + } + } + } + else if (role == Qt::UserRole) + { + switch (index.column()) + { + case ModuleModel::ModuleColumns::NAME: + return mod->name.c_str(); + case ModuleModel::ModuleColumns::VERSION: + return mod->version; + case ModuleModel::ModuleColumns::ENTRY: + return mod->entry; + case ModuleModel::ModuleColumns::GP: + return mod->gp; + case ModuleModel::ModuleColumns::TEXT_SECTION: + return mod->text_addr; + case ModuleModel::ModuleColumns::DATA_SECTION: + return mod->text_addr + mod->text_size; + case ModuleModel::ModuleColumns::BSS_SECTION: + { + if (mod->bss_size == 0) + { + return 0; + } + return mod->text_addr + mod->text_size + mod->data_size; + } + } + } + return QVariant(); +} + +QVariant ModuleModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) + { + switch (section) + { + case ModuleColumns::NAME: + //: Warning: short space limit. Abbreviate if needed. + return tr("NAME"); + case ModuleColumns::VERSION: + //: Warning: short space limit. Abbreviate if needed. + return tr("VERSION"); + case ModuleColumns::ENTRY: + //: Warning: short space limit. Abbreviate if needed. // Entrypoint of the executable + return tr("ENTRY"); + case ModuleColumns::GP: + //: Warning: short space limit. Abbreviate if needed. + return tr("GP"); + case ModuleColumns::TEXT_SECTION: + //: Warning: short space limit. Abbreviate if needed. // Text section of the executable + return tr("TEXT"); + case ModuleColumns::DATA_SECTION: + //: Warning: short space limit. Abbreviate if needed. // Data section of the executable + return tr("DATA"); + case ModuleColumns::BSS_SECTION: + //: Warning: short space limit. Abbreviate if needed. // BSS section of the executable + return tr("BSS"); + default: + return QVariant(); + } + } + return QVariant(); +} + +void ModuleModel::refreshData() +{ + beginResetModel(); + endResetModel(); +} diff --git a/pcsx2-qt/Debugger/ModuleModel.h b/pcsx2-qt/Debugger/ModuleModel.h new file mode 100644 index 0000000000..095ded4121 --- /dev/null +++ b/pcsx2-qt/Debugger/ModuleModel.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include +#include + +#include "DebugTools/DebugInterface.h" +#include "DebugTools/BiosDebugData.h" + +class ModuleModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + enum ModuleColumns : int + { + NAME = 0, + VERSION, + ENTRY, + GP, + TEXT_SECTION, + DATA_SECTION, + BSS_SECTION, + COLUMN_COUNT + }; + + static constexpr QHeaderView::ResizeMode HeaderResizeModes[ModuleColumns::COLUMN_COUNT] = + { + QHeaderView::ResizeMode::ResizeToContents, + QHeaderView::ResizeMode::Stretch, + QHeaderView::ResizeMode::Stretch, + QHeaderView::ResizeMode::Stretch, + QHeaderView::ResizeMode::ResizeToContents, + QHeaderView::ResizeMode::ResizeToContents, + QHeaderView::ResizeMode::ResizeToContents, + }; + + explicit ModuleModel(DebugInterface& cpu, QObject* parent = nullptr); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + void refreshData(); + +private: + DebugInterface& m_cpu; +}; diff --git a/pcsx2-qt/Debugger/ModuleView.cpp b/pcsx2-qt/Debugger/ModuleView.cpp new file mode 100644 index 0000000000..cbb4867faf --- /dev/null +++ b/pcsx2-qt/Debugger/ModuleView.cpp @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "ModuleView.h" + +#include "QtUtils.h" + +#include +#include + +ModuleView::ModuleView(const DebuggerViewParameters& parameters) + : DebuggerView(parameters, MONOSPACE_FONT) + , m_model(new ModuleModel(cpu())) +{ + m_ui.setupUi(this); + m_ui.moduleList->setModel(m_model); + + m_ui.moduleList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_ui.moduleList, &QTableView::customContextMenuRequested, this, &ModuleView::openContextMenu); + connect(m_ui.moduleList, &QTableView::doubleClicked, this, &ModuleView::onDoubleClick); + + for (std::size_t i = 0; auto mode : ModuleModel::HeaderResizeModes) + { + m_ui.moduleList->horizontalHeader()->setSectionResizeMode(i, mode); + i++; + } + + receiveEvent([this](const DebuggerEvents::VMUpdate& event) -> bool { + m_model->refreshData(); + return true; + }); +} + +void ModuleView::openContextMenu(QPoint pos) +{ + if (!m_ui.moduleList->selectionModel()->hasSelection()) + return; + + QMenu* menu = new QMenu(m_ui.moduleList); + menu->setAttribute(Qt::WA_DeleteOnClose); + + QAction* copy = menu->addAction(tr("Copy")); + connect(copy, &QAction::triggered, [this]() { + const QItemSelectionModel* selection_model = m_ui.moduleList->selectionModel(); + if (!selection_model->hasSelection()) + return; + + QGuiApplication::clipboard()->setText(m_model->data(selection_model->currentIndex()).toString()); + }); + + menu->addSeparator(); + + QAction* copy_all_as_csv = menu->addAction(tr("Copy all as CSV")); + connect(copy_all_as_csv, &QAction::triggered, [this]() { + QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.moduleList->model())); + }); + + menu->popup(m_ui.moduleList->viewport()->mapToGlobal(pos)); +} + +void ModuleView::onDoubleClick(const QModelIndex& index) +{ + switch (index.column()) + { + case ModuleModel::ModuleColumns::ENTRY: + { + goToInDisassembler(m_model->data(index, Qt::UserRole).toUInt(), true); + break; + } + case ModuleModel::ModuleColumns::GP: + { + goToInMemoryView(m_model->data(index, Qt::UserRole).toUInt(), true); + break; + } + case ModuleModel::ModuleColumns::TEXT_SECTION: + { + goToInDisassembler(m_model->data(index, Qt::UserRole).toUInt(), true); + break; + } + case ModuleModel::ModuleColumns::DATA_SECTION: + { + goToInMemoryView(m_model->data(index, Qt::UserRole).toUInt(), true); + break; + } + case ModuleModel::ModuleColumns::BSS_SECTION: + { + auto data = m_model->data(index, Qt::UserRole).toUInt(); + if (data) + { + goToInMemoryView(data, true); + } + break; + } + default: + { + break; + } + } +} diff --git a/pcsx2-qt/Debugger/ModuleView.h b/pcsx2-qt/Debugger/ModuleView.h new file mode 100644 index 0000000000..7ca48bf3b7 --- /dev/null +++ b/pcsx2-qt/Debugger/ModuleView.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "ui_ModuleView.h" + +#include "DebuggerView.h" +#include "ModuleModel.h" + +#include + +class ModuleView final : public DebuggerView +{ + Q_OBJECT + +public: + ModuleView(const DebuggerViewParameters& parameters); + + void openContextMenu(QPoint pos); + void onDoubleClick(const QModelIndex& index); + +private: + Ui::ModuleView m_ui; + + ModuleModel* m_model; +}; diff --git a/pcsx2-qt/Debugger/ModuleView.ui b/pcsx2-qt/Debugger/ModuleView.ui new file mode 100644 index 0000000000..8119858831 --- /dev/null +++ b/pcsx2-qt/Debugger/ModuleView.ui @@ -0,0 +1,39 @@ + + + ModuleView + + + + 0 + 0 + 400 + 300 + + + + Modules + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index 7dc9a2866e..b2344f8be0 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -121,6 +121,8 @@ + + @@ -235,6 +237,8 @@ + + @@ -309,6 +313,8 @@ + + @@ -368,6 +374,7 @@ + diff --git a/pcsx2/DebugTools/BiosDebugData.cpp b/pcsx2/DebugTools/BiosDebugData.cpp index 9572d616f8..2de87b1b4b 100644 --- a/pcsx2/DebugTools/BiosDebugData.cpp +++ b/pcsx2/DebugTools/BiosDebugData.cpp @@ -79,7 +79,16 @@ std::vector getIOPModules() { IopMod mod; - mod.name = iopMemReadString(iopMemRead32(maddr + 4)); + u32 nstr = iopMemRead32(maddr + 4); + if (nstr) + { + mod.name = iopMemReadString(iopMemRead32(maddr + 4)); + } + else + { + mod.name = "(NULL)"; + } + mod.version = iopMemRead16(maddr + 8); mod.entry = iopMemRead32(maddr + 0x10); mod.gp = iopMemRead32(maddr + 0x14); diff --git a/pcsx2/DebugTools/DebugInterface.cpp b/pcsx2/DebugTools/DebugInterface.cpp index 2792e34dc8..0c6fffd3ab 100644 --- a/pcsx2/DebugTools/DebugInterface.cpp +++ b/pcsx2/DebugTools/DebugInterface.cpp @@ -734,6 +734,11 @@ std::vector> R5900DebugInterface::GetThreadList() co return getEEThreads(); } +std::vector R5900DebugInterface::GetModuleList() const +{ + return {}; +} + // // R3000DebugInterface // @@ -1062,6 +1067,11 @@ std::vector> R3000DebugInterface::GetThreadList() co return getIOPThreads(); } +std::vector R3000DebugInterface::GetModuleList() const +{ + return getIOPModules(); +} + ElfMemoryReader::ElfMemoryReader(const ccc::ElfFile& elf) : m_elf(elf) { diff --git a/pcsx2/DebugTools/DebugInterface.h b/pcsx2/DebugTools/DebugInterface.h index e0097c498e..690bb59a96 100644 --- a/pcsx2/DebugTools/DebugInterface.h +++ b/pcsx2/DebugTools/DebugInterface.h @@ -90,6 +90,7 @@ public: virtual SymbolGuardian& GetSymbolGuardian() const = 0; virtual SymbolImporter* GetSymbolImporter() const = 0; virtual std::vector> GetThreadList() const = 0; + virtual std::vector GetModuleList() const = 0; bool isAlive(); bool isCpuPaused(); @@ -151,6 +152,7 @@ public: SymbolGuardian& GetSymbolGuardian() const override; SymbolImporter* GetSymbolImporter() const override; std::vector> GetThreadList() const override; + std::vector GetModuleList() const override; std::string disasm(u32 address, bool simplify) override; bool isValidAddress(u32 address) override; @@ -194,6 +196,7 @@ public: SymbolGuardian& GetSymbolGuardian() const override; SymbolImporter* GetSymbolImporter() const override; std::vector> GetThreadList() const override; + std::vector GetModuleList() const override; std::string disasm(u32 address, bool simplify) override; bool isValidAddress(u32 address) override;