Debugger: Add symbol tree widgets for functions and variables

This adds three new tabs in the debugger: The Globals tab, the Locals
tab and the Parameters tab. In addition, it rewrites the Functions tab.

All four of these tabs use the new symbol tree widgets and the
associated model. This allows the user the inspect complex data
structures in memory with full type information.

Lastly, new dialogs have been added for creating symbols.
This commit is contained in:
chaoticgd 2024-08-26 21:57:31 +01:00 committed by Ty
parent c83cca1d87
commit 79dbc272b8
23 changed files with 5226 additions and 394 deletions

View File

@ -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

View File

@ -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)

View File

@ -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 <QtWidgets/QWidget>
@ -70,6 +71,8 @@ public slots:
void saveSavedAddressesToDebuggerSettings();
private:
void setupSymbolTrees();
std::vector<QTableWidget*> 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;
};

View File

@ -21,17 +21,27 @@
<property name="windowTitle">
<string/>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0">
<item>
<widget class="QSplitter" name="verticalSplitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<widget class="QSplitter" name="horizontalSplitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<widget class="QTabWidget" name="tabWidgetRegFunc">
<property name="minimumSize">
<size>
<width>320</width>
<height>350</height>
<width>100</width>
<height>100</height>
</size>
</property>
<property name="currentIndex">
@ -41,7 +51,7 @@
<attribute name="title">
<string>Registers</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<layout class="QHBoxLayout" name="registerLayout">
<property name="spacing">
<number>0</number>
</property>
@ -68,13 +78,7 @@
<property name="minimumSize">
<size>
<width>320</width>
<height>300</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>595</height>
<height>100</height>
</size>
</property>
<property name="font">
@ -99,88 +103,12 @@
<attribute name="title">
<string>Functions</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTreeWidget" name="treeModules">
<property name="enabled">
<bool>true</bool>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="indentation">
<number>8</number>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="columnCount">
<number>3</number>
</property>
<column>
<property name="text">
<string>Module</string>
</property>
</column>
<column>
<property name="text">
<string>Version</string>
</property>
</column>
<column>
<property name="text">
<string>Count</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QListWidget" name="listFunctions">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPushButton" name="btnRefreshFunctions">
<property name="text">
<string>Refresh</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="txtFuncSearch">
<property name="placeholderText">
<string>Filter</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabMemorySearch">
<attribute name="title">
<string>Memory Search</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<layout class="QHBoxLayout" name="memorySearchLayout">
<property name="spacing">
<number>0</number>
</property>
@ -223,8 +151,6 @@
</layout>
</widget>
</widget>
</item>
<item>
<widget class="DisassemblyWidget" name="disassemblyWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
@ -234,14 +160,8 @@
</property>
<property name="minimumSize">
<size>
<width>320</width>
<height>350</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>595</height>
<width>100</width>
<height>100</height>
</size>
</property>
<property name="font">
@ -259,10 +179,7 @@
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
</widget>
<widget class="QTabWidget" name="tabWidget">
<property name="minimumSize">
<size>
@ -270,12 +187,6 @@
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>215</height>
</size>
</property>
<property name="tabPosition">
<enum>QTabWidget::North</enum>
</property>
@ -292,7 +203,7 @@
<attribute name="title">
<string>Memory</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<layout class="QHBoxLayout" name="memoryLayout">
<property name="spacing">
<number>6</number>
</property>
@ -319,13 +230,7 @@
<property name="minimumSize">
<size>
<width>250</width>
<height>145</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
<height>100</height>
</size>
</property>
<property name="font">
@ -347,7 +252,7 @@
<attribute name="title">
<string>Breakpoints</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<layout class="QHBoxLayout" name="breakpointsLayout">
<property name="leftMargin">
<number>0</number>
</property>
@ -379,7 +284,7 @@
<attribute name="title">
<string>Threads</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<layout class="QHBoxLayout" name="threadsLayout">
<property name="spacing">
<number>0</number>
</property>
@ -417,7 +322,7 @@
<attribute name="title">
<string>Active Call Stack</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<layout class="QHBoxLayout" name="callStackLayout">
<property name="spacing">
<number>0</number>
</property>
@ -461,7 +366,7 @@
<attribute name="title">
<string>Saved Addresses</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<layout class="QHBoxLayout" name="savedAddressesLayout">
<property name="leftMargin">
<number>0</number>
</property>
@ -489,9 +394,23 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tabGlobalVariables">
<attribute name="title">
<string>Globals</string>
</attribute>
</widget>
<widget class="QWidget" name="tabLocalVariables">
<attribute name="title">
<string>Locals</string>
</attribute>
</widget>
<widget class="QWidget" name="tabParameterVariables">
<attribute name="title">
<string>Parameters</string>
</attribute>
</widget>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</widget>

View File

@ -15,6 +15,7 @@
#include <QtGui/QClipboard>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMessageBox>
#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()

View File

@ -0,0 +1,314 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewSymbolDialog</class>
<widget class="QDialog" name="NewSymbolDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>400</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>200</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>400</height>
</size>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabBar" name="storageTabBar" native="true"/>
</item>
<item>
<layout class="QFormLayout" name="form">
<item row="0" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="nameLineEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="addressLabel">
<property name="text">
<string>Address</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="addressLineEdit"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="registerLabel">
<property name="text">
<string>Register</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="registerComboBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="stackPointerOffsetLabel">
<property name="text">
<string>Stack Pointer Offset</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="stackPointerOffsetSpinBox">
<property name="maximum">
<number>268435456</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="sizeLabel">
<property name="text">
<string>Size</string>
</property>
</widget>
</item>
<item row="4" column="1">
<layout class="QVBoxLayout" name="sizeLayout">
<item>
<widget class="QRadioButton" name="fillExistingFunctionRadioButton">
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">sizeButtonGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="fillEmptySpaceRadioButton">
<property name="text">
<string/>
</property>
<property name="checked">
<bool>false</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">sizeButtonGroup</string>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="customSizeLayout">
<item>
<widget class="QRadioButton" name="customSizeRadioButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Custom</string>
</property>
<attribute name="buttonGroup">
<string notr="true">sizeButtonGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QSpinBox" name="customSizeSpinBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>false</bool>
</property>
<property name="maximum">
<number>268435456</number>
</property>
<property name="singleStep">
<number>4</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item row="5" column="0">
<widget class="QLabel" name="existingFunctionsLabel">
<property name="text">
<string>Existing Functions</string>
</property>
</widget>
</item>
<item row="5" column="1">
<layout class="QVBoxLayout" name="existingFunctionsLayout">
<item>
<widget class="QRadioButton" name="shrinkExistingRadioButton">
<property name="text">
<string>Shrink to avoid overlaps</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">existingFunctionsButtonGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="doNotModifyExistingRadioButton">
<property name="text">
<string>Do not modify</string>
</property>
<attribute name="buttonGroup">
<string notr="true">existingFunctionsButtonGroup</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item row="6" column="0">
<widget class="QLabel" name="typeLabel">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="typeLineEdit"/>
</item>
<item row="7" column="0">
<widget class="QLabel" name="functionLabel">
<property name="text">
<string>Function</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QComboBox" name="functionComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="errorMessage">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">color: red</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QTabBar</class>
<extends>QWidget</extends>
<header location="global">QtWidgets/QTabBar</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>NewSymbolDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>NewSymbolDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="sizeButtonGroup"/>
<buttongroup name="existingFunctionsButtonGroup"/>
</buttongroups>
</ui>

View File

@ -0,0 +1,652 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "NewSymbolDialogs.h"
#include <QtCore/QTimer>
#include <QtCore/QMetaMethod>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QPushButton>
#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<QWidget*>(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<u32> 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<u32> 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<u32> 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<u32> 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<u32> 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<u32> 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<ccc::SymbolSourceHandle> source = database.get_symbol_source("User-defined");
if (!source.success())
{
error_message = tr("Cannot create symbol source.");
return;
}
ccc::Result<ccc::Function*> 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<ccc::SymbolSourceHandle> source = database.get_symbol_source("User-defined");
if (!source.success())
{
error_message = tr("Cannot create symbol source.");
return;
}
ccc::Result<ccc::GlobalVariable*> 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<ccc::GlobalStorage>();
m_address = parseAddress(error_message);
if (!error_message.isEmpty())
return;
break;
}
case REGISTER_STORAGE:
{
ccc::RegisterStorage& register_storage = m_storage.emplace<ccc::RegisterStorage>();
register_storage.dbx_register_number = m_ui.registerComboBox->currentIndex();
break;
}
case STACK_STORAGE:
{
ccc::StackStorage& stack_storage = m_storage.emplace<ccc::StackStorage>();
stack_storage.stack_pointer_offset = m_ui.stackPointerOffsetSpinBox->value();
// Convert to caller sp relative.
if (std::optional<u32> 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<ccc::SymbolSourceHandle> source = database.get_symbol_source("User-defined");
if (!source.success())
{
error_message = tr("Cannot create symbol source.");
return;
}
ccc::Result<ccc::LocalVariable*> 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<ccc::LocalVariableHandle> 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<ccc::RegisterStorage, ccc::StackStorage> storage;
switch (storageType())
{
case GLOBAL_STORAGE:
{
error_message = tr("Invalid storage type.");
return;
}
case REGISTER_STORAGE:
{
ccc::RegisterStorage& register_storage = storage.emplace<ccc::RegisterStorage>();
register_storage.dbx_register_number = m_ui.registerComboBox->currentIndex();
break;
}
case STACK_STORAGE:
{
ccc::StackStorage& stack_storage = storage.emplace<ccc::StackStorage>();
stack_storage.stack_pointer_offset = m_ui.stackPointerOffsetSpinBox->value();
// Convert to caller sp relative.
if (std::optional<u32> 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<ccc::SymbolSourceHandle> source = database.get_symbol_source("User-defined");
if (!source.success())
{
error_message = tr("Cannot create symbol source.");
return;
}
ccc::Result<ccc::ParameterVariable*> 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<ccc::ParameterVariableHandle> 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);
}

View File

@ -0,0 +1,156 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include <QtWidgets/QDialog>
#include <ccc/ast.h>
#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<u32> fillExistingFunctionSize(u32 address, const ccc::SymbolDatabase& database);
std::optional<u32> 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<ccc::FunctionHandle> 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<ccc::ast::Node> 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<ccc::GlobalStorage, ccc::RegisterStorage, ccc::StackStorage> m_storage;
u32 m_address = 0;
std::unique_ptr<ccc::ast::Node> 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<ccc::RegisterStorage, ccc::StackStorage> m_storage;
std::unique_ptr<ccc::ast::Node> m_type;
ccc::FunctionHandle m_function;
};

View File

@ -0,0 +1,475 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "SymbolTreeDelegates.h"
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDoubleSpinBox>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QMessageBox>
#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<const SymbolTreeModel*>(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<ccc::ast::BuiltIn>();
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<ccc::ast::Enum>();
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<const SymbolTreeModel*>(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<ccc::ast::BuiltIn>();
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<QLineEdit*>(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<QLineEdit*>(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<QCheckBox*>(editor);
value = check_box->isChecked();
break;
}
case ccc::ast::BuiltInClass::FLOAT_32:
{
QLineEdit* line_edit = qobject_cast<QLineEdit*>(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<QLineEdit*>(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<ccc::ast::Enum>();
QComboBox* combo_box = qobject_cast<QComboBox*>(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<QLineEdit*>(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<QComboBox*>(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<const SymbolTreeModel*>(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<const SymbolTreeModel*>(index.model());
if (!model)
return;
SymbolTreeNode* node = model->nodeFromIndex(index);
if (!node || !node->symbol.valid())
return;
QLineEdit* line_edit = qobject_cast<QLineEdit*>(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<const SymbolTreeModel*>(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<QLineEdit*>(editor);
Q_ASSERT(line_edit);
SymbolTreeModel* symbol_tree_model = qobject_cast<SymbolTreeModel*>(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<const SymbolTreeModel*>(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<const SymbolTreeModel*>(index.model());
if (!tree_model)
return;
SymbolTreeNode* node = tree_model->nodeFromIndex(index);
if (!node || !node->symbol.valid())
return;
QLineEdit* line_edit = qobject_cast<QLineEdit*>(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<const SymbolTreeModel*>(index.model());
if (!tree_model)
return;
SymbolTreeNode* node = tree_model->nodeFromIndex(index);
if (!node || !node->symbol.valid())
return;
QLineEdit* line_edit = qobject_cast<QLineEdit*>(editor);
Q_ASSERT(line_edit);
SymbolTreeModel* symbol_tree_model = qobject_cast<SymbolTreeModel*>(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<ccc::ast::Node> 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);
}

View File

@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
#include <QtWidgets/QStyledItemDelegate>
#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;
};

View File

@ -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:
{
}
}
}

View File

@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
#include <QtCore/QString>
#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;
};

View File

@ -0,0 +1,597 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "SymbolTreeModel.h"
#include <QtWidgets/QApplication>
#include <QtGui/QBrush>
#include <QtGui/QPalette>
#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<std::unique_ptr<SymbolTreeNode>> 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<SymbolTreeNode*>(index.internalPointer());
if (!node)
return m_root.get();
return node;
}
void SymbolTreeModel::reset(std::unique_ptr<SymbolTreeNode> 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<SymbolTreeNode>& 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<QString> 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<ccc::ast::Node> 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<QString> 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<std::unique_ptr<SymbolTreeNode>> 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<std::unique_ptr<SymbolTreeNode>> children;
switch (physical_type->descriptor)
{
case ccc::ast::ARRAY:
{
const ccc::ast::Array& array = physical_type->as<ccc::ast::Array>();
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<SymbolTreeNode> element = std::make_unique<SymbolTreeNode>();
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<ccc::ast::PointerOrReference>();
u32 address = location.read32(cpu);
if (!cpu.isValidAddress(address))
break;
std::unique_ptr<SymbolTreeNode> pointee = std::make_unique<SymbolTreeNode>();
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<ccc::ast::StructOrUnion>();
std::vector<ccc::ast::StructOrUnion::FlatField> 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<SymbolTreeNode> child_node = std::make_unique<SymbolTreeNode>();
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<ccc::ast::Array>();
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<ccc::ast::StructOrUnion>();
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;
}

View File

@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
#include <QtCore/QAbstractItemModel>
#include <ccc/ast.h>
#include <ccc/symbol_database.h>
#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<SymbolTreeNode> 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<QString> changeTypeTemporarily(QModelIndex index, std::string_view type_string);
std::optional<QString> typeFromModelIndexToString(QModelIndex index);
protected:
static std::vector<std::unique_ptr<SymbolTreeNode>> 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<SymbolTreeNode> m_root;
QString m_filter;
DebugInterface& m_cpu;
};

View File

@ -0,0 +1,555 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "SymbolTreeNode.h"
#include <ccc/ast.h>
const QVariant& SymbolTreeNode::value() const
{
return m_value;
}
const QString& SymbolTreeNode::display_value() const
{
return m_display_value;
}
std::optional<bool> 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<ccc::ast::BuiltIn>();
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<float*>(&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<double*>(&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<ccc::ast::BuiltIn>();
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<u32*>(&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<u64*>(&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<ccc::ast::Array>();
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<ccc::ast::BuiltIn>();
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<float*>(&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<double*>(&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<ccc::ast::Enum>();
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<ccc::ast::PointerOrReference>();
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<ccc::ast::StructOrUnion>();
QString result;
result += "{";
std::vector<ccc::ast::StructOrUnion::FlatField> 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<bool> 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<SymbolTreeNode>& child : children())
if (child->anySymbolsValid(database))
return true;
return false;
}
const SymbolTreeNode* SymbolTreeNode::parent() const
{
return m_parent;
}
const std::vector<std::unique_ptr<SymbolTreeNode>>& SymbolTreeNode::children() const
{
return m_children;
}
bool SymbolTreeNode::childrenFetched() const
{
return m_children_fetched;
}
void SymbolTreeNode::setChildren(std::vector<std::unique_ptr<SymbolTreeNode>> new_children)
{
for (std::unique_ptr<SymbolTreeNode>& child : new_children)
child->m_parent = this;
m_children = std::move(new_children);
m_children_fetched = true;
}
void SymbolTreeNode::insertChildren(std::vector<std::unique_ptr<SymbolTreeNode>> new_children)
{
for (std::unique_ptr<SymbolTreeNode>& 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<SymbolTreeNode> 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<SymbolTreeNode>& lhs, const std::unique_ptr<SymbolTreeNode>& 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<SymbolTreeNode>& child : m_children)
child->sortChildrenRecursively(sort_by_if_type_is_known);
}

View File

@ -0,0 +1,85 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
#include <QtCore/QString>
#include <QtCore/QVariant>
#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<u32> size;
ccc::NodeHandle type;
std::unique_ptr<ccc::ast::Node> 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<bool> 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<std::unique_ptr<SymbolTreeNode>>& children() const;
bool childrenFetched() const;
void setChildren(std::vector<std::unique_ptr<SymbolTreeNode>> new_children);
void insertChildren(std::vector<std::unique_ptr<SymbolTreeNode>> new_children);
void emplaceChild(std::unique_ptr<SymbolTreeNode> new_child);
void clearChildren();
void sortChildrenRecursively(bool sort_by_if_type_is_known);
protected:
QVariant m_value;
QString m_display_value;
std::optional<bool> m_liveness;
SymbolTreeNode* m_parent = nullptr;
std::vector<std::unique_ptr<SymbolTreeNode>> m_children;
bool m_children_fetched = false;
};

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SymbolTreeWidget</class>
<widget class="QWidget" name="SymbolTreeWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>419</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="vertical_layout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTreeView" name="treeView"/>
</item>
<item>
<layout class="QHBoxLayout" name="bottomPanel">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="refreshButton">
<property name="text">
<string>Refresh</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="filterBox">
<property name="placeholderText">
<string>Filter</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="newButton">
<property name="maximumSize">
<size>
<width>26</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>+</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteButton">
<property name="maximumSize">
<size>
<width>26</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,213 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include <QtWidgets/QWidget>
#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<SymbolTreeNode> buildTree(const SymbolFilters& filters, const ccc::SymbolDatabase& database);
std::unique_ptr<SymbolTreeNode> groupBySourceFile(
std::unique_ptr<SymbolTreeNode> child,
const SymbolWork& child_work,
SymbolTreeNode*& prev_group,
const SymbolWork*& prev_work,
const SymbolFilters& filters);
std::unique_ptr<SymbolTreeNode> groupBySection(
std::unique_ptr<SymbolTreeNode> child,
const SymbolWork& child_work,
SymbolTreeNode*& prev_group,
const SymbolWork*& prev_work,
const SymbolFilters& filters);
std::unique_ptr<SymbolTreeNode> groupByModule(
std::unique_ptr<SymbolTreeNode> child,
const SymbolWork& child_work,
SymbolTreeNode*& prev_group,
const SymbolWork*& prev_work,
const SymbolFilters& filters);
void setupMenu();
void openMenu(QPoint pos);
virtual std::vector<SymbolWork> getSymbols(
const QString& filter, const ccc::SymbolDatabase& database) = 0;
virtual std::unique_ptr<SymbolTreeNode> 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<SymbolWork> getSymbols(
const QString& filter, const ccc::SymbolDatabase& database) override;
std::unique_ptr<SymbolTreeNode> 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<SymbolWork> getSymbols(
const QString& filter, const ccc::SymbolDatabase& database) override;
std::unique_ptr<SymbolTreeNode> 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<SymbolWork> getSymbols(
const QString& filter, const ccc::SymbolDatabase& database) override;
std::unique_ptr<SymbolTreeNode> buildNode(
SymbolWork& work, const ccc::SymbolDatabase& database) const override;
void configureColumns() override;
void onNewButtonPressed() override;
std::optional<u32> m_caller_stack_pointer;
};
class ParameterVariableTreeWidget : public SymbolTreeWidget
{
Q_OBJECT
public:
explicit ParameterVariableTreeWidget(DebugInterface& cpu, QWidget* parent = nullptr);
virtual ~ParameterVariableTreeWidget();
protected:
std::vector<SymbolWork> getSymbols(
const QString& filter, const ccc::SymbolDatabase& database) override;
std::unique_ptr<SymbolTreeNode> buildNode(
SymbolWork& work, const ccc::SymbolDatabase& database) const override;
void configureColumns() override;
void onNewButtonPressed() override;
std::optional<u32> m_caller_stack_pointer;
};
struct SymbolFilters
{
bool group_by_module = false;
bool group_by_section = false;
bool group_by_source_file = false;
QString string;
};

View File

@ -0,0 +1,162 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "TypeString.h"
#include <QtWidgets/QApplication>
#include "common/Pcsx2Types.h"
std::unique_ptr<ccc::ast::Node> 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<s32> 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<ccc::ast::Node> result;
// Create the AST.
std::unique_ptr<ccc::ast::TypeName> type_name = std::make_unique<ccc::ast::TypeName>();
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<ccc::ast::PointerOrReference> pointer_or_reference = std::make_unique<ccc::ast::PointerOrReference>();
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<ccc::ast::Array> array = std::make_unique<ccc::ast::Array>();
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<ccc::ast::Array>();
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<ccc::ast::PointerOrReference>();
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<ccc::ast::BuiltIn>();
name = ccc::ast::builtin_class_to_string(built_in.bclass);
break;
}
case ccc::ast::TYPE_NAME:
{
const ccc::ast::TypeName& type_name = type->as<ccc::ast::TypeName>();
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;
}

View File

@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
#include <string_view>
#include <QtCore/QString>
#include <ccc/ast.h>
// 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<ccc::ast::Node> 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);

View File

@ -48,7 +48,7 @@
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\ccc\src</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)pcsx2</AdditionalIncludeDirectories>
<!-- Needed for moc pch -->
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(ProjectDir)\Settings;$(ProjectDir)\GameList;$(ProjectDir)\Tools\InputRecording;$(ProjectDir)\Debugger;$(ProjectDir)\Debugger\Models</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(ProjectDir)\Settings;$(ProjectDir)\GameList;$(ProjectDir)\Tools\InputRecording;$(ProjectDir)\Debugger;$(ProjectDir)\Debugger\Models;$(ProjectDir)\Debugger\SymbolTree</AdditionalIncludeDirectories>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>PrecompiledHeader.h</PrecompiledHeaderFile>
<ForcedIncludeFiles>PrecompiledHeader.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
@ -87,6 +87,13 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="ColorPickerButton.cpp" />
<ClCompile Include="Debugger\SymbolTree\NewSymbolDialogs.cpp" />
<ClCompile Include="Debugger\SymbolTree\SymbolTreeDelegates.cpp" />
<ClCompile Include="Debugger\SymbolTree\SymbolTreeLocation.cpp" />
<ClCompile Include="Debugger\SymbolTree\SymbolTreeModel.cpp" />
<ClCompile Include="Debugger\SymbolTree\SymbolTreeNode.cpp" />
<ClCompile Include="Debugger\SymbolTree\SymbolTreeWidgets.cpp" />
<ClCompile Include="Debugger\SymbolTree\TypeString.cpp" />
<ClCompile Include="EarlyHardwareCheck.cpp" />
<ClCompile Include="LogWindow.cpp" />
<ClCompile Include="QtProgressCallback.cpp" />
@ -176,6 +183,13 @@
<QtMoc Include="QtProgressCallback.h" />
<QtMoc Include="ColorPickerButton.h" />
<QtMoc Include="LogWindow.h" />
<QtMoc Include="Debugger\SymbolTree\NewSymbolDialogs.h" />
<QtMoc Include="Debugger\SymbolTree\SymbolTreeDelegates.h" />
<QtMoc Include="Debugger\SymbolTree\SymbolTreeLocation.h" />
<QtMoc Include="Debugger\SymbolTree\SymbolTreeModel.h" />
<QtMoc Include="Debugger\SymbolTree\SymbolTreeNode.h" />
<QtMoc Include="Debugger\SymbolTree\SymbolTreeWidgets.h" />
<QtMoc Include="Debugger\SymbolTree\TypeString.h" />
<ClInclude Include="Settings\ControllerSettingWidgetBinder.h" />
<QtMoc Include="Settings\FolderSettingsWidget.h" />
<QtMoc Include="Settings\DebugSettingsWidget.h" />
@ -261,6 +275,13 @@
<ClCompile Include="$(IntDir)Debugger\Models\moc_ThreadModel.cpp" />
<ClCompile Include="$(IntDir)Debugger\Models\moc_StackModel.cpp" />
<ClCompile Include="$(IntDir)Debugger\Models\moc_SavedAddressesModel.cpp" />
<ClCompile Include="$(IntDir)Debugger\SymbolTree\moc_NewSymbolDialogs.cpp" />
<ClCompile Include="$(IntDir)Debugger\SymbolTree\moc_SymbolTreeDelegates.cpp" />
<ClCompile Include="$(IntDir)Debugger\SymbolTree\moc_SymbolTreeLocation.cpp" />
<ClCompile Include="$(IntDir)Debugger\SymbolTree\moc_SymbolTreeModel.cpp" />
<ClCompile Include="$(IntDir)Debugger\SymbolTree\moc_SymbolTreeNode.cpp" />
<ClCompile Include="$(IntDir)Debugger\SymbolTree\moc_SymbolTreeWidgets.cpp" />
<ClCompile Include="$(IntDir)Debugger\SymbolTree\moc_TypeString.cpp" />
<ClCompile Include="$(IntDir)GameList\moc_GameListModel.cpp" />
<ClCompile Include="$(IntDir)GameList\moc_GameListRefreshThread.cpp" />
<ClCompile Include="$(IntDir)GameList\moc_GameListWidget.cpp" />
@ -445,6 +466,8 @@
<QtUi Include="Settings\ControllerMacroWidget.ui">
<FileType>Document</FileType>
</QtUi>
<None Include="Debugger\SymbolTree\NewSymbolDialog.ui" />
<None Include="Debugger\SymbolTree\SymbolTreeWidget.ui" />
<QtUi Include="Settings\AudioExpansionSettingsDialog.ui">
<FileType>Document</FileType>
</QtUi>

View File

@ -28,6 +28,9 @@
<Filter Include="Translations">
<UniqueIdentifier>{ad04f939-64a0-4039-97aa-a38b8aa46855}</UniqueIdentifier>
</Filter>
<Filter Include="Debugger\SymbolTree">
<UniqueIdentifier>{a622b871-62ae-4b70-b9b2-6ee30ce7fa7a}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\pcsx2\windows\PCSX2.rc" />
@ -355,6 +358,27 @@
<ClCompile Include="Debugger\DebuggerSettingsManager.cpp">
<Filter>Debugger</Filter>
</ClCompile>
<ClCompile Include="Debugger\SymbolTree\NewSymbolDialogs.cpp">
<Filter>Debugger\SymbolTree</Filter>
</ClCompile>
<ClCompile Include="Debugger\SymbolTree\SymbolTreeDelegates.cpp">
<Filter>Debugger\SymbolTree</Filter>
</ClCompile>
<ClCompile Include="Debugger\SymbolTree\SymbolTreeLocation.cpp">
<Filter>Debugger\SymbolTree</Filter>
</ClCompile>
<ClCompile Include="Debugger\SymbolTree\SymbolTreeModel.cpp">
<Filter>Debugger\SymbolTree</Filter>
</ClCompile>
<ClCompile Include="Debugger\SymbolTree\SymbolTreeNode.cpp">
<Filter>Debugger\SymbolTree</Filter>
</ClCompile>
<ClCompile Include="Debugger\SymbolTree\SymbolTreeWidgets.cpp">
<Filter>Debugger\SymbolTree</Filter>
</ClCompile>
<ClCompile Include="Debugger\SymbolTree\TypeString.cpp">
<Filter>Debugger\SymbolTree</Filter>
</ClCompile>
<ClCompile Include="VCRuntimeChecker.cpp" />
</ItemGroup>
<ItemGroup>
@ -378,6 +402,27 @@
<ClInclude Include="Debugger\DebuggerSettingsManager.h">
<Filter>Debugger</Filter>
</ClInclude>
<ClInclude Include="Debugger\SymbolTree\NewSymbolDialogs.h">
<Filter>Debugger\SymbolTree</Filter>
</ClInclude>
<ClInclude Include="Debugger\SymbolTree\SymbolTreeDelegates.h">
<Filter>Debugger\SymbolTree</Filter>
</ClInclude>
<ClInclude Include="Debugger\SymbolTree\SymbolTreeLocation.h">
<Filter>Debugger\SymbolTree</Filter>
</ClInclude>
<ClInclude Include="Debugger\SymbolTree\SymbolTreeModel.h">
<Filter>Debugger\SymbolTree</Filter>
</ClInclude>
<ClInclude Include="Debugger\SymbolTree\SymbolTreeNode.h">
<Filter>Debugger\SymbolTree</Filter>
</ClInclude>
<ClInclude Include="Debugger\SymbolTree\SymbolTreeWidgets.h">
<Filter>Debugger\SymbolTree</Filter>
</ClInclude>
<ClInclude Include="Debugger\SymbolTree\TypeString.h">
<Filter>Debugger\SymbolTree</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<QtMoc Include="MainWindow.h" />
@ -695,6 +740,12 @@
<None Include="Settings\ControllerMappingSettingsDialog.ui">
<Filter>Settings</Filter>
</None>
<None Include="Debugger\SymbolTree\NewSymbolDialog.ui">
<Filter>Debugger\SymbolTree</Filter>
</None>
<None Include="Debugger\SymbolTree\SymbolTreeWidget.ui">
<Filter>Debugger\SymbolTree</Filter>
</None>
</ItemGroup>
<ItemGroup>
<QtTs Include="Translations\pcsx2-qt_en.ts">