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 +