From 79dbc272b87cc8255fe5a78069c84eb1029c10a3 Mon Sep 17 00:00:00 2001 From: chaoticgd <43898262+chaoticgd@users.noreply.github.com> Date: Mon, 26 Aug 2024 21:57:31 +0100 Subject: [PATCH] Debugger: Add symbol tree widgets for functions and variables This adds three new tabs in the debugger: The Globals tab, the Locals tab and the Parameters tab. In addition, it rewrites the Functions tab. All four of these tabs use the new symbol tree widgets and the associated model. This allows the user the inspect complex data structures in memory with full type information. Lastly, new dialogs have been added for creating symbols. --- pcsx2-qt/CMakeLists.txt | 16 + pcsx2-qt/Debugger/CpuWidget.cpp | 50 + pcsx2-qt/Debugger/CpuWidget.h | 8 + pcsx2-qt/Debugger/CpuWidget.ui | 705 +++++------- pcsx2-qt/Debugger/DisassemblyWidget.cpp | 8 + .../Debugger/SymbolTree/NewSymbolDialog.ui | 314 +++++ .../Debugger/SymbolTree/NewSymbolDialogs.cpp | 652 +++++++++++ .../Debugger/SymbolTree/NewSymbolDialogs.h | 156 +++ .../SymbolTree/SymbolTreeDelegates.cpp | 475 ++++++++ .../Debugger/SymbolTree/SymbolTreeDelegates.h | 65 ++ .../SymbolTree/SymbolTreeLocation.cpp | 222 ++++ .../Debugger/SymbolTree/SymbolTreeLocation.h | 46 + .../Debugger/SymbolTree/SymbolTreeModel.cpp | 597 ++++++++++ .../Debugger/SymbolTree/SymbolTreeModel.h | 84 ++ .../Debugger/SymbolTree/SymbolTreeNode.cpp | 555 +++++++++ pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.h | 85 ++ .../Debugger/SymbolTree/SymbolTreeWidget.ui | 86 ++ .../Debugger/SymbolTree/SymbolTreeWidgets.cpp | 1025 +++++++++++++++++ .../Debugger/SymbolTree/SymbolTreeWidgets.h | 213 ++++ pcsx2-qt/Debugger/SymbolTree/TypeString.cpp | 162 +++ pcsx2-qt/Debugger/SymbolTree/TypeString.h | 20 + pcsx2-qt/pcsx2-qt.vcxproj | 25 +- pcsx2-qt/pcsx2-qt.vcxproj.filters | 51 + 23 files changed, 5226 insertions(+), 394 deletions(-) create mode 100644 pcsx2-qt/Debugger/SymbolTree/NewSymbolDialog.ui create mode 100644 pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.cpp create mode 100644 pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.h create mode 100644 pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.cpp create mode 100644 pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.h create mode 100644 pcsx2-qt/Debugger/SymbolTree/SymbolTreeLocation.cpp create mode 100644 pcsx2-qt/Debugger/SymbolTree/SymbolTreeLocation.h create mode 100644 pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.cpp create mode 100644 pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.h create mode 100644 pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.cpp create mode 100644 pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.h create mode 100644 pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidget.ui create mode 100644 pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp create mode 100644 pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.h create mode 100644 pcsx2-qt/Debugger/SymbolTree/TypeString.cpp create mode 100644 pcsx2-qt/Debugger/SymbolTree/TypeString.h diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index 069834ed11..36dce5b072 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -180,6 +180,22 @@ target_sources(pcsx2-qt PRIVATE Debugger/Models/StackModel.h Debugger/Models/SavedAddressesModel.cpp Debugger/Models/SavedAddressesModel.h + Debugger/SymbolTree/NewSymbolDialogs.cpp + Debugger/SymbolTree/NewSymbolDialogs.h + Debugger/SymbolTree/NewSymbolDialog.ui + Debugger/SymbolTree/SymbolTreeLocation.cpp + Debugger/SymbolTree/SymbolTreeLocation.h + Debugger/SymbolTree/SymbolTreeModel.cpp + Debugger/SymbolTree/SymbolTreeModel.h + Debugger/SymbolTree/SymbolTreeNode.cpp + Debugger/SymbolTree/SymbolTreeNode.h + Debugger/SymbolTree/SymbolTreeDelegates.cpp + Debugger/SymbolTree/SymbolTreeDelegates.h + Debugger/SymbolTree/SymbolTreeWidgets.cpp + Debugger/SymbolTree/SymbolTreeWidgets.h + Debugger/SymbolTree/SymbolTreeWidget.ui + Debugger/SymbolTree/TypeString.cpp + Debugger/SymbolTree/TypeString.h Tools/InputRecording/NewInputRecordingDlg.cpp Tools/InputRecording/NewInputRecordingDlg.h Tools/InputRecording/NewInputRecordingDlg.ui diff --git a/pcsx2-qt/Debugger/CpuWidget.cpp b/pcsx2-qt/Debugger/CpuWidget.cpp index 584ab0ff02..ac5749b067 100644 --- a/pcsx2-qt/Debugger/CpuWidget.cpp +++ b/pcsx2-qt/Debugger/CpuWidget.cpp @@ -117,6 +117,8 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu) savedAddressesTableView->resizeColumnToContents(topLeft.column()); }); + setupSymbolTrees(); + DebuggerSettingsManager::loadGameSettings(&m_bpModel); DebuggerSettingsManager::loadGameSettings(&m_savedAddressesModel); @@ -135,6 +137,46 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu) CpuWidget::~CpuWidget() = default; +void CpuWidget::setupSymbolTrees() +{ + m_ui.tabFunctions->setLayout(new QVBoxLayout()); + m_ui.tabGlobalVariables->setLayout(new QVBoxLayout()); + m_ui.tabLocalVariables->setLayout(new QVBoxLayout()); + m_ui.tabParameterVariables->setLayout(new QVBoxLayout()); + + m_ui.tabFunctions->layout()->setContentsMargins(0, 0, 0, 0); + m_ui.tabGlobalVariables->layout()->setContentsMargins(0, 0, 0, 0); + m_ui.tabLocalVariables->layout()->setContentsMargins(0, 0, 0, 0); + m_ui.tabParameterVariables->layout()->setContentsMargins(0, 0, 0, 0); + + m_function_tree = new FunctionTreeWidget(m_cpu); + m_global_variable_tree = new GlobalVariableTreeWidget(m_cpu); + m_local_variable_tree = new LocalVariableTreeWidget(m_cpu); + m_parameter_variable_tree = new ParameterVariableTreeWidget(m_cpu); + + m_ui.tabFunctions->layout()->addWidget(m_function_tree); + m_ui.tabGlobalVariables->layout()->addWidget(m_global_variable_tree); + m_ui.tabLocalVariables->layout()->addWidget(m_local_variable_tree); + m_ui.tabParameterVariables->layout()->addWidget(m_parameter_variable_tree); + + connect(m_ui.tabWidgetRegFunc, &QTabWidget::currentChanged, m_function_tree, &SymbolTreeWidget::updateModel); + connect(m_ui.tabWidget, &QTabWidget::currentChanged, m_global_variable_tree, &SymbolTreeWidget::updateModel); + connect(m_ui.tabWidget, &QTabWidget::currentChanged, m_local_variable_tree, &SymbolTreeWidget::updateModel); + + connect(m_function_tree, &SymbolTreeWidget::goToInDisassembly, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus); + connect(m_global_variable_tree, &SymbolTreeWidget::goToInDisassembly, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus); + connect(m_local_variable_tree, &SymbolTreeWidget::goToInDisassembly, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus); + connect(m_parameter_variable_tree, &SymbolTreeWidget::goToInDisassembly, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus); + + connect(m_function_tree, &SymbolTreeWidget::goToInMemoryView, this, &CpuWidget::onGotoInMemory); + connect(m_global_variable_tree, &SymbolTreeWidget::goToInMemoryView, this, &CpuWidget::onGotoInMemory); + connect(m_local_variable_tree, &SymbolTreeWidget::goToInMemoryView, this, &CpuWidget::onGotoInMemory); + connect(m_parameter_variable_tree, &SymbolTreeWidget::goToInMemoryView, this, &CpuWidget::onGotoInMemory); + + connect(m_function_tree, &SymbolTreeWidget::nameColumnClicked, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus); + connect(m_function_tree, &SymbolTreeWidget::locationColumnClicked, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddressAndSetFocus); +} + void CpuWidget::refreshDebugger() { if (!m_cpu.isAlive()) @@ -144,6 +186,11 @@ void CpuWidget::refreshDebugger() m_ui.disassemblyWidget->update(); m_ui.memoryviewWidget->update(); m_ui.memorySearchWidget->update(); + + m_function_tree->updateModel(); + m_global_variable_tree->updateModel(); + m_local_variable_tree->updateModel(); + m_parameter_variable_tree->updateModel(); } void CpuWidget::reloadCPUWidgets() @@ -154,6 +201,9 @@ void CpuWidget::reloadCPUWidgets() m_ui.registerWidget->update(); m_ui.disassemblyWidget->update(); m_ui.memoryviewWidget->update(); + + m_local_variable_tree->reset(); + m_parameter_variable_tree->reset(); } void CpuWidget::paintEvent(QPaintEvent* event) diff --git a/pcsx2-qt/Debugger/CpuWidget.h b/pcsx2-qt/Debugger/CpuWidget.h index a24e28fc22..f5033984a1 100644 --- a/pcsx2-qt/Debugger/CpuWidget.h +++ b/pcsx2-qt/Debugger/CpuWidget.h @@ -11,6 +11,7 @@ #include "Models/ThreadModel.h" #include "Models/StackModel.h" #include "Models/SavedAddressesModel.h" +#include "Debugger/SymbolTree/SymbolTreeWidgets.h" #include "QtHost.h" #include @@ -70,6 +71,8 @@ public slots: void saveSavedAddressesToDebuggerSettings(); private: + void setupSymbolTrees(); + std::vector m_registerTableViews; QMenu* m_stacklistContextMenu = 0; @@ -86,4 +89,9 @@ private: QSortFilterProxyModel m_threadProxyModel; StackModel m_stackModel; SavedAddressesModel m_savedAddressesModel; + + FunctionTreeWidget* m_function_tree = nullptr; + GlobalVariableTreeWidget* m_global_variable_tree = nullptr; + LocalVariableTreeWidget* m_local_variable_tree = nullptr; + ParameterVariableTreeWidget* m_parameter_variable_tree = nullptr; }; diff --git a/pcsx2-qt/Debugger/CpuWidget.ui b/pcsx2-qt/Debugger/CpuWidget.ui index c5ad30f83c..587fd31449 100644 --- a/pcsx2-qt/Debugger/CpuWidget.ui +++ b/pcsx2-qt/Debugger/CpuWidget.ui @@ -21,280 +21,39 @@ - - - - - - - - - - 320 - 350 - - - - 0 - - - - Registers - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 320 - 300 - - - - - 16777215 - 595 - - - - - Monospace - - - - Qt::StrongFocus - - - Qt::CustomContextMenu - - - true - - - - - - - - Functions - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - Qt::CustomContextMenu - - - 8 - - - true - - - 3 - - - - Module - - - - - Version - - - - - Count - - - - - - - - Qt::CustomContextMenu - - - - - - - - - Refresh - - - - - - - Filter - - - - - - - - - - Memory Search - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - Monospace - - - - Qt::StrongFocus - - - Qt::CustomContextMenu - - - true - - - - - - - - - - - - 0 - 1 - - - - - 320 - 350 - - - - - 16777215 - 595 - - - - - Monospace - - - - Qt::StrongFocus - - - Qt::CustomContextMenu - - - true - - - - - - - + + + + + Qt::Vertical + + + false + + + + Qt::Horizontal + + + false + + - 150 - 0 + 100 + 100 - - - 16777215 - 215 - - - - QTabWidget::North - 0 - - - - 0 - 0 - - + - Memory + Registers - + - 6 + 0 0 @@ -309,23 +68,17 @@ 0 - + - + 0 0 - 250 - 145 - - - - - 16777215 - 16777215 + 320 + 100 @@ -333,6 +86,9 @@ Monospace + + Qt::StrongFocus + Qt::CustomContextMenu @@ -343,43 +99,16 @@ - + - Breakpoints + Functions - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::CustomContextMenu - - - Qt::ScrollBarAlwaysOff - - - Qt::NoPen - - - - - + - Threads + Memory Search - + 0 @@ -396,102 +125,292 @@ 0 - + + + + 0 + 0 + + + + + Monospace + + + + Qt::StrongFocus + Qt::CustomContextMenu - - QAbstractItemView::SingleSelection - - - Qt::NoPen - - - false - - - - - - - - Active Call Stack - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::CustomContextMenu - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustToContentsOnFirstShow - - - QAbstractItemView::SingleSelection - - - Qt::NoPen - - - false - - - - - - - - Saved Addresses - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::CustomContextMenu - - - Qt::ScrollBarAlwaysOff - - - Qt::NoPen + + true - - + + + + 0 + 1 + + + + + 100 + 100 + + + + + Monospace + + + + Qt::StrongFocus + + + Qt::CustomContextMenu + + + true + + + + + + + 150 + 0 + + + + QTabWidget::North + + + 0 + + + + + 0 + 0 + + + + Memory + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 250 + 100 + + + + + Monospace + + + + Qt::CustomContextMenu + + + true + + + + + + + + Breakpoints + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::CustomContextMenu + + + Qt::ScrollBarAlwaysOff + + + Qt::NoPen + + + + + + + + Threads + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::CustomContextMenu + + + QAbstractItemView::SingleSelection + + + Qt::NoPen + + + false + + + + + + + + Active Call Stack + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::CustomContextMenu + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContentsOnFirstShow + + + QAbstractItemView::SingleSelection + + + Qt::NoPen + + + false + + + + + + + + Saved Addresses + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::CustomContextMenu + + + Qt::ScrollBarAlwaysOff + + + Qt::NoPen + + + + + + + + Globals + + + + + Locals + + + + + Parameters + + + + diff --git a/pcsx2-qt/Debugger/DisassemblyWidget.cpp b/pcsx2-qt/Debugger/DisassemblyWidget.cpp index 615744fbfc..b9e41af245 100644 --- a/pcsx2-qt/Debugger/DisassemblyWidget.cpp +++ b/pcsx2-qt/Debugger/DisassemblyWidget.cpp @@ -15,6 +15,7 @@ #include #include #include +#include "SymbolTree/NewSymbolDialogs.h" using namespace QtUtils; @@ -181,6 +182,13 @@ void DisassemblyWidget::contextGoToAddress() void DisassemblyWidget::contextAddFunction() { + NewFunctionDialog* dialog = new NewFunctionDialog(*m_cpu, this); + dialog->setName(QString("func_%1").arg(m_selectedAddressStart, 8, 16, QChar('0'))); + dialog->setAddress(m_selectedAddressStart); + if (m_selectedAddressEnd != m_selectedAddressStart) + dialog->setCustomSize(m_selectedAddressEnd - m_selectedAddressStart + 4); + if (dialog->exec() == QDialog::Accepted) + update(); } void DisassemblyWidget::contextCopyFunctionName() diff --git a/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialog.ui b/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialog.ui new file mode 100644 index 0000000000..1030d8c79d --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialog.ui @@ -0,0 +1,314 @@ + + + NewSymbolDialog + + + + 0 + 0 + 600 + 400 + + + + + 0 + 0 + + + + + 300 + 200 + + + + + 600 + 400 + + + + Dialog + + + + + + + + + + + Name + + + + + + + + + + Address + + + + + + + + + + Register + + + + + + + + + + Stack Pointer Offset + + + + + + + 268435456 + + + + + + + Size + + + + + + + + + + + + true + + + sizeButtonGroup + + + + + + + + + + false + + + sizeButtonGroup + + + + + + + + + + 0 + 0 + + + + Custom + + + sizeButtonGroup + + + + + + + false + + + false + + + 268435456 + + + 4 + + + + + + + + + + + Existing Functions + + + + + + + + + Shrink to avoid overlaps + + + true + + + existingFunctionsButtonGroup + + + + + + + Do not modify + + + existingFunctionsButtonGroup + + + + + + + + + Type + + + + + + + + + + Function + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + + + + + + + + + 0 + 0 + + + + color: red + + + + + + true + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + QTabBar + QWidget +
QtWidgets/QTabBar
+ 1 +
+
+ + + + buttonBox + accepted() + NewSymbolDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NewSymbolDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + + + + +
diff --git a/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.cpp b/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.cpp new file mode 100644 index 0000000000..924cf548cb --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.cpp @@ -0,0 +1,652 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "NewSymbolDialogs.h" + +#include +#include +#include +#include + +#include "TypeString.h" + +NewSymbolDialog::NewSymbolDialog(u32 flags, u32 alignment, DebugInterface& cpu, QWidget* parent) + : QDialog(parent) + , m_cpu(cpu) + , m_alignment(alignment) +{ + m_ui.setupUi(this); + + connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &NewSymbolDialog::createSymbol); + connect(m_ui.storageTabBar, &QTabBar::currentChanged, this, &NewSymbolDialog::onStorageTabChanged); + + if (flags & GLOBAL_STORAGE) + { + int tab = m_ui.storageTabBar->addTab(tr("Global")); + m_ui.storageTabBar->setTabData(tab, GLOBAL_STORAGE); + } + + if (flags & REGISTER_STORAGE) + { + int tab = m_ui.storageTabBar->addTab(tr("Register")); + m_ui.storageTabBar->setTabData(tab, REGISTER_STORAGE); + + setupRegisterField(); + } + + if (flags & STACK_STORAGE) + { + int tab = m_ui.storageTabBar->addTab(tr("Stack")); + m_ui.storageTabBar->setTabData(tab, STACK_STORAGE); + } + + if (m_ui.storageTabBar->count() == 1) + m_ui.storageTabBar->hide(); + + m_ui.form->setRowVisible(Row::SIZE, flags & SIZE_FIELD); + m_ui.form->setRowVisible(Row::EXISTING_FUNCTIONS, flags & EXISTING_FUNCTIONS_FIELD); + m_ui.form->setRowVisible(Row::TYPE, flags & TYPE_FIELD); + m_ui.form->setRowVisible(Row::FUNCTION, flags & FUNCTION_FIELD); + + if (flags & SIZE_FIELD) + { + setupSizeField(); + updateSizeField(); + } + + if (flags & FUNCTION_FIELD) + setupFunctionField(); + + connectInputWidgets(); + onStorageTabChanged(0); + adjustSize(); +} + +void NewSymbolDialog::setName(QString name) +{ + m_ui.nameLineEdit->setText(name); +} + +void NewSymbolDialog::setAddress(u32 address) +{ + m_ui.addressLineEdit->setText(QString::number(address, 16)); +} + +void NewSymbolDialog::setCustomSize(u32 size) +{ + m_ui.customSizeRadioButton->setChecked(true); + m_ui.customSizeSpinBox->setValue(size); +} + +void NewSymbolDialog::setupRegisterField() +{ + m_ui.registerComboBox->clear(); + for (int i = 0; i < m_cpu.getRegisterCount(0); i++) + m_ui.registerComboBox->addItem(m_cpu.getRegisterName(0, i)); +} + +void NewSymbolDialog::setupSizeField() +{ + connect(m_ui.customSizeRadioButton, &QRadioButton::toggled, m_ui.customSizeSpinBox, &QSpinBox::setEnabled); + connect(m_ui.addressLineEdit, &QLineEdit::textChanged, this, &NewSymbolDialog::updateSizeField); +} + +void NewSymbolDialog::setupFunctionField() +{ + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + const ccc::Function* default_function = database.functions.symbol_overlapping_address(m_cpu.getPC()); + + for (const ccc::Function& function : database.functions) + { + QString name = QString::fromStdString(function.name()); + name.truncate(64); + m_ui.functionComboBox->addItem(name); + m_functions.emplace_back(function.handle()); + + if (default_function && function.handle() == default_function->handle()) + m_ui.functionComboBox->setCurrentIndex(m_ui.functionComboBox->count() - 1); + } + }); +} + +void NewSymbolDialog::connectInputWidgets() +{ + QMetaMethod parse_user_input = metaObject()->method(metaObject()->indexOfSlot("parseUserInput()")); + for (QObject* child : children()) + { + QWidget* widget = qobject_cast(child); + if (!widget) + continue; + + QMetaProperty property = widget->metaObject()->userProperty(); + if (!property.isValid() || !property.hasNotifySignal()) + continue; + + connect(widget, property.notifySignal(), this, parse_user_input); + } +} + +void NewSymbolDialog::updateErrorMessage(QString error_message) +{ + m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(error_message.isEmpty()); + m_ui.errorMessage->setText(error_message); +} + +NewSymbolDialog::FunctionSizeType NewSymbolDialog::functionSizeType() const +{ + if (m_ui.fillExistingFunctionRadioButton->isChecked()) + return FILL_EXISTING_FUNCTION; + + if (m_ui.fillEmptySpaceRadioButton->isChecked()) + return FILL_EMPTY_SPACE; + + return CUSTOM_SIZE; +} + +void NewSymbolDialog::updateSizeField() +{ + bool ok; + u32 address = m_ui.addressLineEdit->text().toUInt(&ok, 16); + if (ok) + { + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + std::optional fill_existing_function_size = fillExistingFunctionSize(address, database); + if (fill_existing_function_size.has_value()) + m_ui.fillExistingFunctionRadioButton->setText( + tr("Fill existing function (%1 bytes)").arg(*fill_existing_function_size)); + else + m_ui.fillExistingFunctionRadioButton->setText( + tr("Fill existing function (none found)")); + m_ui.fillExistingFunctionRadioButton->setEnabled(fill_existing_function_size.has_value()); + + std::optional fill_empty_space_size = fillEmptySpaceSize(address, database); + if (fill_empty_space_size.has_value()) + m_ui.fillEmptySpaceRadioButton->setText( + tr("Fill space (%1 bytes)").arg(*fill_empty_space_size)); + else + m_ui.fillEmptySpaceRadioButton->setText(tr("Fill space (no next symbol)")); + m_ui.fillEmptySpaceRadioButton->setEnabled(fill_empty_space_size.has_value()); + }); + } + else + { + // Add some padding to the end of the radio button text so that the + // layout engine knows we need some more space for the size. + QString padding(16, ' '); + m_ui.fillExistingFunctionRadioButton->setText(tr("Fill existing function").append(padding)); + m_ui.fillEmptySpaceRadioButton->setText(tr("Fill space").append(padding)); + } +} + +std::optional NewSymbolDialog::fillExistingFunctionSize(u32 address, const ccc::SymbolDatabase& database) +{ + const ccc::Function* existing_function = database.functions.symbol_overlapping_address(address); + if (!existing_function) + return std::nullopt; + + return existing_function->address_range().high.value - address; +} + +std::optional NewSymbolDialog::fillEmptySpaceSize(u32 address, const ccc::SymbolDatabase& database) +{ + const ccc::Symbol* next_symbol = database.symbol_after_address( + address, ccc::FUNCTION | ccc::GLOBAL_VARIABLE | ccc::LOCAL_VARIABLE); + if (!next_symbol) + return std::nullopt; + + return next_symbol->address().value - address; +} + +u32 NewSymbolDialog::storageType() const +{ + return m_ui.storageTabBar->tabData(m_ui.storageTabBar->currentIndex()).toUInt(); +} + +void NewSymbolDialog::onStorageTabChanged(int index) +{ + u32 storage = m_ui.storageTabBar->tabData(index).toUInt(); + + m_ui.form->setRowVisible(Row::ADDRESS, storage == GLOBAL_STORAGE); + m_ui.form->setRowVisible(Row::REGISTER, storage == REGISTER_STORAGE); + m_ui.form->setRowVisible(Row::STACK_POINTER_OFFSET, storage == STACK_STORAGE); + + QTimer::singleShot(0, this, [&]() { + parseUserInput(); + }); +} + +std::string NewSymbolDialog::parseName(QString& error_message) +{ + std::string name = m_ui.nameLineEdit->text().toStdString(); + if (name.empty()) + error_message = tr("Name is empty."); + + return name; +} + +u32 NewSymbolDialog::parseAddress(QString& error_message) +{ + bool ok; + u32 address = m_ui.addressLineEdit->text().toUInt(&ok, 16); + if (!ok) + error_message = tr("Address is not valid."); + + if (address % m_alignment != 0) + error_message = tr("Address is not aligned."); + + return address; +} + +// ***************************************************************************** + +NewFunctionDialog::NewFunctionDialog(DebugInterface& cpu, QWidget* parent) + : NewSymbolDialog(GLOBAL_STORAGE | SIZE_FIELD | EXISTING_FUNCTIONS_FIELD, 4, cpu, parent) +{ + setWindowTitle("New Function"); + + m_ui.customSizeSpinBox->setValue(8); +} + +bool NewFunctionDialog::parseUserInput() +{ + QString error_message; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + m_name = parseName(error_message); + if (!error_message.isEmpty()) + return; + + m_address = parseAddress(error_message); + if (!error_message.isEmpty()) + return; + + m_size = 0; + switch (functionSizeType()) + { + case FILL_EXISTING_FUNCTION: + { + std::optional fill_existing_function_size = fillExistingFunctionSize(m_address, database); + if (!fill_existing_function_size.has_value()) + { + error_message = tr("No existing function found."); + return; + } + + m_size = *fill_existing_function_size; + + break; + } + case FILL_EMPTY_SPACE: + { + std::optional fill_space_size = fillEmptySpaceSize(m_address, database); + if (!fill_space_size.has_value()) + { + error_message = tr("No next symbol found."); + return; + } + + m_size = *fill_space_size; + + break; + } + case CUSTOM_SIZE: + { + m_size = m_ui.customSizeSpinBox->value(); + break; + } + } + + if (m_size == 0 || m_size > 256 * 1024 * 1024) + { + error_message = tr("Size is invalid."); + return; + } + + if (m_size % 4 != 0) + { + error_message = tr("Size is not a multiple of 4."); + return; + } + + // Handle an existing function if it exists. + const ccc::Function* existing_function = database.functions.symbol_overlapping_address(m_address); + m_existing_function = ccc::FunctionHandle(); + if (existing_function) + { + if (existing_function->address().value == m_address) + { + error_message = tr("A function already exists at that address."); + return; + } + + if (m_ui.shrinkExistingRadioButton->isChecked()) + { + m_new_existing_function_size = m_address - existing_function->address().value; + m_existing_function = existing_function->handle(); + } + } + }); + + updateErrorMessage(error_message); + return error_message.isEmpty(); +} + +void NewFunctionDialog::createSymbol() +{ + if (!parseUserInput()) + return; + + QString error_message; + m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { + ccc::Result source = database.get_symbol_source("User-defined"); + if (!source.success()) + { + error_message = tr("Cannot create symbol source."); + return; + } + + ccc::Result function = database.functions.create_symbol(std::move(m_name), m_address, *source, nullptr); + if (!function.success()) + { + error_message = tr("Cannot create symbol."); + return; + } + + (*function)->set_size(m_size); + + ccc::Function* existing_function = database.functions.symbol_from_handle(m_existing_function); + if (existing_function) + existing_function->set_size(m_new_existing_function_size); + }); + + if (!error_message.isEmpty()) + QMessageBox::warning(this, tr("Cannot Create Function"), error_message); +} + +// ***************************************************************************** + +NewGlobalVariableDialog::NewGlobalVariableDialog(DebugInterface& cpu, QWidget* parent) + : NewSymbolDialog(GLOBAL_STORAGE | TYPE_FIELD, 1, cpu, parent) +{ + setWindowTitle("New Global Variable"); +} + +bool NewGlobalVariableDialog::parseUserInput() +{ + QString error_message; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + m_name = parseName(error_message); + if (!error_message.isEmpty()) + return; + + m_address = parseAddress(error_message); + if (!error_message.isEmpty()) + return; + + m_type = stringToType(m_ui.typeLineEdit->text().toStdString(), database, error_message); + if (!error_message.isEmpty()) + return; + }); + + updateErrorMessage(error_message); + return error_message.isEmpty(); +} + +void NewGlobalVariableDialog::createSymbol() +{ + if (!parseUserInput()) + return; + + QString error_message; + m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { + ccc::Result source = database.get_symbol_source("User-defined"); + if (!source.success()) + { + error_message = tr("Cannot create symbol source."); + return; + } + + ccc::Result global_variable = database.global_variables.create_symbol(std::move(m_name), m_address, *source, nullptr); + if (!global_variable.success()) + { + error_message = tr("Cannot create symbol."); + return; + } + + (*global_variable)->set_type(std::move(m_type)); + }); + + if (!error_message.isEmpty()) + QMessageBox::warning(this, tr("Cannot Create Global Variable"), error_message); +} + +// ***************************************************************************** + +NewLocalVariableDialog::NewLocalVariableDialog(DebugInterface& cpu, QWidget* parent) + : NewSymbolDialog(GLOBAL_STORAGE | REGISTER_STORAGE | STACK_STORAGE | TYPE_FIELD | FUNCTION_FIELD, 1, cpu, parent) +{ + setWindowTitle("New Local Variable"); +} + +bool NewLocalVariableDialog::parseUserInput() +{ + QString error_message; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + m_name = parseName(error_message); + if (!error_message.isEmpty()) + return; + + int function_index = m_ui.functionComboBox->currentIndex(); + if (function_index > 0 && function_index < (int)m_functions.size()) + m_function = m_functions[m_ui.functionComboBox->currentIndex()]; + else + m_function = ccc::FunctionHandle(); + + const ccc::Function* function = database.functions.symbol_from_handle(m_function); + if (!function) + { + error_message = tr("Invalid function."); + return; + } + + switch (storageType()) + { + case GLOBAL_STORAGE: + { + m_storage.emplace(); + + m_address = parseAddress(error_message); + if (!error_message.isEmpty()) + return; + + break; + } + case REGISTER_STORAGE: + { + ccc::RegisterStorage& register_storage = m_storage.emplace(); + register_storage.dbx_register_number = m_ui.registerComboBox->currentIndex(); + break; + } + case STACK_STORAGE: + { + ccc::StackStorage& stack_storage = m_storage.emplace(); + stack_storage.stack_pointer_offset = m_ui.stackPointerOffsetSpinBox->value(); + + // Convert to caller sp relative. + if (std::optional stack_frame_size = m_cpu.getStackFrameSize(*function)) + stack_storage.stack_pointer_offset -= *stack_frame_size; + else + { + error_message = tr("Cannot determine stack frame size of selected function."); + return; + } + + break; + } + } + + std::string type_string = m_ui.typeLineEdit->text().toStdString(); + m_type = stringToType(type_string, database, error_message); + if (!error_message.isEmpty()) + return; + }); + + updateErrorMessage(error_message); + return error_message.isEmpty(); +} + +void NewLocalVariableDialog::createSymbol() +{ + if (!parseUserInput()) + return; + + QString error_message; + m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { + ccc::Function* function = database.functions.symbol_from_handle(m_function); + if (!function) + { + error_message = tr("Invalid function."); + return; + } + + ccc::Result source = database.get_symbol_source("User-defined"); + if (!source.success()) + { + error_message = tr("Cannot create symbol source."); + return; + } + + ccc::Result local_variable = + database.local_variables.create_symbol(std::move(m_name), m_address, *source, nullptr); + if (!local_variable.success()) + { + error_message = tr("Cannot create symbol."); + return; + } + + (*local_variable)->set_type(std::move(m_type)); + (*local_variable)->storage = m_storage; + + std::vector local_variables; + if (function->local_variables().has_value()) + local_variables = *function->local_variables(); + local_variables.emplace_back((*local_variable)->handle()); + function->set_local_variables(local_variables, database); + }); + + if (!error_message.isEmpty()) + QMessageBox::warning(this, tr("Cannot Create Local Variable"), error_message); +} + +// ***************************************************************************** + +NewParameterVariableDialog::NewParameterVariableDialog(DebugInterface& cpu, QWidget* parent) + : NewSymbolDialog(REGISTER_STORAGE | STACK_STORAGE | TYPE_FIELD | FUNCTION_FIELD, 1, cpu, parent) +{ + setWindowTitle("New Parameter Variable"); +} + +bool NewParameterVariableDialog::parseUserInput() +{ + QString error_message; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + m_name = parseName(error_message); + if (!error_message.isEmpty()) + return; + + int function_index = m_ui.functionComboBox->currentIndex(); + if (function_index > 0 && function_index < (int)m_functions.size()) + m_function = m_functions[m_ui.functionComboBox->currentIndex()]; + else + m_function = ccc::FunctionHandle(); + + const ccc::Function* function = database.functions.symbol_from_handle(m_function); + if (!function) + { + error_message = tr("Invalid function."); + return; + } + + std::variant storage; + switch (storageType()) + { + case GLOBAL_STORAGE: + { + error_message = tr("Invalid storage type."); + return; + } + case REGISTER_STORAGE: + { + ccc::RegisterStorage& register_storage = storage.emplace(); + register_storage.dbx_register_number = m_ui.registerComboBox->currentIndex(); + break; + } + case STACK_STORAGE: + { + ccc::StackStorage& stack_storage = storage.emplace(); + stack_storage.stack_pointer_offset = m_ui.stackPointerOffsetSpinBox->value(); + + // Convert to caller sp relative. + if (std::optional stack_frame_size = m_cpu.getStackFrameSize(*function)) + stack_storage.stack_pointer_offset -= *stack_frame_size; + else + { + error_message = tr("Cannot determine stack frame size of selected function."); + return; + } + + break; + } + } + + std::string type_string = m_ui.typeLineEdit->text().toStdString(); + m_type = stringToType(type_string, database, error_message); + if (!error_message.isEmpty()) + return; + }); + + updateErrorMessage(error_message); + return error_message.isEmpty(); +} + +void NewParameterVariableDialog::createSymbol() +{ + if (!parseUserInput()) + return; + + QString error_message; + m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { + ccc::Function* function = database.functions.symbol_from_handle(m_function); + if (!function) + { + error_message = tr("Invalid function."); + return; + } + + ccc::Result source = database.get_symbol_source("User-defined"); + if (!source.success()) + { + error_message = tr("Cannot create symbol source."); + return; + } + + ccc::Result parameter_variable = + database.parameter_variables.create_symbol(std::move(m_name), *source, nullptr); + if (!parameter_variable.success()) + { + error_message = tr("Cannot create symbol."); + return; + } + + (*parameter_variable)->set_type(std::move(m_type)); + (*parameter_variable)->storage = m_storage; + + std::vector parameter_variables; + if (function->parameter_variables().has_value()) + parameter_variables = *function->parameter_variables(); + parameter_variables.emplace_back((*parameter_variable)->handle()); + function->set_parameter_variables(parameter_variables, database); + }); + + if (!error_message.isEmpty()) + QMessageBox::warning(this, tr("Cannot Create Parameter Variable"), error_message); +} diff --git a/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.h b/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.h new file mode 100644 index 0000000000..aaa724f9ec --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.h @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include + +#include + +#include "DebugTools/DebugInterface.h" +#include "ui_NewSymbolDialog.h" + +// Base class for symbol creation dialogs. +class NewSymbolDialog : public QDialog +{ + Q_OBJECT + +public: + // Used to apply default settings. + void setName(QString name); + void setAddress(u32 address); + void setCustomSize(u32 size); + +protected: + explicit NewSymbolDialog(u32 flags, u32 alignment, DebugInterface& cpu, QWidget* parent = nullptr); + + enum Flags + { + GLOBAL_STORAGE = 1 << 0, + REGISTER_STORAGE = 1 << 1, + STACK_STORAGE = 1 << 2, + SIZE_FIELD = 1 << 3, + EXISTING_FUNCTIONS_FIELD = 1 << 4, + TYPE_FIELD = 1 << 5, + FUNCTION_FIELD = 1 << 6 + }; + + // Used for setting up row visibility. Keep in sync with the .ui file! + enum Row + { + NAME, + ADDRESS, + REGISTER, + STACK_POINTER_OFFSET, + SIZE, + EXISTING_FUNCTIONS, + TYPE, + FUNCTION + }; + +protected slots: + virtual bool parseUserInput() = 0; + +protected: + virtual void createSymbol() = 0; + + void setupRegisterField(); + void setupSizeField(); + void setupFunctionField(); + + void connectInputWidgets(); + void updateErrorMessage(QString error_message); + + enum FunctionSizeType + { + FILL_EXISTING_FUNCTION, + FILL_EMPTY_SPACE, + CUSTOM_SIZE + }; + + FunctionSizeType functionSizeType() const; + void updateSizeField(); + std::optional fillExistingFunctionSize(u32 address, const ccc::SymbolDatabase& database); + std::optional fillEmptySpaceSize(u32 address, const ccc::SymbolDatabase& database); + + u32 storageType() const; + void onStorageTabChanged(int index); + + std::string parseName(QString& error_message); + u32 parseAddress(QString& error_message); + + DebugInterface& m_cpu; + Ui::NewSymbolDialog m_ui; + +u32 m_alignment; + std::vector m_functions; +}; + +class NewFunctionDialog : public NewSymbolDialog +{ + Q_OBJECT + +public: + NewFunctionDialog(DebugInterface& cpu, QWidget* parent = nullptr); + +protected: + bool parseUserInput() override; + void createSymbol() override; + + std::string m_name; + u32 m_address = 0; + u32 m_size = 0; + ccc::FunctionHandle m_existing_function; + u32 m_new_existing_function_size = 0; +}; + +class NewGlobalVariableDialog : public NewSymbolDialog +{ + Q_OBJECT + +public: + NewGlobalVariableDialog(DebugInterface& cpu, QWidget* parent = nullptr); + +protected: + bool parseUserInput() override; + void createSymbol() override; + + std::string m_name; + u32 m_address; + std::unique_ptr m_type; +}; + +class NewLocalVariableDialog : public NewSymbolDialog +{ + Q_OBJECT + +public: + NewLocalVariableDialog(DebugInterface& cpu, QWidget* parent = nullptr); + +protected: + bool parseUserInput() override; + void createSymbol() override; + + std::string m_name; + std::variant m_storage; + u32 m_address = 0; + std::unique_ptr m_type; + ccc::FunctionHandle m_function; +}; + +class NewParameterVariableDialog : public NewSymbolDialog +{ + Q_OBJECT + +public: + NewParameterVariableDialog(DebugInterface& cpu, QWidget* parent = nullptr); + +protected: + bool parseUserInput() override; + void createSymbol() override; + + std::string m_name; + std::variant m_storage; + std::unique_ptr m_type; + ccc::FunctionHandle m_function; +}; diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.cpp b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.cpp new file mode 100644 index 0000000000..3d199351be --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.cpp @@ -0,0 +1,475 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: LGPL-3.0+ + +#include "SymbolTreeDelegates.h" + +#include +#include +#include +#include +#include +#include "Debugger/SymbolTree/SymbolTreeModel.h" +#include "Debugger/SymbolTree/TypeString.h" + +SymbolTreeValueDelegate::SymbolTreeValueDelegate( + DebugInterface& cpu, + QObject* parent) + : QStyledItemDelegate(parent) + , m_cpu(cpu) +{ +} + +QWidget* SymbolTreeValueDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + if (!index.isValid()) + return nullptr; + + const SymbolTreeModel* tree_model = qobject_cast(index.model()); + if (!tree_model) + return nullptr; + + SymbolTreeNode* node = tree_model->nodeFromIndex(index); + if (!node || !node->type.valid()) + return nullptr; + + QWidget* result = nullptr; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + const ccc::ast::Node* logical_type = node->type.lookup_node(database); + if (!logical_type) + return; + + const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first; + QVariant value = node->readValueAsVariant(physical_type, m_cpu, database); + + const ccc::ast::Node& type = *logical_type->physical_type(database).first; + switch (type.descriptor) + { + case ccc::ast::BUILTIN: + { + const ccc::ast::BuiltIn& builtIn = type.as(); + + switch (builtIn.bclass) + { + case ccc::ast::BuiltInClass::UNSIGNED_8: + case ccc::ast::BuiltInClass::UNQUALIFIED_8: + case ccc::ast::BuiltInClass::UNSIGNED_16: + case ccc::ast::BuiltInClass::UNSIGNED_32: + case ccc::ast::BuiltInClass::UNSIGNED_64: + { + QLineEdit* editor = new QLineEdit(parent); + editor->setText(QString::number(value.toULongLong())); + result = editor; + + break; + } + case ccc::ast::BuiltInClass::SIGNED_8: + case ccc::ast::BuiltInClass::SIGNED_16: + case ccc::ast::BuiltInClass::SIGNED_32: + case ccc::ast::BuiltInClass::SIGNED_64: + { + QLineEdit* editor = new QLineEdit(parent); + editor->setText(QString::number(value.toLongLong())); + result = editor; + + break; + } + case ccc::ast::BuiltInClass::BOOL_8: + { + QCheckBox* editor = new QCheckBox(parent); + editor->setChecked(value.toBool()); + result = editor; + + break; + } + case ccc::ast::BuiltInClass::FLOAT_32: + { + QLineEdit* editor = new QLineEdit(parent); + editor->setText(QString::number(value.toFloat())); + result = editor; + + break; + } + case ccc::ast::BuiltInClass::FLOAT_64: + { + QLineEdit* editor = new QLineEdit(parent); + editor->setText(QString::number(value.toDouble())); + result = editor; + + break; + } + default: + { + } + } + break; + } + case ccc::ast::ENUM: + { + const ccc::ast::Enum& enumeration = type.as(); + + QComboBox* combo_box = new QComboBox(parent); + for (s32 i = 0; i < (s32)enumeration.constants.size(); i++) + { + combo_box->addItem(QString::fromStdString(enumeration.constants[i].second)); + if (enumeration.constants[i].first == value.toInt()) + combo_box->setCurrentIndex(i); + } + connect(combo_box, &QComboBox::currentIndexChanged, this, &SymbolTreeValueDelegate::onComboBoxIndexChanged); + result = combo_box; + + break; + } + case ccc::ast::POINTER_OR_REFERENCE: + case ccc::ast::POINTER_TO_DATA_MEMBER: + { + QLineEdit* editor = new QLineEdit(parent); + editor->setText(QString::number(value.toULongLong(), 16)); + result = editor; + + break; + } + default: + { + } + } + }); + + return result; +} + +void SymbolTreeValueDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const +{ + // This function is intentionally left blank to prevent the values of + // editors from constantly being reset every time the model is updated. +} + +void SymbolTreeValueDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const +{ + if (!index.isValid()) + return; + + const SymbolTreeModel* tree_model = qobject_cast(index.model()); + if (!model) + return; + + SymbolTreeNode* node = tree_model->nodeFromIndex(index); + if (!node || !node->type.valid()) + return; + + QVariant value; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + const ccc::ast::Node* logical_type = node->type.lookup_node(database); + if (!logical_type) + return; + + const ccc::ast::Node& type = *logical_type->physical_type(database).first; + switch (type.descriptor) + { + case ccc::ast::BUILTIN: + { + const ccc::ast::BuiltIn& builtIn = type.as(); + + switch (builtIn.bclass) + { + case ccc::ast::BuiltInClass::UNSIGNED_8: + case ccc::ast::BuiltInClass::UNQUALIFIED_8: + case ccc::ast::BuiltInClass::UNSIGNED_16: + case ccc::ast::BuiltInClass::UNSIGNED_32: + case ccc::ast::BuiltInClass::UNSIGNED_64: + { + QLineEdit* line_edit = qobject_cast(editor); + Q_ASSERT(line_edit); + + bool ok; + qulonglong i = line_edit->text().toULongLong(&ok); + if (ok) + value = i; + + break; + } + case ccc::ast::BuiltInClass::SIGNED_8: + case ccc::ast::BuiltInClass::SIGNED_16: + case ccc::ast::BuiltInClass::SIGNED_32: + case ccc::ast::BuiltInClass::SIGNED_64: + { + QLineEdit* line_edit = qobject_cast(editor); + Q_ASSERT(line_edit); + + bool ok; + qlonglong i = line_edit->text().toLongLong(&ok); + if (ok) + value = i; + + break; + } + case ccc::ast::BuiltInClass::BOOL_8: + { + QCheckBox* check_box = qobject_cast(editor); + value = check_box->isChecked(); + + break; + } + case ccc::ast::BuiltInClass::FLOAT_32: + { + QLineEdit* line_edit = qobject_cast(editor); + Q_ASSERT(line_edit); + + bool ok; + float f = line_edit->text().toFloat(&ok); + if (ok) + value = f; + + break; + } + case ccc::ast::BuiltInClass::FLOAT_64: + { + QLineEdit* line_edit = qobject_cast(editor); + Q_ASSERT(line_edit); + + bool ok; + double d = line_edit->text().toDouble(&ok); + if (ok) + value = d; + + break; + } + default: + { + } + } + break; + } + case ccc::ast::ENUM: + { + const ccc::ast::Enum& enumeration = type.as(); + QComboBox* combo_box = qobject_cast(editor); + Q_ASSERT(combo_box); + + s32 comboIndex = combo_box->currentIndex(); + if (comboIndex < 0 || comboIndex >= (s32)enumeration.constants.size()) + break; + + value = enumeration.constants[comboIndex].first; + + break; + } + case ccc::ast::POINTER_OR_REFERENCE: + case ccc::ast::POINTER_TO_DATA_MEMBER: + { + QLineEdit* line_edit = qobject_cast(editor); + Q_ASSERT(line_edit); + + bool ok; + qulonglong address = line_edit->text().toUInt(&ok, 16); + if (ok) + value = address; + + break; + } + default: + { + } + } + }); + + if (value.isValid()) + model->setData(index, value, SymbolTreeModel::EDIT_ROLE); +} + +void SymbolTreeValueDelegate::onComboBoxIndexChanged(int index) +{ + QComboBox* combo_box = qobject_cast(sender()); + if (combo_box) + commitData(combo_box); +} + +// ***************************************************************************** + +SymbolTreeLocationDelegate::SymbolTreeLocationDelegate( + DebugInterface& cpu, + u32 alignment, + QObject* parent) + : QStyledItemDelegate(parent) + , m_cpu(cpu) + , m_alignment(alignment) +{ +} + +QWidget* SymbolTreeLocationDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + if (!index.isValid()) + return nullptr; + + const SymbolTreeModel* model = qobject_cast(index.model()); + if (!model) + return nullptr; + + SymbolTreeNode* node = model->nodeFromIndex(index); + if (!node || !node->symbol.valid() || !node->symbol.is_flag_set(ccc::WITH_ADDRESS_MAP)) + return nullptr; + + if (!node->is_location_editable) + return nullptr; + + return new QLineEdit(parent); +} + +void SymbolTreeLocationDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const +{ + if (!index.isValid()) + return; + + const SymbolTreeModel* model = qobject_cast(index.model()); + if (!model) + return; + + SymbolTreeNode* node = model->nodeFromIndex(index); + if (!node || !node->symbol.valid()) + return; + + QLineEdit* line_edit = qobject_cast(editor); + Q_ASSERT(line_edit); + + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + const ccc::Symbol* symbol = node->symbol.lookup_symbol(database); + if (!symbol || !symbol->address().valid()) + return; + + line_edit->setText(QString::number(symbol->address().value, 16)); + }); +} + +void SymbolTreeLocationDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const +{ + if (!index.isValid()) + return; + + const SymbolTreeModel* tree_model = qobject_cast(index.model()); + if (!tree_model) + return; + + SymbolTreeNode* node = tree_model->nodeFromIndex(index); + if (!node || !node->symbol.valid() || !node->symbol.is_flag_set(ccc::WITH_ADDRESS_MAP)) + return; + + QLineEdit* line_edit = qobject_cast(editor); + Q_ASSERT(line_edit); + + SymbolTreeModel* symbol_tree_model = qobject_cast(model); + Q_ASSERT(symbol_tree_model); + + bool ok; + u32 address = line_edit->text().toUInt(&ok, 16); + if (!ok) + return; + + address -= address % m_alignment; + + bool success = false; + m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { + success = node->symbol.move_symbol(address, database); + }); + + if (success) + { + node->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, address); + symbol_tree_model->setData(index, QVariant(), SymbolTreeModel::UPDATE_FROM_MEMORY_ROLE); + symbol_tree_model->resetChildren(index); + } +} + +// ***************************************************************************** + +SymbolTreeTypeDelegate::SymbolTreeTypeDelegate( + DebugInterface& cpu, + QObject* parent) + : QStyledItemDelegate(parent) + , m_cpu(cpu) +{ +} + +QWidget* SymbolTreeTypeDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + if (!index.isValid()) + return nullptr; + + const SymbolTreeModel* tree_model = qobject_cast(index.model()); + if (!tree_model) + return nullptr; + + SymbolTreeNode* node = tree_model->nodeFromIndex(index); + if (!node || !node->symbol.valid()) + return nullptr; + + return new QLineEdit(parent); +} + +void SymbolTreeTypeDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const +{ + if (!index.isValid()) + return; + + const SymbolTreeModel* tree_model = qobject_cast(index.model()); + if (!tree_model) + return; + + SymbolTreeNode* node = tree_model->nodeFromIndex(index); + if (!node || !node->symbol.valid()) + return; + + QLineEdit* line_edit = qobject_cast(editor); + Q_ASSERT(line_edit); + + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + const ccc::Symbol* symbol = node->symbol.lookup_symbol(database); + if (!symbol || !symbol->type()) + return; + + line_edit->setText(typeToString(symbol->type(), database)); + }); +} + +void SymbolTreeTypeDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const +{ + if (!index.isValid()) + return; + + const SymbolTreeModel* tree_model = qobject_cast(index.model()); + if (!tree_model) + return; + + SymbolTreeNode* node = tree_model->nodeFromIndex(index); + if (!node || !node->symbol.valid()) + return; + + QLineEdit* line_edit = qobject_cast(editor); + Q_ASSERT(line_edit); + + SymbolTreeModel* symbol_tree_model = qobject_cast(model); + Q_ASSERT(symbol_tree_model); + + QString error_message; + m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { + ccc::Symbol* symbol = node->symbol.lookup_symbol(database); + if (!symbol) + { + error_message = tr("Symbol no longer exists."); + return; + } + + std::unique_ptr type = stringToType(line_edit->text().toStdString(), database, error_message); + if (!error_message.isEmpty()) + return; + + symbol->set_type(std::move(type)); + node->type = ccc::NodeHandle(node->symbol.descriptor(), *symbol, symbol->type()); + }); + + if (error_message.isEmpty()) + { + symbol_tree_model->setData(index, QVariant(), SymbolTreeModel::UPDATE_FROM_MEMORY_ROLE); + symbol_tree_model->resetChildren(index); + } + else + QMessageBox::warning(editor, tr("Cannot Change Type"), error_message); +} diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.h b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.h new file mode 100644 index 0000000000..934727fe73 --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: LGPL-3.0+ + +#pragma once + +#include + +#include "DebugTools/SymbolGuardian.h" + +class SymbolTreeValueDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + SymbolTreeValueDelegate( + DebugInterface& cpu, + QObject* parent = nullptr); + + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + void setEditorData(QWidget* editor, const QModelIndex& index) const override; + void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; + +protected: + // Without this, setModelData would only be called when a combo box was + // deselected rather than when an option was picked. + void onComboBoxIndexChanged(int index); + + DebugInterface& m_cpu; +}; + +class SymbolTreeLocationDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + SymbolTreeLocationDelegate( + DebugInterface& cpu, + u32 alignment, + QObject* parent = nullptr); + + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + void setEditorData(QWidget* editor, const QModelIndex& index) const override; + void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; + +protected: + DebugInterface& m_cpu; + u32 m_alignment; +}; + +class SymbolTreeTypeDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + SymbolTreeTypeDelegate( + DebugInterface& cpu, + QObject* parent = nullptr); + + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + void setEditorData(QWidget* editor, const QModelIndex& index) const override; + void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; + +protected: + DebugInterface& m_cpu; +}; diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeLocation.cpp b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeLocation.cpp new file mode 100644 index 0000000000..70f95d94f4 --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeLocation.cpp @@ -0,0 +1,222 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: LGPL-3.0+ + +#include "SymbolTreeLocation.h" + +#include "DebugTools/DebugInterface.h" + +SymbolTreeLocation::SymbolTreeLocation() = default; + +SymbolTreeLocation::SymbolTreeLocation(Type type_arg, u32 address_arg) + : type(type_arg) + , address(address_arg) +{ +} + +QString SymbolTreeLocation::toString(DebugInterface& cpu) const +{ + switch (type) + { + case REGISTER: + if (address < 32) + return cpu.getRegisterName(0, address); + else + return QString("Reg %1").arg(address); + case MEMORY: + return QString::number(address, 16); + default: + { + } + } + return QString(); +} + +SymbolTreeLocation SymbolTreeLocation::addOffset(u32 offset) const +{ + SymbolTreeLocation location; + switch (type) + { + case REGISTER: + if (offset == 0) + location = *this; + break; + case MEMORY: + location.type = type; + location.address = address + offset; + break; + default: + { + } + } + return location; +} + +u8 SymbolTreeLocation::read8(DebugInterface& cpu) const +{ + switch (type) + { + case REGISTER: + if (address < 32) + return cpu.getRegister(EECAT_GPR, address)._u8[0]; + break; + case MEMORY: + return (u8)cpu.read8(address); + default: + { + } + } + return 0; +} + +u16 SymbolTreeLocation::read16(DebugInterface& cpu) const +{ + switch (type) + { + case REGISTER: + if (address < 32) + return cpu.getRegister(EECAT_GPR, address)._u16[0]; + break; + case MEMORY: + return (u16)cpu.read16(address); + default: + { + } + } + return 0; +} + +u32 SymbolTreeLocation::read32(DebugInterface& cpu) const +{ + switch (type) + { + case REGISTER: + if (address < 32) + return cpu.getRegister(EECAT_GPR, address)._u32[0]; + break; + case MEMORY: + return cpu.read32(address); + default: + { + } + } + return 0; +} + +u64 SymbolTreeLocation::read64(DebugInterface& cpu) const +{ + switch (type) + { + case REGISTER: + if (address < 32) + return cpu.getRegister(EECAT_GPR, address)._u64[0]; + break; + case MEMORY: + return cpu.read64(address); + default: + { + } + } + return 0; +} + +u128 SymbolTreeLocation::read128(DebugInterface& cpu) const +{ + switch (type) + { + case REGISTER: + if (address < 32) + return cpu.getRegister(EECAT_GPR, address); + break; + case MEMORY: + return cpu.read128(address); + default: + { + } + } + return u128::From32(0); +} + +void SymbolTreeLocation::write8(u8 value, DebugInterface& cpu) const +{ + switch (type) + { + case REGISTER: + if (address < 32) + cpu.setRegister(0, address, u128::From32(value)); + break; + case MEMORY: + cpu.write8(address, value); + break; + default: + { + } + } +} + +void SymbolTreeLocation::write16(u16 value, DebugInterface& cpu) const +{ + switch (type) + { + case REGISTER: + if (address < 32) + cpu.setRegister(0, address, u128::From32(value)); + break; + case MEMORY: + cpu.write16(address, value); + break; + default: + { + } + } +} + +void SymbolTreeLocation::write32(u32 value, DebugInterface& cpu) const +{ + switch (type) + { + case REGISTER: + if (address < 32) + cpu.setRegister(0, address, u128::From32(value)); + break; + case MEMORY: + cpu.write32(address, value); + break; + default: + { + } + } +} + +void SymbolTreeLocation::write64(u64 value, DebugInterface& cpu) const +{ + switch (type) + { + case REGISTER: + if (address < 32) + cpu.setRegister(0, address, u128::From64(value)); + break; + case MEMORY: + cpu.write64(address, value); + break; + default: + { + } + } +} + +void SymbolTreeLocation::write128(u128 value, DebugInterface& cpu) const +{ + switch (type) + { + case REGISTER: + if (address < 32) + cpu.setRegister(0, address, value); + break; + case MEMORY: + cpu.write128(address, value); + break; + default: + { + } + } +} diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeLocation.h b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeLocation.h new file mode 100644 index 0000000000..e81d61ea95 --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeLocation.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: LGPL-3.0+ + +#pragma once + +#include + +#include "common/Pcsx2Types.h" +#include "DebugTools/DebugInterface.h" + +class DebugInterface; + +// A memory location, either a register or an address. +struct SymbolTreeLocation +{ + enum Type + { + REGISTER, + MEMORY, + NONE // Put NONE last so nodes of this type sort to the bottom. + }; + + Type type = NONE; + u32 address = 0; + + SymbolTreeLocation(); + SymbolTreeLocation(Type type_arg, u32 address_arg); + + QString toString(DebugInterface& cpu) const; + + SymbolTreeLocation addOffset(u32 offset) const; + + u8 read8(DebugInterface& cpu) const; + u16 read16(DebugInterface& cpu) const; + u32 read32(DebugInterface& cpu) const; + u64 read64(DebugInterface& cpu) const; + u128 read128(DebugInterface& cpu) const; + + void write8(u8 value, DebugInterface& cpu) const; + void write16(u16 value, DebugInterface& cpu) const; + void write32(u32 value, DebugInterface& cpu) const; + void write64(u64 value, DebugInterface& cpu) const; + void write128(u128 value, DebugInterface& cpu) const; + + friend auto operator<=>(const SymbolTreeLocation& lhs, const SymbolTreeLocation& rhs) = default; +}; diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.cpp b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.cpp new file mode 100644 index 0000000000..b7c10901ce --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.cpp @@ -0,0 +1,597 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: LGPL-3.0+ + +#include "SymbolTreeModel.h" + +#include +#include +#include + +#include "common/Assertions.h" + +#include "TypeString.h" + +SymbolTreeModel::SymbolTreeModel(DebugInterface& cpu, QObject* parent) + : QAbstractItemModel(parent) + , m_cpu(cpu) +{ +} + +QModelIndex SymbolTreeModel::index(int row, int column, const QModelIndex& parent) const +{ + SymbolTreeNode* parent_node = nodeFromIndex(parent); + if (!parent_node) + return QModelIndex(); + + if (row < 0 || row >= (int)parent_node->children().size()) + return QModelIndex(); + + const SymbolTreeNode* child_node = parent_node->children()[row].get(); + if (!child_node) + return QModelIndex(); + + return createIndex(row, column, child_node); +} + +QModelIndex SymbolTreeModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + SymbolTreeNode* child_node = nodeFromIndex(index); + if (!child_node) + return QModelIndex(); + + const SymbolTreeNode* parent_node = child_node->parent(); + if (!parent_node || parent_node == m_root.get()) + return QModelIndex(); + + return indexFromNode(*parent_node); +} + +int SymbolTreeModel::rowCount(const QModelIndex& parent) const +{ + if (parent.column() > 0) + return 0; + + SymbolTreeNode* node = nodeFromIndex(parent); + if (!node) + return 0; + + return (int)node->children().size(); +} + +int SymbolTreeModel::columnCount(const QModelIndex& parent) const +{ + return COLUMN_COUNT; +} + +bool SymbolTreeModel::hasChildren(const QModelIndex& parent) const +{ + if (!parent.isValid()) + return true; + + SymbolTreeNode* parent_node = nodeFromIndex(parent); + if (!parent_node) + return true; + + // If a node doesn't have a type, it can't generate any children, so all the + // children that will exist must already be there. + if (!parent_node->type.valid()) + return !parent_node->children().empty(); + + bool result = true; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { + const ccc::ast::Node* type = parent_node->type.lookup_node(database); + if (!type) + return; + + result = nodeHasChildren(*type, database); + }); + + return result; +} + +QVariant SymbolTreeModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + SymbolTreeNode* node = nodeFromIndex(index); + if (!node) + return QVariant(); + + if (role == Qt::ForegroundRole) + { + bool active = true; + + // Gray out the names of symbols that have been overwritten in memory. + if (index.column() == NAME && node->symbol.valid()) + active = symbolMatchesMemory(node->symbol); + + // Gray out the values of variables that are dead. + if (index.column() == VALUE && node->liveness().has_value()) + active = *node->liveness(); + + QPalette::ColorGroup group = active ? QPalette::Active : QPalette::Disabled; + return QBrush(QApplication::palette().color(group, QPalette::Text)); + } + + if (role != Qt::DisplayRole) + return QVariant(); + + switch (index.column()) + { + case NAME: + { + return node->name; + } + case VALUE: + { + if (node->tag != SymbolTreeNode::OBJECT) + return QVariant(); + + return node->display_value(); + } + case LOCATION: + { + return node->location.toString(m_cpu).rightJustified(8); + } + case SIZE: + { + if (!node->size.has_value()) + return QVariant(); + + return QString::number(*node->size); + } + case TYPE: + { + QVariant result; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { + const ccc::ast::Node* type = node->type.lookup_node(database); + if (!type) + return; + + result = typeToString(type, database); + }); + + return result; + } + case LIVENESS: + { + if (!node->liveness().has_value()) + return QVariant(); + + return *node->liveness() ? tr("Alive") : tr("Dead"); + } + } + + return QVariant(); +} + +bool SymbolTreeModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!index.isValid()) + return false; + + SymbolTreeNode* node = nodeFromIndex(index); + if (!node) + return false; + + bool data_changed = false; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { + switch (role) + { + case EDIT_ROLE: + data_changed = node->writeToVM(value, m_cpu, database); + break; + case UPDATE_FROM_MEMORY_ROLE: + data_changed = node->readFromVM(m_cpu, database); + break; + } + }); + + if (data_changed) + emit dataChanged(index.siblingAtColumn(0), index.siblingAtColumn(COLUMN_COUNT - 1)); + + return data_changed; +} + +void SymbolTreeModel::fetchMore(const QModelIndex& parent) +{ + if (!parent.isValid()) + return; + + SymbolTreeNode* parent_node = nodeFromIndex(parent); + if (!parent_node || !parent_node->type.valid()) + return; + + if (!parent_node->children().empty()) + return; + + std::vector> children; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { + const ccc::ast::Node* logical_parent_type = parent_node->type.lookup_node(database); + if (!logical_parent_type) + return; + + children = populateChildren( + parent_node->name, parent_node->location, *logical_parent_type, parent_node->type, m_cpu, database); + }); + + bool insert_children = !children.empty(); + if (insert_children) + beginInsertRows(parent, 0, children.size() - 1); + parent_node->setChildren(std::move(children)); + if (insert_children) + endInsertRows(); +} + +bool SymbolTreeModel::canFetchMore(const QModelIndex& parent) const +{ + if (!parent.isValid()) + return false; + + SymbolTreeNode* parent_node = nodeFromIndex(parent); + if (!parent_node || !parent_node->type.valid()) + return false; + + bool result = false; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { + const ccc::ast::Node* parent_type = parent_node->type.lookup_node(database); + if (!parent_type) + return; + + result = nodeHasChildren(*parent_type, database) && !parent_node->childrenFetched(); + }); + + return result; +} + +Qt::ItemFlags SymbolTreeModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + + if (index.column() == LOCATION || index.column() == TYPE || index.column() == VALUE) + flags |= Qt::ItemIsEditable; + + return flags; +} + +QVariant SymbolTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) + return QVariant(); + + switch (section) + { + case NAME: + return tr("Name"); + case VALUE: + return tr("Value"); + case LOCATION: + return tr("Location"); + case SIZE: + return tr("Size"); + case TYPE: + return tr("Type"); + case LIVENESS: + return tr("Liveness"); + } + + return QVariant(); +} + +QModelIndex SymbolTreeModel::indexFromNode(const SymbolTreeNode& node) const +{ + int row = 0; + if (node.parent()) + { + for (int i = 0; i < (int)node.parent()->children().size(); i++) + if (node.parent()->children()[i].get() == &node) + row = i; + } + else + row = 0; + + return createIndex(row, 0, &node); +} + +SymbolTreeNode* SymbolTreeModel::nodeFromIndex(const QModelIndex& index) const +{ + if (!index.isValid()) + return m_root.get(); + + SymbolTreeNode* node = static_cast(index.internalPointer()); + if (!node) + return m_root.get(); + + return node; +} + +void SymbolTreeModel::reset(std::unique_ptr new_root) +{ + beginResetModel(); + m_root = std::move(new_root); + endResetModel(); +} + +void SymbolTreeModel::resetChildren(QModelIndex index) +{ + pxAssertRel(index.isValid(), "Invalid model index."); + + SymbolTreeNode* node = nodeFromIndex(index); + if (!node || node->tag != SymbolTreeNode::OBJECT) + return; + + resetChildrenRecursive(*node); +} + +void SymbolTreeModel::resetChildrenRecursive(SymbolTreeNode& node) +{ + for (const std::unique_ptr& child : node.children()) + resetChildrenRecursive(*child); + + bool remove_rows = !node.children().empty(); + if (remove_rows) + beginRemoveRows(indexFromNode(node), 0, node.children().size() - 1); + node.clearChildren(); + if (remove_rows) + endRemoveRows(); +} + +bool SymbolTreeModel::needsReset() const +{ + if (!m_root) + return true; + + bool needs_reset = false; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + needs_reset = !m_root->anySymbolsValid(database); + }); + + return needs_reset; +} + +std::optional SymbolTreeModel::changeTypeTemporarily(QModelIndex index, std::string_view type_string) +{ + SymbolTreeNode* node = nodeFromIndex(index); + if (!node) + return std::nullopt; + + resetChildren(index); + + QString error_message; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { + std::unique_ptr type = stringToType(type_string, database, error_message); + if (!error_message.isEmpty()) + return; + + node->temporary_type = std::move(type); + node->type = ccc::NodeHandle(node->temporary_type.get()); + }); + + setData(index, QVariant(), UPDATE_FROM_MEMORY_ROLE); + + return error_message; +} + +std::optional SymbolTreeModel::typeFromModelIndexToString(QModelIndex index) +{ + SymbolTreeNode* node = nodeFromIndex(index); + if (!node || node->tag != SymbolTreeNode::OBJECT) + return std::nullopt; + + QString result; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { + const ccc::ast::Node* type = node->type.lookup_node(database); + if (!type) + return; + + result = typeToString(type, database); + }); + + return result; +} + +std::vector> SymbolTreeModel::populateChildren( + const QString& name, + SymbolTreeLocation location, + const ccc::ast::Node& logical_type, + ccc::NodeHandle parent_handle, + DebugInterface& cpu, + const ccc::SymbolDatabase& database) +{ + auto [physical_type, symbol] = logical_type.physical_type(database); + + // If we went through a type name, we need to make the node handles for the + // children point to the new symbol instead of the original one. + if (symbol) + parent_handle = ccc::NodeHandle(*symbol, nullptr); + + std::vector> children; + + switch (physical_type->descriptor) + { + case ccc::ast::ARRAY: + { + const ccc::ast::Array& array = physical_type->as(); + + for (s32 i = 0; i < array.element_count; i++) + { + SymbolTreeLocation element_location = location.addOffset(i * array.element_type->size_bytes); + if (element_location.type == SymbolTreeLocation::NONE) + continue; + + std::unique_ptr element = std::make_unique(); + element->name = QString("[%1]").arg(i); + element->type = parent_handle.handle_for_child(array.element_type.get()); + element->location = element_location; + children.emplace_back(std::move(element)); + } + + break; + } + case ccc::ast::POINTER_OR_REFERENCE: + { + const ccc::ast::PointerOrReference& pointer_or_reference = physical_type->as(); + + u32 address = location.read32(cpu); + if (!cpu.isValidAddress(address)) + break; + + std::unique_ptr pointee = std::make_unique(); + pointee->name = QString("*%1").arg(name); + pointee->type = parent_handle.handle_for_child(pointer_or_reference.value_type.get()); + pointee->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, address); + children.emplace_back(std::move(pointee)); + + break; + } + case ccc::ast::STRUCT_OR_UNION: + { + const ccc::ast::StructOrUnion& struct_or_union = physical_type->as(); + + std::vector fields; + struct_or_union.flatten_fields(fields, nullptr, database, true); + + for (const ccc::ast::StructOrUnion::FlatField& field : fields) + { + if (symbol) + parent_handle = ccc::NodeHandle(*symbol, nullptr); + + SymbolTreeLocation field_location = location.addOffset(field.base_offset + field.node->offset_bytes); + if (field_location.type == SymbolTreeLocation::NONE) + continue; + + std::unique_ptr child_node = std::make_unique(); + if (!field.node->name.empty()) + child_node->name = QString::fromStdString(field.node->name); + else + child_node->name = QString("(anonymous %1)").arg(ccc::ast::node_type_to_string(*field.node)); + child_node->type = parent_handle.handle_for_child(field.node); + child_node->location = field_location; + children.emplace_back(std::move(child_node)); + } + + break; + } + default: + { + } + } + + return children; +} + +bool SymbolTreeModel::nodeHasChildren(const ccc::ast::Node& logical_type, const ccc::SymbolDatabase& database) +{ + const ccc::ast::Node& type = *logical_type.physical_type(database).first; + + bool result = false; + switch (type.descriptor) + { + case ccc::ast::ARRAY: + { + const ccc::ast::Array& array = type.as(); + result = array.element_count > 0; + break; + } + case ccc::ast::POINTER_OR_REFERENCE: + { + result = true; + break; + } + case ccc::ast::STRUCT_OR_UNION: + { + const ccc::ast::StructOrUnion& struct_or_union = type.as(); + result = !struct_or_union.base_classes.empty() || !struct_or_union.fields.empty(); + break; + } + default: + { + } + } + + return result; +} + +bool SymbolTreeModel::symbolMatchesMemory(ccc::MultiSymbolHandle& symbol) const +{ + bool matching = true; + switch (symbol.descriptor()) + { + case ccc::SymbolDescriptor::FUNCTION: + { + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { + const ccc::Function* function = database.functions.symbol_from_handle(symbol.handle()); + if (!function || function->original_hash() == 0) + return; + + matching = function->current_hash() == function->original_hash(); + }); + break; + } + case ccc::SymbolDescriptor::GLOBAL_VARIABLE: + { + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { + const ccc::GlobalVariable* global_variable = database.global_variables.symbol_from_handle(symbol.handle()); + if (!global_variable) + return; + + const ccc::SourceFile* source_file = database.source_files.symbol_from_handle(global_variable->source_file()); + if (!source_file) + return; + + matching = source_file->functions_match(); + }); + break; + } + case ccc::SymbolDescriptor::LOCAL_VARIABLE: + { + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { + const ccc::LocalVariable* local_variable = database.local_variables.symbol_from_handle(symbol.handle()); + if (!local_variable) + return; + + const ccc::Function* function = database.functions.symbol_from_handle(local_variable->function()); + if (!function) + return; + + const ccc::SourceFile* source_file = database.source_files.symbol_from_handle(function->source_file()); + if (!source_file) + return; + + matching = source_file->functions_match(); + }); + break; + } + case ccc::SymbolDescriptor::PARAMETER_VARIABLE: + { + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { + const ccc::ParameterVariable* parameter_variable = database.parameter_variables.symbol_from_handle(symbol.handle()); + if (!parameter_variable) + return; + + const ccc::Function* function = database.functions.symbol_from_handle(parameter_variable->function()); + if (!function) + return; + + const ccc::SourceFile* source_file = database.source_files.symbol_from_handle(function->source_file()); + if (!source_file) + return; + + matching = source_file->functions_match(); + }); + break; + } + default: + { + } + } + + return matching; +} diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.h b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.h new file mode 100644 index 0000000000..d1a5e4a258 --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.h @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: LGPL-3.0+ + +#pragma once + +#include + +#include +#include + +#include "common/Pcsx2Defs.h" +#include "DebugTools/DebugInterface.h" +#include "SymbolTreeNode.h" + +// Model for the symbol trees. It will dynamically grow itself as the user +// chooses to expand different nodes. +class SymbolTreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum Column + { + NAME = 0, + VALUE = 1, + LOCATION = 2, + SIZE = 3, + TYPE = 4, + LIVENESS = 5, + COLUMN_COUNT = 6 + }; + + enum SetDataRole + { + EDIT_ROLE = Qt::EditRole, + UPDATE_FROM_MEMORY_ROLE = Qt::UserRole + }; + + SymbolTreeModel(DebugInterface& cpu, QObject* parent = nullptr); + + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& index) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + bool hasChildren(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex& index, const QVariant& value, int role = EDIT_ROLE) override; + void fetchMore(const QModelIndex& parent) override; + bool canFetchMore(const QModelIndex& parent) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + QModelIndex indexFromNode(const SymbolTreeNode& node) const; + SymbolTreeNode* nodeFromIndex(const QModelIndex& index) const; + + // Reset the whole model. + void reset(std::unique_ptr new_root); + + // Remove all the children of a given node, and allow fetching again. + void resetChildren(QModelIndex index); + void resetChildrenRecursive(SymbolTreeNode& node); + + bool needsReset() const; + + std::optional changeTypeTemporarily(QModelIndex index, std::string_view type_string); + std::optional typeFromModelIndexToString(QModelIndex index); + +protected: + static std::vector> populateChildren( + const QString& name, + SymbolTreeLocation location, + const ccc::ast::Node& logical_type, + ccc::NodeHandle parent_handle, + DebugInterface& cpu, + const ccc::SymbolDatabase& database); + + static bool nodeHasChildren(const ccc::ast::Node& logical_type, const ccc::SymbolDatabase& database); + + bool symbolMatchesMemory(ccc::MultiSymbolHandle& symbol) const; + + std::unique_ptr m_root; + QString m_filter; + DebugInterface& m_cpu; +}; diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.cpp b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.cpp new file mode 100644 index 0000000000..9b06ec0aeb --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.cpp @@ -0,0 +1,555 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: LGPL-3.0+ + +#include "SymbolTreeNode.h" + +#include + +const QVariant& SymbolTreeNode::value() const +{ + return m_value; +} + +const QString& SymbolTreeNode::display_value() const +{ + return m_display_value; +} + +std::optional SymbolTreeNode::liveness() +{ + return m_liveness; +} + +bool SymbolTreeNode::readFromVM(DebugInterface& cpu, const ccc::SymbolDatabase& database) +{ + QVariant new_value; + + const ccc::ast::Node* logical_type = type.lookup_node(database); + if (logical_type) + { + const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first; + new_value = readValueAsVariant(physical_type, cpu, database); + } + + bool data_changed = false; + + if (new_value != m_value) + { + m_value = std::move(new_value); + data_changed = true; + } + + data_changed |= updateDisplayString(cpu, database); + data_changed |= updateLiveness(cpu); + + return data_changed; +} + +bool SymbolTreeNode::writeToVM(QVariant value, DebugInterface& cpu, const ccc::SymbolDatabase& database) +{ + bool data_changed = false; + + if (value != m_value) + { + m_value = std::move(value); + data_changed = true; + } + + const ccc::ast::Node* logical_type = type.lookup_node(database); + if (logical_type) + { + const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first; + writeValueFromVariant(m_value, physical_type, cpu); + } + + data_changed |= updateDisplayString(cpu, database); + data_changed |= updateLiveness(cpu); + + return data_changed; +} + +QVariant SymbolTreeNode::readValueAsVariant(const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database) const +{ + switch (physical_type.descriptor) + { + case ccc::ast::BUILTIN: + { + const ccc::ast::BuiltIn& builtIn = physical_type.as(); + switch (builtIn.bclass) + { + case ccc::ast::BuiltInClass::UNSIGNED_8: + return (qulonglong)location.read8(cpu); + case ccc::ast::BuiltInClass::SIGNED_8: + return (qlonglong)(s8)location.read8(cpu); + case ccc::ast::BuiltInClass::UNQUALIFIED_8: + return (qulonglong)location.read8(cpu); + case ccc::ast::BuiltInClass::BOOL_8: + return (bool)location.read8(cpu); + case ccc::ast::BuiltInClass::UNSIGNED_16: + return (qulonglong)location.read16(cpu); + case ccc::ast::BuiltInClass::SIGNED_16: + return (qlonglong)(s16)location.read16(cpu); + case ccc::ast::BuiltInClass::UNSIGNED_32: + return (qulonglong)location.read32(cpu); + case ccc::ast::BuiltInClass::SIGNED_32: + return (qlonglong)(s32)location.read32(cpu); + case ccc::ast::BuiltInClass::FLOAT_32: + { + u32 value = location.read32(cpu); + return *reinterpret_cast(&value); + } + case ccc::ast::BuiltInClass::UNSIGNED_64: + return (qulonglong)location.read64(cpu); + case ccc::ast::BuiltInClass::SIGNED_64: + return (qlonglong)(s64)location.read64(cpu); + case ccc::ast::BuiltInClass::FLOAT_64: + { + u64 value = location.read64(cpu); + return *reinterpret_cast(&value); + } + default: + { + } + } + break; + } + case ccc::ast::ENUM: + return location.read32(cpu); + case ccc::ast::POINTER_OR_REFERENCE: + case ccc::ast::POINTER_TO_DATA_MEMBER: + return location.read32(cpu); + default: + { + } + } + + return QVariant(); +} + +bool SymbolTreeNode::writeValueFromVariant(QVariant value, const ccc::ast::Node& physical_type, DebugInterface& cpu) const +{ + switch (physical_type.descriptor) + { + case ccc::ast::BUILTIN: + { + const ccc::ast::BuiltIn& built_in = physical_type.as(); + + switch (built_in.bclass) + { + case ccc::ast::BuiltInClass::UNSIGNED_8: + location.write8((u8)value.toULongLong(), cpu); + break; + case ccc::ast::BuiltInClass::SIGNED_8: + location.write8((u8)(s8)value.toLongLong(), cpu); + break; + case ccc::ast::BuiltInClass::UNQUALIFIED_8: + location.write8((u8)value.toULongLong(), cpu); + break; + case ccc::ast::BuiltInClass::BOOL_8: + location.write8((u8)value.toBool(), cpu); + break; + case ccc::ast::BuiltInClass::UNSIGNED_16: + location.write16((u16)value.toULongLong(), cpu); + break; + case ccc::ast::BuiltInClass::SIGNED_16: + location.write16((u16)(s16)value.toLongLong(), cpu); + break; + case ccc::ast::BuiltInClass::UNSIGNED_32: + location.write32((u32)value.toULongLong(), cpu); + break; + case ccc::ast::BuiltInClass::SIGNED_32: + location.write32((u32)(s32)value.toLongLong(), cpu); + break; + case ccc::ast::BuiltInClass::FLOAT_32: + { + float f = value.toFloat(); + location.write32(*reinterpret_cast(&f), cpu); + break; + } + case ccc::ast::BuiltInClass::UNSIGNED_64: + location.write64((u64)value.toULongLong(), cpu); + break; + case ccc::ast::BuiltInClass::SIGNED_64: + location.write64((u64)(s64)value.toLongLong(), cpu); + break; + case ccc::ast::BuiltInClass::FLOAT_64: + { + double d = value.toDouble(); + location.write64(*reinterpret_cast(&d), cpu); + break; + } + default: + { + return false; + } + } + break; + } + case ccc::ast::ENUM: + location.write32((u32)value.toULongLong(), cpu); + break; + case ccc::ast::POINTER_OR_REFERENCE: + case ccc::ast::POINTER_TO_DATA_MEMBER: + location.write32((u32)value.toULongLong(), cpu); + break; + default: + { + return false; + } + } + + return true; +} + +bool SymbolTreeNode::updateDisplayString(DebugInterface& cpu, const ccc::SymbolDatabase& database) +{ + QString result; + + const ccc::ast::Node* logical_type = type.lookup_node(database); + if (logical_type) + { + const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first; + result = generateDisplayString(physical_type, cpu, database, 0); + } + + if (result.isEmpty()) + { + // We don't know how to display objects of this type, so just show the + // first 4 bytes of it as a hex dump. + u32 value = location.read32(cpu); + result = QString("%1 %2 %3 %4") + .arg(value & 0xff, 2, 16, QChar('0')) + .arg((value >> 8) & 0xff, 2, 16, QChar('0')) + .arg((value >> 16) & 0xff, 2, 16, QChar('0')) + .arg((value >> 24) & 0xff, 2, 16, QChar('0')); + } + + if (result == m_display_value) + return false; + + m_display_value = std::move(result); + + return true; +} + +QString SymbolTreeNode::generateDisplayString( + const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database, s32 depth) const +{ + s32 max_elements_to_display = 0; + switch (depth) + { + case 0: + max_elements_to_display = 8; + break; + case 1: + max_elements_to_display = 2; + break; + } + + switch (physical_type.descriptor) + { + case ccc::ast::ARRAY: + { + const ccc::ast::Array& array = physical_type.as(); + const ccc::ast::Node& element_type = *array.element_type->physical_type(database).first; + + if (element_type.name == "char" && location.type == SymbolTreeLocation::MEMORY) + { + char* string = cpu.stringFromPointer(location.address); + if (string) + return QString("\"%1\"").arg(string); + } + + QString result; + result += "{"; + + s32 elements_to_display = std::min(array.element_count, max_elements_to_display); + for (s32 i = 0; i < elements_to_display; i++) + { + SymbolTreeNode node; + node.location = location.addOffset(i * array.element_type->size_bytes); + + QString element = node.generateDisplayString(element_type, cpu, database, depth + 1); + if (element.isEmpty()) + element = QString("(%1)").arg(ccc::ast::node_type_to_string(element_type)); + result += element; + + if (i + 1 != array.element_count) + result += ","; + } + + if (elements_to_display != array.element_count) + result += "..."; + + result += "}"; + return result; + } + case ccc::ast::BUILTIN: + { + const ccc::ast::BuiltIn& builtIn = physical_type.as(); + + QString result; + switch (builtIn.bclass) + { + case ccc::ast::BuiltInClass::UNSIGNED_8: + result = QString::number(location.read8(cpu)); + break; + case ccc::ast::BuiltInClass::SIGNED_8: + result = QString::number((s8)location.read8(cpu)); + break; + case ccc::ast::BuiltInClass::UNQUALIFIED_8: + result = QString::number(location.read8(cpu)); + break; + case ccc::ast::BuiltInClass::BOOL_8: + result = location.read8(cpu) ? "true" : "false"; + break; + case ccc::ast::BuiltInClass::UNSIGNED_16: + result = QString::number(location.read16(cpu)); + break; + case ccc::ast::BuiltInClass::SIGNED_16: + result = QString::number((s16)location.read16(cpu)); + break; + case ccc::ast::BuiltInClass::UNSIGNED_32: + result = QString::number(location.read32(cpu)); + break; + case ccc::ast::BuiltInClass::SIGNED_32: + result = QString::number((s32)location.read32(cpu)); + break; + case ccc::ast::BuiltInClass::FLOAT_32: + { + u32 value = location.read32(cpu); + result = QString::number(*reinterpret_cast(&value)); + break; + } + case ccc::ast::BuiltInClass::UNSIGNED_64: + result = QString::number(location.read64(cpu)); + break; + case ccc::ast::BuiltInClass::SIGNED_64: + result = QString::number((s64)location.read64(cpu)); + break; + case ccc::ast::BuiltInClass::FLOAT_64: + { + u64 value = location.read64(cpu); + result = QString::number(*reinterpret_cast(&value)); + break; + } + case ccc::ast::BuiltInClass::UNSIGNED_128: + case ccc::ast::BuiltInClass::SIGNED_128: + case ccc::ast::BuiltInClass::UNQUALIFIED_128: + case ccc::ast::BuiltInClass::FLOAT_128: + { + if (depth > 0) + { + result = "(128-bit value)"; + break; + } + + for (s32 i = 0; i < 16; i++) + { + u8 value = location.addOffset(i).read8(cpu); + result += QString("%1 ").arg(value, 2, 16, QChar('0')); + if ((i + 1) % 4 == 0) + result += " "; + } + + break; + } + default: + { + } + } + + if (result.isEmpty()) + break; + + if (builtIn.name == "char") + { + char c = location.read8(cpu); + if (QChar::fromLatin1(c).isPrint()) + { + if (depth == 0) + result = result.leftJustified(3); + result += QString(" '%1'").arg(c); + } + } + + return result; + } + case ccc::ast::ENUM: + { + s32 value = (s32)location.read32(cpu); + const auto& enum_type = physical_type.as(); + for (auto [test_value, name] : enum_type.constants) + { + if (test_value == value) + return QString::fromStdString(name); + } + + break; + } + case ccc::ast::POINTER_OR_REFERENCE: + { + const auto& pointer_or_reference = physical_type.as(); + const ccc::ast::Node& value_type = + *pointer_or_reference.value_type->physical_type(database).first; + + u32 address = location.read32(cpu); + if (address == 0) + return "NULL"; + + QString result = QString::number(address, 16); + + if (pointer_or_reference.is_pointer && value_type.name == "char") + { + const char* string = cpu.stringFromPointer(address); + if (string) + result += QString(" \"%1\"").arg(string); + } + else if (depth == 0) + { + QString pointee = generateDisplayString(value_type, cpu, database, depth + 1); + if (!pointee.isEmpty()) + result += QString(" -> %1").arg(pointee); + } + + return result; + } + case ccc::ast::POINTER_TO_DATA_MEMBER: + { + return QString::number(location.read32(cpu), 16); + } + case ccc::ast::STRUCT_OR_UNION: + { + const ccc::ast::StructOrUnion& struct_or_union = physical_type.as(); + + QString result; + result += "{"; + + std::vector fields; + bool all_fields = struct_or_union.flatten_fields(fields, nullptr, database, true, 0, max_elements_to_display); + + for (size_t i = 0; i < fields.size(); i++) + { + const ccc::ast::StructOrUnion::FlatField& field = fields[i]; + + SymbolTreeNode node; + node.location = location.addOffset(field.base_offset + field.node->offset_bytes); + + const ccc::ast::Node& field_type = *field.node->physical_type(database).first; + QString field_value = node.generateDisplayString(field_type, cpu, database, depth + 1); + if (field_value.isEmpty()) + field_value = QString("(%1)").arg(ccc::ast::node_type_to_string(field_type)); + + QString field_name = QString::fromStdString(field.node->name); + result += QString(".%1=%2").arg(field_name).arg(field_value); + + if (i + 1 != fields.size() || !all_fields) + result += ","; + } + + if (!all_fields) + result += "..."; + + result += "}"; + return result; + } + default: + { + } + } + + return QString(); +} + +bool SymbolTreeNode::updateLiveness(DebugInterface& cpu) +{ + std::optional new_liveness; + if (live_range.low.valid() && live_range.high.valid()) + { + u32 pc = cpu.getPC(); + new_liveness = pc >= live_range.low && pc < live_range.high; + } + + if (new_liveness == m_liveness) + return false; + + m_liveness = new_liveness; + + return true; +} + +bool SymbolTreeNode::anySymbolsValid(const ccc::SymbolDatabase& database) const +{ + if (symbol.lookup_symbol(database)) + return true; + + for (const std::unique_ptr& child : children()) + if (child->anySymbolsValid(database)) + return true; + + return false; +} + +const SymbolTreeNode* SymbolTreeNode::parent() const +{ + return m_parent; +} + +const std::vector>& SymbolTreeNode::children() const +{ + return m_children; +} + +bool SymbolTreeNode::childrenFetched() const +{ + return m_children_fetched; +} + +void SymbolTreeNode::setChildren(std::vector> new_children) +{ + for (std::unique_ptr& child : new_children) + child->m_parent = this; + m_children = std::move(new_children); + m_children_fetched = true; +} + +void SymbolTreeNode::insertChildren(std::vector> new_children) +{ + for (std::unique_ptr& child : new_children) + child->m_parent = this; + m_children.insert(m_children.end(), + std::make_move_iterator(new_children.begin()), + std::make_move_iterator(new_children.end())); + m_children_fetched = true; +} + +void SymbolTreeNode::emplaceChild(std::unique_ptr new_child) +{ + new_child->m_parent = this; + m_children.emplace_back(std::move(new_child)); + m_children_fetched = true; +} + +void SymbolTreeNode::clearChildren() +{ + m_children.clear(); + m_children_fetched = false; +} + +void SymbolTreeNode::sortChildrenRecursively(bool sort_by_if_type_is_known) +{ + auto comparator = [&](const std::unique_ptr& lhs, const std::unique_ptr& rhs) -> bool { + if (lhs->tag != rhs->tag) + return lhs->tag < rhs->tag; + if (sort_by_if_type_is_known && lhs->type.valid() != rhs->type.valid()) + return lhs->type.valid() > rhs->type.valid(); + if (lhs->location != rhs->location) + return lhs->location < rhs->location; + return lhs->name < rhs->name; + }; + + std::sort(m_children.begin(), m_children.end(), comparator); + + for (std::unique_ptr& child : m_children) + child->sortChildrenRecursively(sort_by_if_type_is_known); +} diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.h b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.h new file mode 100644 index 0000000000..53071914fd --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.h @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: LGPL-3.0+ + +#pragma once + +#include +#include + +#include "SymbolTreeLocation.h" + +class DebugInterface; + +// A node in a symbol tree model. +struct SymbolTreeNode +{ +public: + enum Tag + { + ROOT, + UNKNOWN_GROUP, + GROUP, + OBJECT + }; + + Tag tag = OBJECT; + ccc::MultiSymbolHandle symbol; + QString name; + SymbolTreeLocation location; + bool is_location_editable = false; + std::optional size; + ccc::NodeHandle type; + std::unique_ptr temporary_type; + ccc::AddressRange live_range; + + SymbolTreeNode() {} + ~SymbolTreeNode() {} + + SymbolTreeNode(const SymbolTreeNode& rhs) = delete; + SymbolTreeNode& operator=(const SymbolTreeNode& rhs) = delete; + + SymbolTreeNode(SymbolTreeNode&& rhs) = delete; + SymbolTreeNode& operator=(SymbolTreeNode&& rhs) = delete; + + // Generated from VM state, to be updated regularly. + const QVariant& value() const; + const QString& display_value() const; + std::optional liveness(); + + // Read the value from the VM memory, update liveness information, and + // generate a display string. Returns true if the data changed. + bool readFromVM(DebugInterface& cpu, const ccc::SymbolDatabase& database); + + // Write the value back to the VM memory. Returns true on success. + bool writeToVM(QVariant value, DebugInterface& cpu, const ccc::SymbolDatabase& database); + + QVariant readValueAsVariant(const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database) const; + bool writeValueFromVariant(QVariant value, const ccc::ast::Node& physical_type, DebugInterface& cpu) const; + + bool updateDisplayString(DebugInterface& cpu, const ccc::SymbolDatabase& database); + QString generateDisplayString(const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database, s32 depth) const; + + bool updateLiveness(DebugInterface& cpu); + + bool anySymbolsValid(const ccc::SymbolDatabase& database) const; + + const SymbolTreeNode* parent() const; + + const std::vector>& children() const; + bool childrenFetched() const; + void setChildren(std::vector> new_children); + void insertChildren(std::vector> new_children); + void emplaceChild(std::unique_ptr new_child); + void clearChildren(); + + void sortChildrenRecursively(bool sort_by_if_type_is_known); + +protected: + QVariant m_value; + QString m_display_value; + std::optional m_liveness; + + SymbolTreeNode* m_parent = nullptr; + std::vector> m_children; + bool m_children_fetched = false; +}; diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidget.ui b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidget.ui new file mode 100644 index 0000000000..4fb07245f4 --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidget.ui @@ -0,0 +1,86 @@ + + + SymbolTreeWidget + + + + 0 + 0 + 419 + 300 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + + + + + Refresh + + + + + + + Filter + + + + + + + + 26 + 16777215 + + + + + + + + + + + + + 26 + 16777215 + + + + - + + + + + + + + + + diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp new file mode 100644 index 0000000000..d920b9861a --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp @@ -0,0 +1,1025 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "SymbolTreeWidgets.h" + +#include +#include +#include +#include +#include + +#include "NewSymbolDialogs.h" +#include "SymbolTreeDelegates.h" + +static bool testName(const QString& name, const QString& filter); + +SymbolTreeWidget::SymbolTreeWidget(u32 flags, s32 symbol_address_alignment, DebugInterface& cpu, QWidget* parent) + : QWidget(parent) + , m_cpu(cpu) + , m_flags(flags) + , m_symbol_address_alignment(symbol_address_alignment) +{ + m_ui.setupUi(this); + + setupMenu(); + + connect(m_ui.refreshButton, &QPushButton::clicked, this, &SymbolTreeWidget::reset); + connect(m_ui.filterBox, &QLineEdit::textEdited, this, &SymbolTreeWidget::reset); + + connect(m_ui.newButton, &QPushButton::clicked, this, &SymbolTreeWidget::onNewButtonPressed); + connect(m_ui.deleteButton, &QPushButton::clicked, this, &SymbolTreeWidget::onDeleteButtonPressed); + + connect(m_ui.treeView->verticalScrollBar(), &QScrollBar::valueChanged, this, &SymbolTreeWidget::updateVisibleNodes); + + m_ui.treeView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_ui.treeView, &QTreeView::customContextMenuRequested, this, &SymbolTreeWidget::openMenu); + connect(m_ui.treeView, &QTreeView::expanded, this, &SymbolTreeWidget::updateVisibleNodes); +} + +SymbolTreeWidget::~SymbolTreeWidget() = default; + +void SymbolTreeWidget::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + + updateVisibleNodes(); +} + +void SymbolTreeWidget::updateModel() +{ + if (!m_model || m_model->needsReset()) + reset(); + + updateVisibleNodes(); +} + +void SymbolTreeWidget::reset() +{ + if (!m_model) + setupTree(); + + m_ui.treeView->setColumnHidden(SymbolTreeModel::SIZE, !m_show_size_column || !m_show_size_column->isChecked()); + + m_cpu.GetSymbolGuardian().UpdateFunctionHashes(m_cpu); + + SymbolFilters filters; + std::unique_ptr root; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) -> void { + filters.group_by_module = m_group_by_module && m_group_by_module->isChecked(); + filters.group_by_section = m_group_by_section && m_group_by_section->isChecked(); + filters.group_by_source_file = m_group_by_source_file && m_group_by_source_file->isChecked(); + filters.string = m_ui.filterBox->text(); + + root = buildTree(filters, database); + }); + + if (root) + { + root->sortChildrenRecursively(m_sort_by_if_type_is_known && m_sort_by_if_type_is_known->isChecked()); + m_model->reset(std::move(root)); + + // Read the initial values for visible nodes. + updateVisibleNodes(); + + if (!filters.string.isEmpty()) + expandGroups(QModelIndex()); + } +} + +void SymbolTreeWidget::updateVisibleNodes() +{ + if (!m_model) + return; + + QModelIndex index = m_ui.treeView->indexAt(m_ui.treeView->rect().topLeft()); + while (m_ui.treeView->visualRect(index).intersects(m_ui.treeView->viewport()->rect())) + { + m_model->setData(index, QVariant(), SymbolTreeModel::UPDATE_FROM_MEMORY_ROLE); + index = m_ui.treeView->indexBelow(index); + } + + m_ui.treeView->update(); +} + +void SymbolTreeWidget::expandGroups(QModelIndex index) +{ + if (!m_model) + return; + + SymbolTreeNode* node = m_model->nodeFromIndex(index); + if (node->tag == SymbolTreeNode::OBJECT) + return; + + m_ui.treeView->expand(index); + + int child_count = m_model->rowCount(index); + for (int i = 0; i < child_count; i++) + { + QModelIndex child = m_model->index(i, 0, index); + expandGroups(child); + } +} + +void SymbolTreeWidget::setupTree() +{ + m_model = new SymbolTreeModel(m_cpu, this); + m_ui.treeView->setModel(m_model); + + auto location_delegate = new SymbolTreeLocationDelegate(m_cpu, m_symbol_address_alignment, this); + m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::LOCATION, location_delegate); + + auto type_delegate = new SymbolTreeTypeDelegate(m_cpu, this); + m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::TYPE, type_delegate); + + auto value_delegate = new SymbolTreeValueDelegate(m_cpu, this); + m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::VALUE, value_delegate); + + m_ui.treeView->setAlternatingRowColors(true); + m_ui.treeView->setEditTriggers(QTreeView::AllEditTriggers); + + configureColumns(); + + connect(m_ui.treeView, &QTreeView::pressed, this, &SymbolTreeWidget::onTreeViewClicked); +} + +std::unique_ptr SymbolTreeWidget::buildTree(const SymbolFilters& filters, const ccc::SymbolDatabase& database) +{ + std::vector symbols = getSymbols(filters.string, database); + + auto source_file_comparator = [](const SymbolWork& lhs, const SymbolWork& rhs) -> bool { + if (lhs.source_file) + return rhs.source_file && lhs.source_file->handle() < rhs.source_file->handle(); + else + return rhs.source_file; + }; + + auto section_comparator = [](const SymbolWork& lhs, const SymbolWork& rhs) -> bool { + if (lhs.section) + return rhs.section && lhs.section->handle() < rhs.section->handle(); + else + return rhs.section; + }; + + auto module_comparator = [](const SymbolWork& lhs, const SymbolWork& rhs) -> bool { + if (lhs.module_symbol) + return rhs.module_symbol && lhs.module_symbol->handle() < rhs.module_symbol->handle(); + else + return rhs.module_symbol; + }; + + // Sort all of the symbols so that we can iterate over them in order and + // build a tree. + if (filters.group_by_source_file) + std::stable_sort(symbols.begin(), symbols.end(), source_file_comparator); + + if (filters.group_by_section) + std::stable_sort(symbols.begin(), symbols.end(), section_comparator); + + if (filters.group_by_module) + std::stable_sort(symbols.begin(), symbols.end(), module_comparator); + + std::unique_ptr root = std::make_unique(); + root->tag = SymbolTreeNode::ROOT; + + SymbolTreeNode* source_file_node = nullptr; + SymbolTreeNode* section_node = nullptr; + SymbolTreeNode* module_node = nullptr; + + const SymbolWork* source_file_work = nullptr; + const SymbolWork* section_work = nullptr; + const SymbolWork* module_work = nullptr; + + // Build the tree. Whenever we enounter a symbol with a different source + // file, section or module, because they're all sorted we know that we have + // to create a new group node (if we're grouping by that attribute). + for (SymbolWork& work : symbols) + { + std::unique_ptr node = buildNode(work, database); + + if (filters.group_by_source_file) + { + node = groupBySourceFile(std::move(node), work, source_file_node, source_file_work, filters); + if (!node) + continue; + } + + if (filters.group_by_section) + { + node = groupBySection(std::move(node), work, section_node, section_work, filters); + if (!node) + continue; + } + + if (filters.group_by_module) + { + node = groupByModule(std::move(node), work, module_node, module_work, filters); + if (!node) + continue; + } + + root->emplaceChild(std::move(node)); + } + + return root; +} + +std::unique_ptr SymbolTreeWidget::groupBySourceFile( + std::unique_ptr child, + const SymbolWork& child_work, + SymbolTreeNode*& prev_group, + const SymbolWork*& prev_work, + const SymbolFilters& filters) +{ + bool group_exists = + prev_group && + child_work.source_file == prev_work->source_file && + (!filters.group_by_section || child_work.section == prev_work->section) && + (!filters.group_by_module || child_work.module_symbol == prev_work->module_symbol); + if (group_exists) + { + prev_group->emplaceChild(std::move(child)); + return nullptr; + } + + std::unique_ptr group_node = std::make_unique(); + if (child_work.source_file) + { + group_node->tag = SymbolTreeNode::GROUP; + if (!child_work.source_file->command_line_path.empty()) + group_node->name = QString::fromStdString(child_work.source_file->command_line_path); + else + group_node->name = QString::fromStdString(child_work.source_file->name()); + if (child_work.source_file->address().valid()) + group_node->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, child_work.source_file->address().value); + } + else + { + group_node->tag = SymbolTreeNode::UNKNOWN_GROUP; + group_node->name = tr("(unknown source file)"); + } + + group_node->emplaceChild(std::move(child)); + child = std::move(group_node); + + prev_group = child.get(); + prev_work = &child_work; + + return child; +} + +std::unique_ptr SymbolTreeWidget::groupBySection( + std::unique_ptr child, + const SymbolWork& child_work, + SymbolTreeNode*& prev_group, + const SymbolWork*& prev_work, + const SymbolFilters& filters) +{ + bool group_exists = + prev_group && + child_work.section == prev_work->section && + (!filters.group_by_module || child_work.module_symbol == prev_work->module_symbol); + if (group_exists) + { + prev_group->emplaceChild(std::move(child)); + return nullptr; + } + + std::unique_ptr group_node = std::make_unique(); + if (child_work.section) + { + group_node->tag = SymbolTreeNode::GROUP; + group_node->name = QString::fromStdString(child_work.section->name()); + if (child_work.section->address().valid()) + group_node->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, child_work.section->address().value); + } + else + { + group_node->tag = SymbolTreeNode::UNKNOWN_GROUP; + group_node->name = tr("(unknown section)"); + } + + group_node->emplaceChild(std::move(child)); + child = std::move(group_node); + + prev_group = child.get(); + prev_work = &child_work; + + return child; +} + +std::unique_ptr SymbolTreeWidget::groupByModule( + std::unique_ptr child, + const SymbolWork& child_work, + SymbolTreeNode*& prev_group, + const SymbolWork*& prev_work, + const SymbolFilters& filters) +{ + bool group_exists = + prev_group && + child_work.module_symbol == prev_work->module_symbol; + if (group_exists) + { + prev_group->emplaceChild(std::move(child)); + return nullptr; + } + + std::unique_ptr group_node = std::make_unique(); + if (child_work.module_symbol) + { + + group_node->tag = SymbolTreeNode::GROUP; + group_node->name = QString::fromStdString(child_work.module_symbol->name()); + if (child_work.module_symbol->address().valid()) + group_node->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, child_work.module_symbol->address().value); + if (child_work.module_symbol->is_irx) + { + s32 major = child_work.module_symbol->version_major; + s32 minor = child_work.module_symbol->version_minor; + group_node->name += QString(" v%1.%2").arg(major).arg(minor); + } + } + else + { + group_node->tag = SymbolTreeNode::UNKNOWN_GROUP; + group_node->name = tr("(unknown module)"); + } + + group_node->emplaceChild(std::move(child)); + child = std::move(group_node); + + prev_group = child.get(); + prev_work = &child_work; + + return child; +} + +void SymbolTreeWidget::setupMenu() +{ + m_context_menu = new QMenu(this); + + QAction* copy_name = new QAction(tr("Copy Name"), this); + connect(copy_name, &QAction::triggered, this, &SymbolTreeWidget::onCopyName); + m_context_menu->addAction(copy_name); + + QAction* copy_location = new QAction(tr("Copy Location"), this); + connect(copy_location, &QAction::triggered, this, &SymbolTreeWidget::onCopyLocation); + m_context_menu->addAction(copy_location); + + m_context_menu->addSeparator(); + + m_rename_symbol = new QAction(tr("Rename Symbol"), this); + connect(m_rename_symbol, &QAction::triggered, this, &SymbolTreeWidget::onRenameSymbol); + m_context_menu->addAction(m_rename_symbol); + + m_context_menu->addSeparator(); + + m_go_to_in_disassembly = new QAction(tr("Go to in Disassembly"), this); + connect(m_go_to_in_disassembly, &QAction::triggered, this, &SymbolTreeWidget::onGoToInDisassembly); + m_context_menu->addAction(m_go_to_in_disassembly); + + m_m_go_to_in_memory_view = new QAction(tr("Go to in Memory View"), this); + connect(m_m_go_to_in_memory_view, &QAction::triggered, this, &SymbolTreeWidget::onGoToInMemoryView); + m_context_menu->addAction(m_m_go_to_in_memory_view); + + m_show_size_column = new QAction(tr("Show Size Column"), this); + m_show_size_column->setCheckable(true); + connect(m_show_size_column, &QAction::triggered, this, &SymbolTreeWidget::reset); + m_context_menu->addAction(m_show_size_column); + + if (m_flags & ALLOW_GROUPING) + { + m_context_menu->addSeparator(); + + m_group_by_module = new QAction(tr("Group by Module"), this); + m_group_by_module->setCheckable(true); + if (m_cpu.getCpuType() == BREAKPOINT_IOP) + m_group_by_module->setChecked(true); + connect(m_group_by_module, &QAction::toggled, this, &SymbolTreeWidget::reset); + m_context_menu->addAction(m_group_by_module); + + m_group_by_section = new QAction(tr("Group by Section"), this); + m_group_by_section->setCheckable(true); + connect(m_group_by_section, &QAction::toggled, this, &SymbolTreeWidget::reset); + m_context_menu->addAction(m_group_by_section); + + m_group_by_source_file = new QAction(tr("Group by Source File"), this); + m_group_by_source_file->setCheckable(true); + connect(m_group_by_source_file, &QAction::toggled, this, &SymbolTreeWidget::reset); + m_context_menu->addAction(m_group_by_source_file); + } + + if (m_flags & ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN) + { + m_context_menu->addSeparator(); + + m_sort_by_if_type_is_known = new QAction(tr("Sort by if type is known"), this); + m_sort_by_if_type_is_known->setCheckable(true); + m_context_menu->addAction(m_sort_by_if_type_is_known); + + connect(m_sort_by_if_type_is_known, &QAction::toggled, this, &SymbolTreeWidget::reset); + } + + if (m_flags & ALLOW_TYPE_ACTIONS) + { + m_context_menu->addSeparator(); + + m_reset_children = new QAction(tr("Reset children"), this); + m_context_menu->addAction(m_reset_children); + + m_change_type_temporarily = new QAction(tr("Change type temporarily"), this); + m_context_menu->addAction(m_change_type_temporarily); + + connect(m_reset_children, &QAction::triggered, this, &SymbolTreeWidget::onResetChildren); + connect(m_change_type_temporarily, &QAction::triggered, this, &SymbolTreeWidget::onChangeTypeTemporarily); + } +} + +void SymbolTreeWidget::openMenu(QPoint pos) +{ + SymbolTreeNode* node = currentNode(); + + bool node_is_object = node->tag == SymbolTreeNode::OBJECT; + bool node_is_symbol = node->symbol.valid(); + bool node_is_memory = node->location.type == SymbolTreeLocation::MEMORY; + + m_rename_symbol->setEnabled(node_is_symbol); + m_go_to_in_disassembly->setEnabled(node_is_memory); + m_m_go_to_in_memory_view->setEnabled(node_is_memory); + + if (m_reset_children) + m_reset_children->setEnabled(node_is_object); + + if (m_change_type_temporarily) + m_change_type_temporarily->setEnabled(node_is_object); + + m_context_menu->exec(m_ui.treeView->viewport()->mapToGlobal(pos)); +} + +void SymbolTreeWidget::onDeleteButtonPressed() +{ + SymbolTreeNode* node = currentNode(); + if (!node) + return; + + if (!node->symbol.valid()) + return; + + if (QMessageBox::question(this, tr("Confirm Deletion"), tr("Delete '%1'?").arg(node->name)) != QMessageBox::Yes) + return; + + m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { + node->symbol.destroy_symbol(database, true); + }); + + reset(); +} + +void SymbolTreeWidget::onCopyName() +{ + SymbolTreeNode* node = currentNode(); + if (!node) + return; + + QApplication::clipboard()->setText(node->name); +} + +void SymbolTreeWidget::onCopyLocation() +{ + SymbolTreeNode* node = currentNode(); + if (!node) + return; + + QApplication::clipboard()->setText(node->location.toString(m_cpu)); +} + +void SymbolTreeWidget::onRenameSymbol() +{ + SymbolTreeNode* node = currentNode(); + if (!node || !node->symbol.valid()) + return; + + QString title = tr("Rename Symbol"); + QString label = tr("Name:"); + + QString text; + m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + const ccc::Symbol* symbol = node->symbol.lookup_symbol(database); + if (!symbol || !symbol->address().valid()) + return; + + text = QString::fromStdString(symbol->name()); + }); + + bool ok; + std::string name = QInputDialog::getText(this, title, label, QLineEdit::Normal, text, &ok).toStdString(); + if (!ok) + return; + + m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { + node->symbol.rename_symbol(name, database); + }); +} + +void SymbolTreeWidget::onGoToInDisassembly() +{ + SymbolTreeNode* node = currentNode(); + if (!node) + return; + + emit goToInDisassembly(node->location.address); +} + +void SymbolTreeWidget::onGoToInMemoryView() +{ + SymbolTreeNode* node = currentNode(); + if (!node) + return; + + emit goToInMemoryView(node->location.address); +} + +void SymbolTreeWidget::onResetChildren() +{ + if (!m_model) + return; + + QModelIndex index = m_ui.treeView->currentIndex(); + if (!index.isValid()) + return; + + m_model->resetChildren(index); +} + +void SymbolTreeWidget::onChangeTypeTemporarily() +{ + if (!m_model) + return; + + QModelIndex index = m_ui.treeView->currentIndex(); + if (!index.isValid()) + return; + + QString title = tr("Change Type To"); + QString label = tr("Type:"); + std::optional old_type = m_model->typeFromModelIndexToString(index); + if (!old_type.has_value()) + { + QMessageBox::warning(this, tr("Cannot Change Type"), tr("That node cannot have a type.")); + return; + } + + bool ok; + QString type_string = QInputDialog::getText(this, title, label, QLineEdit::Normal, *old_type, &ok); + if (!ok) + return; + + std::optional error_message = m_model->changeTypeTemporarily(index, type_string.toStdString()); + if (error_message.has_value() && !error_message->isEmpty()) + QMessageBox::warning(this, tr("Cannot Change Type"), *error_message); +} + +void SymbolTreeWidget::onTreeViewClicked(const QModelIndex& index) +{ + if (!index.isValid()) + return; + + SymbolTreeNode* node = m_model->nodeFromIndex(index); + if (!node) + return; + + switch (index.column()) + { + case SymbolTreeModel::NAME: + emit nameColumnClicked(node->location.address); + break; + case SymbolTreeModel::LOCATION: + emit locationColumnClicked(node->location.address); + break; + } +} + +SymbolTreeNode* SymbolTreeWidget::currentNode() +{ + if (!m_model) + return nullptr; + + QModelIndex index = m_ui.treeView->currentIndex(); + return m_model->nodeFromIndex(index); +} + +// ***************************************************************************** + +FunctionTreeWidget::FunctionTreeWidget(DebugInterface& cpu, QWidget* parent) + : SymbolTreeWidget(ALLOW_GROUPING, 4, cpu, parent) +{ +} + +FunctionTreeWidget::~FunctionTreeWidget() = default; + +std::vector FunctionTreeWidget::getSymbols( + const QString& filter, const ccc::SymbolDatabase& database) +{ + std::vector symbols; + + for (const ccc::Function& function : database.functions) + { + if (!function.address().valid()) + continue; + + QString name = QString::fromStdString(function.name()); + if (!testName(name, filter)) + continue; + + SymbolWork& work = symbols.emplace_back(); + + work.name = std::move(name); + work.descriptor = ccc::FUNCTION; + work.symbol = &function; + + work.module_symbol = database.modules.symbol_from_handle(function.module_handle()); + work.section = database.sections.symbol_overlapping_address(function.address()); + work.source_file = database.source_files.symbol_from_handle(function.source_file()); + } + + return symbols; +} + +std::unique_ptr FunctionTreeWidget::buildNode( + SymbolWork& work, const ccc::SymbolDatabase& database) const +{ + const ccc::Function& function = static_cast(*work.symbol); + + std::unique_ptr node = std::make_unique(); + node->name = std::move(work.name); + node->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, function.address().value); + node->size = function.size(); + node->symbol = ccc::MultiSymbolHandle(function); + + for (auto address_handle : database.labels.handles_from_address_range(function.address_range())) + { + const ccc::Label* label = database.labels.symbol_from_handle(address_handle.second); + if (!label || label->address() == function.address()) + continue; + + std::unique_ptr label_node = std::make_unique(); + label_node->name = QString::fromStdString(label->name()); + label_node->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, label->address().value); + node->emplaceChild(std::move(label_node)); + } + + return node; +} + +void FunctionTreeWidget::configureColumns() +{ + m_ui.treeView->setColumnHidden(SymbolTreeModel::NAME, false); + m_ui.treeView->setColumnHidden(SymbolTreeModel::LOCATION, false); + m_ui.treeView->setColumnHidden(SymbolTreeModel::TYPE, true); + m_ui.treeView->setColumnHidden(SymbolTreeModel::LIVENESS, true); + m_ui.treeView->setColumnHidden(SymbolTreeModel::VALUE, true); + + m_ui.treeView->header()->setSectionResizeMode(SymbolTreeModel::NAME, QHeaderView::Stretch); + + m_ui.treeView->header()->setStretchLastSection(false); +} + +void FunctionTreeWidget::onNewButtonPressed() +{ + NewFunctionDialog* dialog = new NewFunctionDialog(m_cpu, this); + if (dialog->exec() == QDialog::Accepted) + reset(); +} + +// ***************************************************************************** + +GlobalVariableTreeWidget::GlobalVariableTreeWidget(DebugInterface& cpu, QWidget* parent) + : SymbolTreeWidget(ALLOW_GROUPING | ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN | ALLOW_TYPE_ACTIONS, 1, cpu, parent) +{ +} + +GlobalVariableTreeWidget::~GlobalVariableTreeWidget() = default; + +std::vector GlobalVariableTreeWidget::getSymbols( + const QString& filter, const ccc::SymbolDatabase& database) +{ + std::vector symbols; + + for (const ccc::GlobalVariable& global_variable : database.global_variables) + { + if (!global_variable.address().valid()) + continue; + + QString name = QString::fromStdString(global_variable.name()); + if (!testName(name, filter)) + continue; + + SymbolWork& work = symbols.emplace_back(); + + work.name = std::move(name); + work.descriptor = ccc::GLOBAL_VARIABLE; + work.symbol = &global_variable; + + work.module_symbol = database.modules.symbol_from_handle(global_variable.module_handle()); + work.section = database.sections.symbol_overlapping_address(global_variable.address()); + work.source_file = database.source_files.symbol_from_handle(global_variable.source_file()); + } + + // We also include static local variables in the global variable tree + // because they have global storage. Why not. + for (const ccc::LocalVariable& local_variable : database.local_variables) + { + if (!std::holds_alternative(local_variable.storage)) + continue; + + if (!local_variable.address().valid()) + continue; + + ccc::FunctionHandle function_handle = local_variable.function(); + const ccc::Function* function = database.functions.symbol_from_handle(function_handle); + + QString function_name; + if (function) + function_name = QString::fromStdString(function->name()); + else + function_name = tr("unknown function"); + + QString name = QString("%1 (%2)") + .arg(QString::fromStdString(local_variable.name())) + .arg(function_name); + if (!testName(name, filter)) + continue; + + SymbolWork& work = symbols.emplace_back(); + + work.name = std::move(name); + work.descriptor = ccc::LOCAL_VARIABLE; + work.symbol = &local_variable; + + work.module_symbol = database.modules.symbol_from_handle(local_variable.module_handle()); + work.section = database.sections.symbol_overlapping_address(local_variable.address()); + if (function) + work.source_file = database.source_files.symbol_from_handle(function->source_file()); + } + + return symbols; +} + +std::unique_ptr GlobalVariableTreeWidget::buildNode( + SymbolWork& work, const ccc::SymbolDatabase& database) const +{ + std::unique_ptr node = std::make_unique(); + node->name = std::move(work.name); + + switch (work.descriptor) + { + case ccc::GLOBAL_VARIABLE: + { + const ccc::GlobalVariable& global_variable = static_cast(*work.symbol); + + if (global_variable.type()) + node->type = ccc::NodeHandle(global_variable, global_variable.type()); + node->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, global_variable.address().value); + node->is_location_editable = true; + node->size = global_variable.size(); + node->symbol = ccc::MultiSymbolHandle(global_variable); + + break; + } + case ccc::LOCAL_VARIABLE: + { + const ccc::LocalVariable& local_variable = static_cast(*work.symbol); + + if (local_variable.type()) + node->type = ccc::NodeHandle(local_variable, local_variable.type()); + node->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, local_variable.address().value); + node->is_location_editable = true; + node->size = local_variable.size(); + node->symbol = ccc::MultiSymbolHandle(local_variable); + + break; + } + default: + { + } + } + + return node; +} + +void GlobalVariableTreeWidget::configureColumns() +{ + m_ui.treeView->setColumnHidden(SymbolTreeModel::NAME, false); + m_ui.treeView->setColumnHidden(SymbolTreeModel::LOCATION, false); + m_ui.treeView->setColumnHidden(SymbolTreeModel::TYPE, false); + m_ui.treeView->setColumnHidden(SymbolTreeModel::LIVENESS, true); + m_ui.treeView->setColumnHidden(SymbolTreeModel::VALUE, false); + + m_ui.treeView->header()->setSectionResizeMode(SymbolTreeModel::NAME, QHeaderView::Stretch); + m_ui.treeView->header()->setSectionResizeMode(SymbolTreeModel::TYPE, QHeaderView::Stretch); + m_ui.treeView->header()->setSectionResizeMode(SymbolTreeModel::VALUE, QHeaderView::Stretch); + + m_ui.treeView->header()->setStretchLastSection(false); +} + +void GlobalVariableTreeWidget::onNewButtonPressed() +{ + NewGlobalVariableDialog* dialog = new NewGlobalVariableDialog(m_cpu, this); + if (dialog->exec() == QDialog::Accepted) + reset(); +} + +// ***************************************************************************** + +LocalVariableTreeWidget::LocalVariableTreeWidget(DebugInterface& cpu, QWidget* parent) + : SymbolTreeWidget(ALLOW_TYPE_ACTIONS, 1, cpu, parent) +{ +} + +LocalVariableTreeWidget::~LocalVariableTreeWidget() = default; + +std::vector LocalVariableTreeWidget::getSymbols( + const QString& filter, const ccc::SymbolDatabase& database) +{ + u32 program_counter = m_cpu.getPC(); + const ccc::Function* function = database.functions.symbol_overlapping_address(program_counter); + if (!function || !function->local_variables().has_value()) + return std::vector(); + + m_caller_stack_pointer = m_cpu.getCallerStackPointer(*function); + + std::vector symbols; + + for (const ccc::LocalVariableHandle local_variable_handle : *function->local_variables()) + { + const ccc::LocalVariable* local_variable = database.local_variables.symbol_from_handle(local_variable_handle); + if (!local_variable) + continue; + + if (std::holds_alternative(local_variable->storage) && !local_variable->address().valid()) + continue; + + if (std::holds_alternative(local_variable->storage) && !m_caller_stack_pointer.has_value()) + continue; + + QString name = QString::fromStdString(local_variable->name()); + if (!testName(name, filter)) + continue; + + SymbolWork& work = symbols.emplace_back(); + + work.name = std::move(name); + work.descriptor = ccc::LOCAL_VARIABLE; + work.symbol = local_variable; + + work.module_symbol = database.modules.symbol_from_handle(local_variable->module_handle()); + work.section = database.sections.symbol_overlapping_address(local_variable->address()); + work.source_file = database.source_files.symbol_from_handle(function->source_file()); + } + + return symbols; +} + +std::unique_ptr LocalVariableTreeWidget::buildNode( + SymbolWork& work, const ccc::SymbolDatabase& database) const +{ + const ccc::LocalVariable& local_variable = static_cast(*work.symbol); + + std::unique_ptr node = std::make_unique(); + node->name = QString::fromStdString(local_variable.name()); + if (local_variable.type()) + node->type = ccc::NodeHandle(local_variable, local_variable.type()); + node->live_range = local_variable.live_range; + node->symbol = ccc::MultiSymbolHandle(local_variable); + + if (const ccc::GlobalStorage* storage = std::get_if(&local_variable.storage)) + node->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, local_variable.address().value); + else if (const ccc::RegisterStorage* storage = std::get_if(&local_variable.storage)) + node->location = SymbolTreeLocation(SymbolTreeLocation::REGISTER, storage->dbx_register_number); + else if (const ccc::StackStorage* storage = std::get_if(&local_variable.storage)) + node->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, *m_caller_stack_pointer + storage->stack_pointer_offset); + node->size = local_variable.size(); + + return node; +} + +void LocalVariableTreeWidget::configureColumns() +{ + m_ui.treeView->setColumnHidden(SymbolTreeModel::NAME, false); + m_ui.treeView->setColumnHidden(SymbolTreeModel::LOCATION, false); + m_ui.treeView->setColumnHidden(SymbolTreeModel::TYPE, false); + m_ui.treeView->setColumnHidden(SymbolTreeModel::LIVENESS, false); + m_ui.treeView->setColumnHidden(SymbolTreeModel::VALUE, false); + + m_ui.treeView->header()->setSectionResizeMode(SymbolTreeModel::NAME, QHeaderView::Stretch); + m_ui.treeView->header()->setSectionResizeMode(SymbolTreeModel::TYPE, QHeaderView::Stretch); + m_ui.treeView->header()->setSectionResizeMode(SymbolTreeModel::VALUE, QHeaderView::Stretch); + + m_ui.treeView->header()->setStretchLastSection(false); +} + +void LocalVariableTreeWidget::onNewButtonPressed() +{ + NewLocalVariableDialog* dialog = new NewLocalVariableDialog(m_cpu, this); + if (dialog->exec() == QDialog::Accepted) + reset(); +} + +// ***************************************************************************** + +ParameterVariableTreeWidget::ParameterVariableTreeWidget(DebugInterface& cpu, QWidget* parent) + : SymbolTreeWidget(ALLOW_TYPE_ACTIONS, 1, cpu, parent) +{ +} + +ParameterVariableTreeWidget::~ParameterVariableTreeWidget() = default; + +std::vector ParameterVariableTreeWidget::getSymbols( + const QString& filter, const ccc::SymbolDatabase& database) +{ + std::vector symbols; + + u32 program_counter = m_cpu.getPC(); + const ccc::Function* function = database.functions.symbol_overlapping_address(program_counter); + if (!function || !function->parameter_variables().has_value()) + return std::vector(); + + m_caller_stack_pointer = m_cpu.getCallerStackPointer(*function); + + for (const ccc::ParameterVariableHandle parameter_variable_handle : *function->parameter_variables()) + { + const ccc::ParameterVariable* parameter_variable = database.parameter_variables.symbol_from_handle(parameter_variable_handle); + if (!parameter_variable) + continue; + + if (std::holds_alternative(parameter_variable->storage) && !m_caller_stack_pointer.has_value()) + continue; + + QString name = QString::fromStdString(parameter_variable->name()); + if (!testName(name, filter)) + continue; + + ccc::FunctionHandle function_handle = parameter_variable->function(); + const ccc::Function* function = database.functions.symbol_from_handle(function_handle); + + SymbolWork& work = symbols.emplace_back(); + + work.name = std::move(name); + work.descriptor = ccc::PARAMETER_VARIABLE; + work.symbol = parameter_variable; + + work.module_symbol = database.modules.symbol_from_handle(parameter_variable->module_handle()); + work.section = database.sections.symbol_overlapping_address(parameter_variable->address()); + if (function) + work.source_file = database.source_files.symbol_from_handle(function->source_file()); + } + + return symbols; +} + +std::unique_ptr ParameterVariableTreeWidget::buildNode( + SymbolWork& work, const ccc::SymbolDatabase& database) const +{ + const ccc::ParameterVariable& parameter_variable = static_cast(*work.symbol); + + std::unique_ptr node = std::make_unique(); + node->name = QString::fromStdString(parameter_variable.name()); + if (parameter_variable.type()) + node->type = ccc::NodeHandle(parameter_variable, parameter_variable.type()); + node->symbol = ccc::MultiSymbolHandle(parameter_variable); + + if (const ccc::RegisterStorage* storage = std::get_if(¶meter_variable.storage)) + node->location = SymbolTreeLocation(SymbolTreeLocation::REGISTER, storage->dbx_register_number); + else if (const ccc::StackStorage* storage = std::get_if(¶meter_variable.storage)) + node->location = SymbolTreeLocation(SymbolTreeLocation::MEMORY, *m_caller_stack_pointer + storage->stack_pointer_offset); + node->size = parameter_variable.size(); + + return node; +} + +void ParameterVariableTreeWidget::configureColumns() +{ + m_ui.treeView->setColumnHidden(SymbolTreeModel::NAME, false); + m_ui.treeView->setColumnHidden(SymbolTreeModel::LOCATION, false); + m_ui.treeView->setColumnHidden(SymbolTreeModel::TYPE, false); + m_ui.treeView->setColumnHidden(SymbolTreeModel::LIVENESS, true); + m_ui.treeView->setColumnHidden(SymbolTreeModel::VALUE, false); + + m_ui.treeView->header()->setSectionResizeMode(SymbolTreeModel::NAME, QHeaderView::Stretch); + m_ui.treeView->header()->setSectionResizeMode(SymbolTreeModel::TYPE, QHeaderView::Stretch); + m_ui.treeView->header()->setSectionResizeMode(SymbolTreeModel::VALUE, QHeaderView::Stretch); + + m_ui.treeView->header()->setStretchLastSection(false); +} + +void ParameterVariableTreeWidget::onNewButtonPressed() +{ + NewParameterVariableDialog* dialog = new NewParameterVariableDialog(m_cpu, this); + if (dialog->exec() == QDialog::Accepted) + reset(); +} + +static bool testName(const QString& name, const QString& filter) +{ + return filter.isEmpty() || name.contains(filter, Qt::CaseInsensitive); +} diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.h b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.h new file mode 100644 index 0000000000..6c0747c15b --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.h @@ -0,0 +1,213 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include +#include "SymbolTreeModel.h" + +#include "ui_SymbolTreeWidget.h" + +struct SymbolFilters; + +// A symbol tree widget with its associated refresh button, filter box and +// right-click menu. Supports grouping, sorting and various other settings. +class SymbolTreeWidget : public QWidget +{ + Q_OBJECT + +public: + virtual ~SymbolTreeWidget(); + + void updateModel(); + void reset(); + void updateVisibleNodes(); + void expandGroups(QModelIndex index); + +signals: + void goToInDisassembly(u32 address); + void goToInMemoryView(u32 address); + void nameColumnClicked(u32 address); + void locationColumnClicked(u32 address); + +protected: + struct SymbolWork + { + QString name; + ccc::SymbolDescriptor descriptor; + const ccc::Symbol* symbol = nullptr; + const ccc::Module* module_symbol = nullptr; + const ccc::Section* section = nullptr; + const ccc::SourceFile* source_file = nullptr; + }; + + explicit SymbolTreeWidget(u32 flags, s32 symbol_address_alignment, DebugInterface& cpu, QWidget* parent = nullptr); + + void resizeEvent(QResizeEvent* event) override; + + void setupTree(); + std::unique_ptr buildTree(const SymbolFilters& filters, const ccc::SymbolDatabase& database); + + std::unique_ptr groupBySourceFile( + std::unique_ptr child, + const SymbolWork& child_work, + SymbolTreeNode*& prev_group, + const SymbolWork*& prev_work, + const SymbolFilters& filters); + + std::unique_ptr groupBySection( + std::unique_ptr child, + const SymbolWork& child_work, + SymbolTreeNode*& prev_group, + const SymbolWork*& prev_work, + const SymbolFilters& filters); + + std::unique_ptr groupByModule( + std::unique_ptr child, + const SymbolWork& child_work, + SymbolTreeNode*& prev_group, + const SymbolWork*& prev_work, + const SymbolFilters& filters); + + void setupMenu(); + void openMenu(QPoint pos); + + virtual std::vector getSymbols( + const QString& filter, const ccc::SymbolDatabase& database) = 0; + + virtual std::unique_ptr buildNode( + SymbolWork& work, const ccc::SymbolDatabase& database) const = 0; + + virtual void configureColumns() = 0; + + virtual void onNewButtonPressed() = 0; + void onDeleteButtonPressed(); + + void onCopyName(); + void onCopyLocation(); + void onRenameSymbol(); + void onGoToInDisassembly(); + void onGoToInMemoryView(); + void onResetChildren(); + void onChangeTypeTemporarily(); + + void onTreeViewClicked(const QModelIndex& index); + + SymbolTreeNode* currentNode(); + + Ui::SymbolTreeWidget m_ui; + + DebugInterface& m_cpu; + SymbolTreeModel* m_model = nullptr; + + QMenu* m_context_menu = nullptr; + QAction* m_rename_symbol = nullptr; + QAction* m_go_to_in_disassembly = nullptr; + QAction* m_m_go_to_in_memory_view = nullptr; + QAction* m_show_size_column = nullptr; + QAction* m_group_by_module = nullptr; + QAction* m_group_by_section = nullptr; + QAction* m_group_by_source_file = nullptr; + QAction* m_sort_by_if_type_is_known = nullptr; + QAction* m_reset_children = nullptr; + QAction* m_change_type_temporarily = nullptr; + + enum Flags + { + NO_SYMBOL_TREE_FLAGS = 0, + ALLOW_GROUPING = 1 << 0, + ALLOW_SORTING_BY_IF_TYPE_IS_KNOWN = 1 << 1, + ALLOW_TYPE_ACTIONS = 1 << 2 + }; + + u32 m_flags; + u32 m_symbol_address_alignment; +}; + +class FunctionTreeWidget : public SymbolTreeWidget +{ + Q_OBJECT +public: + explicit FunctionTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr); + virtual ~FunctionTreeWidget(); + +protected: + std::vector getSymbols( + const QString& filter, const ccc::SymbolDatabase& database) override; + + std::unique_ptr buildNode( + SymbolWork& work, const ccc::SymbolDatabase& database) const override; + + void configureColumns() override; + + void onNewButtonPressed() override; +}; + +class GlobalVariableTreeWidget : public SymbolTreeWidget +{ + Q_OBJECT +public: + explicit GlobalVariableTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr); + virtual ~GlobalVariableTreeWidget(); + +protected: + std::vector getSymbols( + const QString& filter, const ccc::SymbolDatabase& database) override; + + std::unique_ptr buildNode( + SymbolWork& work, const ccc::SymbolDatabase& database) const override; + + void configureColumns() override; + + void onNewButtonPressed() override; +}; + +class LocalVariableTreeWidget : public SymbolTreeWidget +{ + Q_OBJECT +public: + explicit LocalVariableTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr); + virtual ~LocalVariableTreeWidget(); + +protected: + std::vector getSymbols( + const QString& filter, const ccc::SymbolDatabase& database) override; + + std::unique_ptr buildNode( + SymbolWork& work, const ccc::SymbolDatabase& database) const override; + + void configureColumns() override; + + void onNewButtonPressed() override; + + std::optional m_caller_stack_pointer; +}; + +class ParameterVariableTreeWidget : public SymbolTreeWidget +{ + Q_OBJECT +public: + explicit ParameterVariableTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr); + virtual ~ParameterVariableTreeWidget(); + +protected: + std::vector getSymbols( + const QString& filter, const ccc::SymbolDatabase& database) override; + + std::unique_ptr buildNode( + SymbolWork& work, const ccc::SymbolDatabase& database) const override; + + void configureColumns() override; + + void onNewButtonPressed() override; + + std::optional m_caller_stack_pointer; +}; + +struct SymbolFilters +{ + bool group_by_module = false; + bool group_by_section = false; + bool group_by_source_file = false; + QString string; +}; diff --git a/pcsx2-qt/Debugger/SymbolTree/TypeString.cpp b/pcsx2-qt/Debugger/SymbolTree/TypeString.cpp new file mode 100644 index 0000000000..121f5e2dbf --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/TypeString.cpp @@ -0,0 +1,162 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: LGPL-3.0+ + +#include "TypeString.h" + +#include + +#include "common/Pcsx2Types.h" + +std::unique_ptr stringToType(std::string_view string, const ccc::SymbolDatabase& database, QString& error_out) +{ + if (string.empty()) + return nullptr; + + size_t i = string.size(); + + // Parse array subscripts and pointer characters. + std::vector components; + for (; i > 0; i--) + { + if (string[i - 1] == '*' || string[i - 1] == '&') + { + components.emplace_back(-string[i - 1]); + continue; + } + + if (string[i - 1] != ']' || i < 2) + break; + + size_t j = i - 1; + for (; j > 0; j--) + if (string[j - 1] < '0' || string[j - 1] > '9') + break; + + if (string[j - 1] != '[') + break; + + s32 element_count = atoi(&string[j]); + if (element_count < 0 || element_count > 1024 * 1024) + { + error_out = QCoreApplication::tr("Invalid array subscript."); + return nullptr; + } + + components.emplace_back(element_count); + + i = j; + } + + // Lookup the type. + std::string type_name_string(string.data(), string.data() + i); + if (type_name_string.empty()) + { + error_out = QCoreApplication::tr("No type name provided."); + return nullptr; + } + + ccc::DataTypeHandle handle = database.data_types.first_handle_from_name(type_name_string); + const ccc::DataType* data_type = database.data_types.symbol_from_handle(handle); + if (!data_type || !data_type->type()) + { + error_out = QCoreApplication::tr("Type '%1' not found.").arg(QString::fromStdString(type_name_string)); + return nullptr; + } + + std::unique_ptr result; + + // Create the AST. + std::unique_ptr type_name = std::make_unique(); + type_name->size_bytes = data_type->type()->size_bytes; + type_name->data_type_handle = data_type->handle(); + type_name->source = ccc::ast::TypeNameSource::REFERENCE; + result = std::move(type_name); + + for (i = components.size(); i > 0; i--) + { + if (components[i - 1] < 0) + { + char pointer_character = -components[i - 1]; + + std::unique_ptr pointer_or_reference = std::make_unique(); + pointer_or_reference->size_bytes = 4; + pointer_or_reference->is_pointer = pointer_character == '*'; + pointer_or_reference->value_type = std::move(result); + result = std::move(pointer_or_reference); + } + else + { + s32 element_count = components[i - 1]; + + std::unique_ptr array = std::make_unique(); + array->size_bytes = element_count * result->size_bytes; + array->element_type = std::move(result); + array->element_count = element_count; + result = std::move(array); + } + } + + return result; +} + +QString typeToString(const ccc::ast::Node* type, const ccc::SymbolDatabase& database) +{ + QString suffix; + + // Traverse through arrays, pointers and references, and build a string + // to be appended to the end of the type name. + bool done_finding_arrays_pointers = false; + while (!done_finding_arrays_pointers) + { + switch (type->descriptor) + { + case ccc::ast::ARRAY: + { + const ccc::ast::Array& array = type->as(); + suffix.prepend(QString("[%1]").arg(array.element_count)); + type = array.element_type.get(); + break; + } + case ccc::ast::POINTER_OR_REFERENCE: + { + const ccc::ast::PointerOrReference& pointer_or_reference = type->as(); + suffix.prepend(pointer_or_reference.is_pointer ? '*' : '&'); + type = pointer_or_reference.value_type.get(); + break; + } + default: + { + done_finding_arrays_pointers = true; + break; + } + } + } + + // Determine the actual type name, or at the very least the node type. + QString name; + switch (type->descriptor) + { + case ccc::ast::BUILTIN: + { + const ccc::ast::BuiltIn& built_in = type->as(); + name = ccc::ast::builtin_class_to_string(built_in.bclass); + break; + } + case ccc::ast::TYPE_NAME: + { + const ccc::ast::TypeName& type_name = type->as(); + const ccc::DataType* data_type = database.data_types.symbol_from_handle(type_name.data_type_handle); + if (data_type) + { + name = QString::fromStdString(data_type->name()); + } + break; + } + default: + { + name = ccc::ast::node_type_to_string(*type); + } + } + + return name + suffix; +} diff --git a/pcsx2-qt/Debugger/SymbolTree/TypeString.h b/pcsx2-qt/Debugger/SymbolTree/TypeString.h new file mode 100644 index 0000000000..35ecca659f --- /dev/null +++ b/pcsx2-qt/Debugger/SymbolTree/TypeString.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: LGPL-3.0+ + +#pragma once + +#include + +#include + +#include + +// Take a string e.g. "int*[3]" and generates an AST. Supports type names by +// themselves as well as pointers, references and arrays. Pointer characters +// appear in the same order as they would in C source code, however array +// subscripts appear in the opposite order, so that it is possible to specify a +// pointer to an array. +std::unique_ptr stringToType(std::string_view string, const ccc::SymbolDatabase& database, QString& error_out); + +// Opposite of stringToType. Takes an AST node and converts it to a string. +QString typeToString(const ccc::ast::Node* type, const ccc::SymbolDatabase& database); diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index 0af24df071..748973bc0f 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -48,7 +48,7 @@ %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\ccc\src %(AdditionalIncludeDirectories);$(SolutionDir)pcsx2 - %(AdditionalIncludeDirectories);$(ProjectDir)\Settings;$(ProjectDir)\GameList;$(ProjectDir)\Tools\InputRecording;$(ProjectDir)\Debugger;$(ProjectDir)\Debugger\Models + %(AdditionalIncludeDirectories);$(ProjectDir)\Settings;$(ProjectDir)\GameList;$(ProjectDir)\Tools\InputRecording;$(ProjectDir)\Debugger;$(ProjectDir)\Debugger\Models;$(ProjectDir)\Debugger\SymbolTree Use PrecompiledHeader.h PrecompiledHeader.h;%(ForcedIncludeFiles) @@ -87,6 +87,13 @@ + + + + + + + @@ -176,6 +183,13 @@ + + + + + + + @@ -261,6 +275,13 @@ + + + + + + + @@ -445,6 +466,8 @@ Document + + Document diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters index bd33d2fcce..b8a935c3b6 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj.filters +++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters @@ -28,6 +28,9 @@ {ad04f939-64a0-4039-97aa-a38b8aa46855} + + {a622b871-62ae-4b70-b9b2-6ee30ce7fa7a} + @@ -355,6 +358,27 @@ Debugger + + Debugger\SymbolTree + + + Debugger\SymbolTree + + + Debugger\SymbolTree + + + Debugger\SymbolTree + + + Debugger\SymbolTree + + + Debugger\SymbolTree + + + Debugger\SymbolTree + @@ -378,6 +402,27 @@ Debugger + + Debugger\SymbolTree + + + Debugger\SymbolTree + + + Debugger\SymbolTree + + + Debugger\SymbolTree + + + Debugger\SymbolTree + + + Debugger\SymbolTree + + + Debugger\SymbolTree + @@ -695,6 +740,12 @@ Settings + + Debugger\SymbolTree + + + Debugger\SymbolTree +