First Commit
This commit is contained in:
314
pcsx2-qt/Debugger/SymbolTree/NewSymbolDialog.ui
Normal file
314
pcsx2-qt/Debugger/SymbolTree/NewSymbolDialog.ui
Normal 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>
|
||||
653
pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.cpp
Normal file
653
pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.cpp
Normal file
@@ -0,0 +1,653 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "NewSymbolDialogs.h"
|
||||
#include "QtCompatibility.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();
|
||||
|
||||
setFormRowVisible(m_ui.form, Row::SIZE, flags & SIZE_FIELD);
|
||||
setFormRowVisible(m_ui.form, Row::EXISTING_FUNCTIONS, flags & EXISTING_FUNCTIONS_FIELD);
|
||||
setFormRowVisible(m_ui.form, Row::TYPE, flags & TYPE_FIELD);
|
||||
setFormRowVisible(m_ui.form, 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();
|
||||
|
||||
setFormRowVisible(m_ui.form, Row::ADDRESS, storage == GLOBAL_STORAGE);
|
||||
setFormRowVisible(m_ui.form, Row::REGISTER, storage == REGISTER_STORAGE);
|
||||
setFormRowVisible(m_ui.form, 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);
|
||||
}
|
||||
156
pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.h
Normal file
156
pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.h
Normal file
@@ -0,0 +1,156 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 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;
|
||||
};
|
||||
484
pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.cpp
Normal file
484
pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.cpp
Normal file
@@ -0,0 +1,484 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 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"
|
||||
#include "QtCompatibility.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());
|
||||
connectCheckStateChanged(editor, this, &SymbolTreeValueDelegate::onCheckBoxStateChanged);
|
||||
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::onCheckBoxStateChanged(Qt::CheckState state)
|
||||
{
|
||||
QCheckBox* check_box = qobject_cast<QCheckBox*>(sender());
|
||||
if (check_box)
|
||||
commitData(check_box);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
68
pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.h
Normal file
68
pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.h
Normal file
@@ -0,0 +1,68 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: LGPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtWidgets/QStyledItemDelegate>
|
||||
|
||||
#include "DebugTools/DebugInterface.h"
|
||||
#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:
|
||||
// These make it so the values inputted are written back to memory
|
||||
// immediately when the widgets are interacted with rather than when they
|
||||
// are deselected.
|
||||
void onCheckBoxStateChanged(Qt::CheckState state);
|
||||
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;
|
||||
};
|
||||
222
pcsx2-qt/Debugger/SymbolTree/SymbolTreeLocation.cpp
Normal file
222
pcsx2-qt/Debugger/SymbolTree/SymbolTreeLocation.cpp
Normal file
@@ -0,0 +1,222 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 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:
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
46
pcsx2-qt/Debugger/SymbolTree/SymbolTreeLocation.h
Normal file
46
pcsx2-qt/Debugger/SymbolTree/SymbolTreeLocation.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 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;
|
||||
};
|
||||
523
pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.cpp
Normal file
523
pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.cpp
Normal file
@@ -0,0 +1,523 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 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)
|
||||
active = node->matchesMemory();
|
||||
|
||||
// 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 (field.symbol)
|
||||
parent_handle = ccc::NodeHandle(*field.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:
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
for (std::unique_ptr<SymbolTreeNode>& child : children)
|
||||
child->readFromVM(cpu, database);
|
||||
|
||||
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;
|
||||
}
|
||||
82
pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.h
Normal file
82
pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.h
Normal file
@@ -0,0 +1,82 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 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);
|
||||
|
||||
std::unique_ptr<SymbolTreeNode> m_root;
|
||||
QString m_filter;
|
||||
DebugInterface& m_cpu;
|
||||
};
|
||||
711
pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.cpp
Normal file
711
pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.cpp
Normal file
@@ -0,0 +1,711 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 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);
|
||||
data_changed |= updateMatchesMemory(cpu, database);
|
||||
|
||||
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::updateMatchesMemory(DebugInterface& cpu, const ccc::SymbolDatabase& database)
|
||||
{
|
||||
bool matching = true;
|
||||
|
||||
switch (symbol.descriptor())
|
||||
{
|
||||
case ccc::SymbolDescriptor::FUNCTION:
|
||||
{
|
||||
const ccc::Function* function = database.functions.symbol_from_handle(symbol.handle());
|
||||
if (!function || function->current_hash() == 0 || function->original_hash() == 0)
|
||||
return false;
|
||||
|
||||
matching = function->current_hash() == function->original_hash();
|
||||
|
||||
break;
|
||||
}
|
||||
case ccc::SymbolDescriptor::GLOBAL_VARIABLE:
|
||||
{
|
||||
const ccc::GlobalVariable* global_variable = database.global_variables.symbol_from_handle(symbol.handle());
|
||||
if (!global_variable)
|
||||
return false;
|
||||
|
||||
const ccc::SourceFile* source_file = database.source_files.symbol_from_handle(global_variable->source_file());
|
||||
if (!source_file)
|
||||
return false;
|
||||
|
||||
matching = source_file->functions_match();
|
||||
|
||||
break;
|
||||
}
|
||||
case ccc::SymbolDescriptor::LOCAL_VARIABLE:
|
||||
{
|
||||
const ccc::LocalVariable* local_variable = database.local_variables.symbol_from_handle(symbol.handle());
|
||||
if (!local_variable)
|
||||
return false;
|
||||
|
||||
const ccc::Function* function = database.functions.symbol_from_handle(local_variable->function());
|
||||
if (!function || function->current_hash() == 0 || function->original_hash() == 0)
|
||||
return false;
|
||||
|
||||
matching = function->current_hash() == function->original_hash();
|
||||
|
||||
break;
|
||||
}
|
||||
case ccc::SymbolDescriptor::PARAMETER_VARIABLE:
|
||||
{
|
||||
const ccc::ParameterVariable* parameter_variable = database.parameter_variables.symbol_from_handle(symbol.handle());
|
||||
if (!parameter_variable)
|
||||
return false;
|
||||
|
||||
const ccc::Function* function = database.functions.symbol_from_handle(parameter_variable->function());
|
||||
if (!function || function->current_hash() == 0 || function->original_hash() == 0)
|
||||
return false;
|
||||
|
||||
matching = function->current_hash() == function->original_hash();
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (matching == m_matches_memory)
|
||||
return false;
|
||||
|
||||
m_matches_memory = matching;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymbolTreeNode::matchesMemory() const
|
||||
{
|
||||
return m_matches_memory;
|
||||
}
|
||||
|
||||
void SymbolTreeNode::updateSymbolHashes(std::span<const SymbolTreeNode*> nodes, DebugInterface& cpu, ccc::SymbolDatabase& database)
|
||||
{
|
||||
std::set<ccc::FunctionHandle> functions;
|
||||
std::set<ccc::SourceFile*> source_files;
|
||||
|
||||
// Determine which functions we need to hash again, and in the case of
|
||||
// global variables, which source files are associated with those functions
|
||||
// so that we can check if they still match.
|
||||
for (const SymbolTreeNode* node : nodes)
|
||||
{
|
||||
switch (node->symbol.descriptor())
|
||||
{
|
||||
case ccc::SymbolDescriptor::FUNCTION:
|
||||
{
|
||||
functions.emplace(node->symbol.handle());
|
||||
break;
|
||||
}
|
||||
case ccc::SymbolDescriptor::GLOBAL_VARIABLE:
|
||||
{
|
||||
const ccc::GlobalVariable* global_variable = database.global_variables.symbol_from_handle(node->symbol.handle());
|
||||
if (!global_variable)
|
||||
continue;
|
||||
|
||||
ccc::SourceFile* source_file = database.source_files.symbol_from_handle(global_variable->source_file());
|
||||
if (!source_file)
|
||||
continue;
|
||||
|
||||
for (ccc::FunctionHandle function : source_file->functions())
|
||||
functions.emplace(function);
|
||||
|
||||
source_files.emplace(source_file);
|
||||
|
||||
break;
|
||||
}
|
||||
case ccc::SymbolDescriptor::LOCAL_VARIABLE:
|
||||
{
|
||||
const ccc::LocalVariable* local_variable = database.local_variables.symbol_from_handle(node->symbol.handle());
|
||||
if (!local_variable)
|
||||
continue;
|
||||
|
||||
functions.emplace(local_variable->function());
|
||||
|
||||
break;
|
||||
}
|
||||
case ccc::SymbolDescriptor::PARAMETER_VARIABLE:
|
||||
{
|
||||
const ccc::ParameterVariable* parameter_variable = database.parameter_variables.symbol_from_handle(node->symbol.handle());
|
||||
if (!parameter_variable)
|
||||
continue;
|
||||
|
||||
functions.emplace(parameter_variable->function());
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the hashes for the enumerated functions.
|
||||
for (ccc::FunctionHandle function_handle : functions)
|
||||
{
|
||||
ccc::Function* function = database.functions.symbol_from_handle(function_handle);
|
||||
if (!function || function->original_hash() == 0)
|
||||
continue;
|
||||
|
||||
std::optional<ccc::FunctionHash> hash = SymbolGuardian::HashFunction(*function, cpu);
|
||||
if (!hash.has_value())
|
||||
continue;
|
||||
|
||||
function->set_current_hash(*hash);
|
||||
}
|
||||
|
||||
// Check that the enumerated source files still have matching functions.
|
||||
for (ccc::SourceFile* source_file : source_files)
|
||||
source_file->check_functions_match(database);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
92
pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.h
Normal file
92
pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.h
Normal file
@@ -0,0 +1,92 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 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;
|
||||
QString mangled_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 updateMatchesMemory(DebugInterface& cpu, const ccc::SymbolDatabase& database);
|
||||
bool matchesMemory() const;
|
||||
|
||||
static void updateSymbolHashes(std::span<const SymbolTreeNode*> nodes, DebugInterface& cpu, ccc::SymbolDatabase& database);
|
||||
|
||||
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;
|
||||
bool m_matches_memory = true;
|
||||
|
||||
SymbolTreeNode* m_parent = nullptr;
|
||||
std::vector<std::unique_ptr<SymbolTreeNode>> m_children;
|
||||
bool m_children_fetched = false;
|
||||
};
|
||||
86
pcsx2-qt/Debugger/SymbolTree/SymbolTreeView.ui
Normal file
86
pcsx2-qt/Debugger/SymbolTree/SymbolTreeView.ui
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SymbolTreeView</class>
|
||||
<widget class="QWidget" name="SymbolTreeView">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Symbol Tree</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>
|
||||
1203
pcsx2-qt/Debugger/SymbolTree/SymbolTreeViews.cpp
Normal file
1203
pcsx2-qt/Debugger/SymbolTree/SymbolTreeViews.cpp
Normal file
File diff suppressed because it is too large
Load Diff
201
pcsx2-qt/Debugger/SymbolTree/SymbolTreeViews.h
Normal file
201
pcsx2-qt/Debugger/SymbolTree/SymbolTreeViews.h
Normal file
@@ -0,0 +1,201 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ui_SymbolTreeView.h"
|
||||
|
||||
#include "Debugger/DebuggerView.h"
|
||||
#include "Debugger/SymbolTree/SymbolTreeModel.h"
|
||||
|
||||
// A symbol tree view with its associated refresh button, filter box and
|
||||
// right-click menu. Supports grouping, sorting and various other settings.
|
||||
class SymbolTreeView : public DebuggerView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
virtual ~SymbolTreeView();
|
||||
|
||||
void updateModel();
|
||||
void reset();
|
||||
void updateVisibleNodes(bool update_hashes);
|
||||
void expandGroups(QModelIndex index);
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
SymbolTreeView(
|
||||
u32 flags,
|
||||
s32 symbol_address_alignment,
|
||||
const DebuggerViewParameters& parameters);
|
||||
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
void toJson(JsonValueWrapper& json) override;
|
||||
bool fromJson(const JsonValueWrapper& json) override;
|
||||
|
||||
void setupTree();
|
||||
std::unique_ptr<SymbolTreeNode> buildTree(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);
|
||||
|
||||
std::unique_ptr<SymbolTreeNode> groupBySection(
|
||||
std::unique_ptr<SymbolTreeNode> child,
|
||||
const SymbolWork& child_work,
|
||||
SymbolTreeNode*& prev_group,
|
||||
const SymbolWork*& prev_work);
|
||||
|
||||
std::unique_ptr<SymbolTreeNode> groupByModule(
|
||||
std::unique_ptr<SymbolTreeNode> child,
|
||||
const SymbolWork& child_work,
|
||||
SymbolTreeNode*& prev_group,
|
||||
const SymbolWork*& prev_work);
|
||||
|
||||
void openContextMenu(QPoint pos);
|
||||
|
||||
virtual bool needsReset() const;
|
||||
|
||||
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 onCopyMangledName();
|
||||
void onCopyLocation();
|
||||
void onRenameSymbol();
|
||||
void onResetChildren();
|
||||
void onChangeTypeTemporarily();
|
||||
|
||||
void onTreeViewClicked(const QModelIndex& index);
|
||||
|
||||
SymbolTreeNode* currentNode();
|
||||
|
||||
Ui::SymbolTreeView m_ui;
|
||||
|
||||
SymbolTreeModel* m_model = 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,
|
||||
ALLOW_MANGLED_NAME_ACTIONS = 1 << 3,
|
||||
CLICK_TO_GO_TO_IN_DISASSEMBLER = 1 << 4
|
||||
};
|
||||
|
||||
u32 m_flags;
|
||||
u32 m_symbol_address_alignment;
|
||||
|
||||
bool m_show_size_column = false;
|
||||
bool m_group_by_module = false;
|
||||
bool m_group_by_section = false;
|
||||
bool m_group_by_source_file = false;
|
||||
bool m_sort_by_if_type_is_known = false;
|
||||
};
|
||||
|
||||
class FunctionTreeView : public SymbolTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FunctionTreeView(const DebuggerViewParameters& parameters);
|
||||
virtual ~FunctionTreeView();
|
||||
|
||||
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 GlobalVariableTreeView : public SymbolTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GlobalVariableTreeView(const DebuggerViewParameters& parameters);
|
||||
virtual ~GlobalVariableTreeView();
|
||||
|
||||
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 LocalVariableTreeView : public SymbolTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LocalVariableTreeView(const DebuggerViewParameters& parameters);
|
||||
virtual ~LocalVariableTreeView();
|
||||
|
||||
protected:
|
||||
bool needsReset() const override;
|
||||
|
||||
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;
|
||||
|
||||
ccc::FunctionHandle m_function;
|
||||
std::optional<u32> m_caller_stack_pointer;
|
||||
};
|
||||
|
||||
class ParameterVariableTreeView : public SymbolTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ParameterVariableTreeView(const DebuggerViewParameters& parameters);
|
||||
virtual ~ParameterVariableTreeView();
|
||||
|
||||
protected:
|
||||
bool needsReset() const override;
|
||||
|
||||
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;
|
||||
|
||||
ccc::FunctionHandle m_function;
|
||||
std::optional<u32> m_caller_stack_pointer;
|
||||
};
|
||||
162
pcsx2-qt/Debugger/SymbolTree/TypeString.cpp
Normal file
162
pcsx2-qt/Debugger/SymbolTree/TypeString.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 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;
|
||||
}
|
||||
20
pcsx2-qt/Debugger/SymbolTree/TypeString.h
Normal file
20
pcsx2-qt/Debugger/SymbolTree/TypeString.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: 2002-2025 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);
|
||||
Reference in New Issue
Block a user