// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: GPL-3.0+ #include "NewSymbolDialogs.h" #include #include #include #include #include "TypeString.h" NewSymbolDialog::NewSymbolDialog(u32 flags, u32 alignment, DebugInterface& cpu, QWidget* parent) : QDialog(parent) , m_cpu(cpu) , m_alignment(alignment) { m_ui.setupUi(this); connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &NewSymbolDialog::createSymbol); connect(m_ui.storageTabBar, &QTabBar::currentChanged, this, &NewSymbolDialog::onStorageTabChanged); if (flags & GLOBAL_STORAGE) { int tab = m_ui.storageTabBar->addTab(tr("Global")); m_ui.storageTabBar->setTabData(tab, GLOBAL_STORAGE); } if (flags & REGISTER_STORAGE) { int tab = m_ui.storageTabBar->addTab(tr("Register")); m_ui.storageTabBar->setTabData(tab, REGISTER_STORAGE); setupRegisterField(); } if (flags & STACK_STORAGE) { int tab = m_ui.storageTabBar->addTab(tr("Stack")); m_ui.storageTabBar->setTabData(tab, STACK_STORAGE); } if (m_ui.storageTabBar->count() == 1) m_ui.storageTabBar->hide(); m_ui.form->setRowVisible(Row::SIZE, flags & SIZE_FIELD); m_ui.form->setRowVisible(Row::EXISTING_FUNCTIONS, flags & EXISTING_FUNCTIONS_FIELD); m_ui.form->setRowVisible(Row::TYPE, flags & TYPE_FIELD); m_ui.form->setRowVisible(Row::FUNCTION, flags & FUNCTION_FIELD); if (flags & SIZE_FIELD) { setupSizeField(); updateSizeField(); } if (flags & FUNCTION_FIELD) setupFunctionField(); connectInputWidgets(); onStorageTabChanged(0); adjustSize(); } void NewSymbolDialog::setName(QString name) { m_ui.nameLineEdit->setText(name); } void NewSymbolDialog::setAddress(u32 address) { m_ui.addressLineEdit->setText(QString::number(address, 16)); } void NewSymbolDialog::setCustomSize(u32 size) { m_ui.customSizeRadioButton->setChecked(true); m_ui.customSizeSpinBox->setValue(size); } void NewSymbolDialog::setupRegisterField() { m_ui.registerComboBox->clear(); for (int i = 0; i < m_cpu.getRegisterCount(0); i++) m_ui.registerComboBox->addItem(m_cpu.getRegisterName(0, i)); } void NewSymbolDialog::setupSizeField() { connect(m_ui.customSizeRadioButton, &QRadioButton::toggled, m_ui.customSizeSpinBox, &QSpinBox::setEnabled); connect(m_ui.addressLineEdit, &QLineEdit::textChanged, this, &NewSymbolDialog::updateSizeField); } void NewSymbolDialog::setupFunctionField() { m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { const ccc::Function* default_function = database.functions.symbol_overlapping_address(m_cpu.getPC()); for (const ccc::Function& function : database.functions) { QString name = QString::fromStdString(function.name()); name.truncate(64); m_ui.functionComboBox->addItem(name); m_functions.emplace_back(function.handle()); if (default_function && function.handle() == default_function->handle()) m_ui.functionComboBox->setCurrentIndex(m_ui.functionComboBox->count() - 1); } }); } void NewSymbolDialog::connectInputWidgets() { QMetaMethod parse_user_input = metaObject()->method(metaObject()->indexOfSlot("parseUserInput()")); for (QObject* child : children()) { QWidget* widget = qobject_cast(child); if (!widget) continue; QMetaProperty property = widget->metaObject()->userProperty(); if (!property.isValid() || !property.hasNotifySignal()) continue; connect(widget, property.notifySignal(), this, parse_user_input); } } void NewSymbolDialog::updateErrorMessage(QString error_message) { m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(error_message.isEmpty()); m_ui.errorMessage->setText(error_message); } NewSymbolDialog::FunctionSizeType NewSymbolDialog::functionSizeType() const { if (m_ui.fillExistingFunctionRadioButton->isChecked()) return FILL_EXISTING_FUNCTION; if (m_ui.fillEmptySpaceRadioButton->isChecked()) return FILL_EMPTY_SPACE; return CUSTOM_SIZE; } void NewSymbolDialog::updateSizeField() { bool ok; u32 address = m_ui.addressLineEdit->text().toUInt(&ok, 16); if (ok) { m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { std::optional fill_existing_function_size = fillExistingFunctionSize(address, database); if (fill_existing_function_size.has_value()) m_ui.fillExistingFunctionRadioButton->setText( tr("Fill existing function (%1 bytes)").arg(*fill_existing_function_size)); else m_ui.fillExistingFunctionRadioButton->setText( tr("Fill existing function (none found)")); m_ui.fillExistingFunctionRadioButton->setEnabled(fill_existing_function_size.has_value()); std::optional fill_empty_space_size = fillEmptySpaceSize(address, database); if (fill_empty_space_size.has_value()) m_ui.fillEmptySpaceRadioButton->setText( tr("Fill space (%1 bytes)").arg(*fill_empty_space_size)); else m_ui.fillEmptySpaceRadioButton->setText(tr("Fill space (no next symbol)")); m_ui.fillEmptySpaceRadioButton->setEnabled(fill_empty_space_size.has_value()); }); } else { // Add some padding to the end of the radio button text so that the // layout engine knows we need some more space for the size. QString padding(16, ' '); m_ui.fillExistingFunctionRadioButton->setText(tr("Fill existing function").append(padding)); m_ui.fillEmptySpaceRadioButton->setText(tr("Fill space").append(padding)); } } std::optional NewSymbolDialog::fillExistingFunctionSize(u32 address, const ccc::SymbolDatabase& database) { const ccc::Function* existing_function = database.functions.symbol_overlapping_address(address); if (!existing_function) return std::nullopt; return existing_function->address_range().high.value - address; } std::optional NewSymbolDialog::fillEmptySpaceSize(u32 address, const ccc::SymbolDatabase& database) { const ccc::Symbol* next_symbol = database.symbol_after_address( address, ccc::FUNCTION | ccc::GLOBAL_VARIABLE | ccc::LOCAL_VARIABLE); if (!next_symbol) return std::nullopt; return next_symbol->address().value - address; } u32 NewSymbolDialog::storageType() const { return m_ui.storageTabBar->tabData(m_ui.storageTabBar->currentIndex()).toUInt(); } void NewSymbolDialog::onStorageTabChanged(int index) { u32 storage = m_ui.storageTabBar->tabData(index).toUInt(); m_ui.form->setRowVisible(Row::ADDRESS, storage == GLOBAL_STORAGE); m_ui.form->setRowVisible(Row::REGISTER, storage == REGISTER_STORAGE); m_ui.form->setRowVisible(Row::STACK_POINTER_OFFSET, storage == STACK_STORAGE); QTimer::singleShot(0, this, [&]() { parseUserInput(); }); } std::string NewSymbolDialog::parseName(QString& error_message) { std::string name = m_ui.nameLineEdit->text().toStdString(); if (name.empty()) error_message = tr("Name is empty."); return name; } u32 NewSymbolDialog::parseAddress(QString& error_message) { bool ok; u32 address = m_ui.addressLineEdit->text().toUInt(&ok, 16); if (!ok) error_message = tr("Address is not valid."); if (address % m_alignment != 0) error_message = tr("Address is not aligned."); return address; } // ***************************************************************************** NewFunctionDialog::NewFunctionDialog(DebugInterface& cpu, QWidget* parent) : NewSymbolDialog(GLOBAL_STORAGE | SIZE_FIELD | EXISTING_FUNCTIONS_FIELD, 4, cpu, parent) { setWindowTitle("New Function"); m_ui.customSizeSpinBox->setValue(8); } bool NewFunctionDialog::parseUserInput() { QString error_message; m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { m_name = parseName(error_message); if (!error_message.isEmpty()) return; m_address = parseAddress(error_message); if (!error_message.isEmpty()) return; m_size = 0; switch (functionSizeType()) { case FILL_EXISTING_FUNCTION: { std::optional fill_existing_function_size = fillExistingFunctionSize(m_address, database); if (!fill_existing_function_size.has_value()) { error_message = tr("No existing function found."); return; } m_size = *fill_existing_function_size; break; } case FILL_EMPTY_SPACE: { std::optional fill_space_size = fillEmptySpaceSize(m_address, database); if (!fill_space_size.has_value()) { error_message = tr("No next symbol found."); return; } m_size = *fill_space_size; break; } case CUSTOM_SIZE: { m_size = m_ui.customSizeSpinBox->value(); break; } } if (m_size == 0 || m_size > 256 * 1024 * 1024) { error_message = tr("Size is invalid."); return; } if (m_size % 4 != 0) { error_message = tr("Size is not a multiple of 4."); return; } // Handle an existing function if it exists. const ccc::Function* existing_function = database.functions.symbol_overlapping_address(m_address); m_existing_function = ccc::FunctionHandle(); if (existing_function) { if (existing_function->address().value == m_address) { error_message = tr("A function already exists at that address."); return; } if (m_ui.shrinkExistingRadioButton->isChecked()) { m_new_existing_function_size = m_address - existing_function->address().value; m_existing_function = existing_function->handle(); } } }); updateErrorMessage(error_message); return error_message.isEmpty(); } void NewFunctionDialog::createSymbol() { if (!parseUserInput()) return; QString error_message; m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { ccc::Result source = database.get_symbol_source("User-defined"); if (!source.success()) { error_message = tr("Cannot create symbol source."); return; } ccc::Result function = database.functions.create_symbol(std::move(m_name), m_address, *source, nullptr); if (!function.success()) { error_message = tr("Cannot create symbol."); return; } (*function)->set_size(m_size); ccc::Function* existing_function = database.functions.symbol_from_handle(m_existing_function); if (existing_function) existing_function->set_size(m_new_existing_function_size); }); if (!error_message.isEmpty()) QMessageBox::warning(this, tr("Cannot Create Function"), error_message); } // ***************************************************************************** NewGlobalVariableDialog::NewGlobalVariableDialog(DebugInterface& cpu, QWidget* parent) : NewSymbolDialog(GLOBAL_STORAGE | TYPE_FIELD, 1, cpu, parent) { setWindowTitle("New Global Variable"); } bool NewGlobalVariableDialog::parseUserInput() { QString error_message; m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { m_name = parseName(error_message); if (!error_message.isEmpty()) return; m_address = parseAddress(error_message); if (!error_message.isEmpty()) return; m_type = stringToType(m_ui.typeLineEdit->text().toStdString(), database, error_message); if (!error_message.isEmpty()) return; }); updateErrorMessage(error_message); return error_message.isEmpty(); } void NewGlobalVariableDialog::createSymbol() { if (!parseUserInput()) return; QString error_message; m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { ccc::Result source = database.get_symbol_source("User-defined"); if (!source.success()) { error_message = tr("Cannot create symbol source."); return; } ccc::Result global_variable = database.global_variables.create_symbol(std::move(m_name), m_address, *source, nullptr); if (!global_variable.success()) { error_message = tr("Cannot create symbol."); return; } (*global_variable)->set_type(std::move(m_type)); }); if (!error_message.isEmpty()) QMessageBox::warning(this, tr("Cannot Create Global Variable"), error_message); } // ***************************************************************************** NewLocalVariableDialog::NewLocalVariableDialog(DebugInterface& cpu, QWidget* parent) : NewSymbolDialog(GLOBAL_STORAGE | REGISTER_STORAGE | STACK_STORAGE | TYPE_FIELD | FUNCTION_FIELD, 1, cpu, parent) { setWindowTitle("New Local Variable"); } bool NewLocalVariableDialog::parseUserInput() { QString error_message; m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { m_name = parseName(error_message); if (!error_message.isEmpty()) return; int function_index = m_ui.functionComboBox->currentIndex(); if (function_index > 0 && function_index < (int)m_functions.size()) m_function = m_functions[m_ui.functionComboBox->currentIndex()]; else m_function = ccc::FunctionHandle(); const ccc::Function* function = database.functions.symbol_from_handle(m_function); if (!function) { error_message = tr("Invalid function."); return; } switch (storageType()) { case GLOBAL_STORAGE: { m_storage.emplace(); m_address = parseAddress(error_message); if (!error_message.isEmpty()) return; break; } case REGISTER_STORAGE: { ccc::RegisterStorage& register_storage = m_storage.emplace(); register_storage.dbx_register_number = m_ui.registerComboBox->currentIndex(); break; } case STACK_STORAGE: { ccc::StackStorage& stack_storage = m_storage.emplace(); stack_storage.stack_pointer_offset = m_ui.stackPointerOffsetSpinBox->value(); // Convert to caller sp relative. if (std::optional stack_frame_size = m_cpu.getStackFrameSize(*function)) stack_storage.stack_pointer_offset -= *stack_frame_size; else { error_message = tr("Cannot determine stack frame size of selected function."); return; } break; } } std::string type_string = m_ui.typeLineEdit->text().toStdString(); m_type = stringToType(type_string, database, error_message); if (!error_message.isEmpty()) return; }); updateErrorMessage(error_message); return error_message.isEmpty(); } void NewLocalVariableDialog::createSymbol() { if (!parseUserInput()) return; QString error_message; m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { ccc::Function* function = database.functions.symbol_from_handle(m_function); if (!function) { error_message = tr("Invalid function."); return; } ccc::Result source = database.get_symbol_source("User-defined"); if (!source.success()) { error_message = tr("Cannot create symbol source."); return; } ccc::Result local_variable = database.local_variables.create_symbol(std::move(m_name), m_address, *source, nullptr); if (!local_variable.success()) { error_message = tr("Cannot create symbol."); return; } (*local_variable)->set_type(std::move(m_type)); (*local_variable)->storage = m_storage; std::vector local_variables; if (function->local_variables().has_value()) local_variables = *function->local_variables(); local_variables.emplace_back((*local_variable)->handle()); function->set_local_variables(local_variables, database); }); if (!error_message.isEmpty()) QMessageBox::warning(this, tr("Cannot Create Local Variable"), error_message); } // ***************************************************************************** NewParameterVariableDialog::NewParameterVariableDialog(DebugInterface& cpu, QWidget* parent) : NewSymbolDialog(REGISTER_STORAGE | STACK_STORAGE | TYPE_FIELD | FUNCTION_FIELD, 1, cpu, parent) { setWindowTitle("New Parameter Variable"); } bool NewParameterVariableDialog::parseUserInput() { QString error_message; m_cpu.GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { m_name = parseName(error_message); if (!error_message.isEmpty()) return; int function_index = m_ui.functionComboBox->currentIndex(); if (function_index > 0 && function_index < (int)m_functions.size()) m_function = m_functions[m_ui.functionComboBox->currentIndex()]; else m_function = ccc::FunctionHandle(); const ccc::Function* function = database.functions.symbol_from_handle(m_function); if (!function) { error_message = tr("Invalid function."); return; } std::variant storage; switch (storageType()) { case GLOBAL_STORAGE: { error_message = tr("Invalid storage type."); return; } case REGISTER_STORAGE: { ccc::RegisterStorage& register_storage = storage.emplace(); register_storage.dbx_register_number = m_ui.registerComboBox->currentIndex(); break; } case STACK_STORAGE: { ccc::StackStorage& stack_storage = storage.emplace(); stack_storage.stack_pointer_offset = m_ui.stackPointerOffsetSpinBox->value(); // Convert to caller sp relative. if (std::optional stack_frame_size = m_cpu.getStackFrameSize(*function)) stack_storage.stack_pointer_offset -= *stack_frame_size; else { error_message = tr("Cannot determine stack frame size of selected function."); return; } break; } } std::string type_string = m_ui.typeLineEdit->text().toStdString(); m_type = stringToType(type_string, database, error_message); if (!error_message.isEmpty()) return; }); updateErrorMessage(error_message); return error_message.isEmpty(); } void NewParameterVariableDialog::createSymbol() { if (!parseUserInput()) return; QString error_message; m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { ccc::Function* function = database.functions.symbol_from_handle(m_function); if (!function) { error_message = tr("Invalid function."); return; } ccc::Result source = database.get_symbol_source("User-defined"); if (!source.success()) { error_message = tr("Cannot create symbol source."); return; } ccc::Result parameter_variable = database.parameter_variables.create_symbol(std::move(m_name), *source, nullptr); if (!parameter_variable.success()) { error_message = tr("Cannot create symbol."); return; } (*parameter_variable)->set_type(std::move(m_type)); (*parameter_variable)->storage = m_storage; std::vector parameter_variables; if (function->parameter_variables().has_value()) parameter_variables = *function->parameter_variables(); parameter_variables.emplace_back((*parameter_variable)->handle()); function->set_parameter_variables(parameter_variables, database); }); if (!error_message.isEmpty()) QMessageBox::warning(this, tr("Cannot Create Parameter Variable"), error_message); }