First Commit

This commit is contained in:
2025-11-18 14:18:26 -07:00
parent 33eb6e3707
commit 27277ec342
6106 changed files with 3571167 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,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);
}

View 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;
};

View 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);
}

View 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;
};

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

View 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;
};

View 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;
}

View 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;
};

View 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);
}

View 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;
};

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

File diff suppressed because it is too large Load Diff

View 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;
};

View 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;
}

View 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);