From cce0ae43697388cbce882fe320dd6ea9662236ba Mon Sep 17 00:00:00 2001 From: chaoticgd <43898262+chaoticgd@users.noreply.github.com> Date: Tue, 12 Nov 2024 23:03:04 +0000 Subject: [PATCH] Debugger: Allow loading symbols conditionally and with a base address --- 3rdparty/ccc/src/ccc/symbol_table.cpp | 6 +- 3rdparty/ccc/src/ccc/symbol_table.h | 3 +- .../Settings/DebugAnalysisSettingsWidget.cpp | 165 +++++-- .../Settings/DebugAnalysisSettingsWidget.h | 17 +- .../Settings/DebugAnalysisSettingsWidget.ui | 22 +- pcsx2/Config.h | 4 +- pcsx2/DebugTools/DebugInterface.cpp | 461 +++++++++--------- pcsx2/DebugTools/DebugInterface.h | 25 +- pcsx2/DebugTools/SymbolImporter.cpp | 96 +++- pcsx2/DebugTools/SymbolImporter.h | 9 + pcsx2/Pcsx2Config.cpp | 2 + 11 files changed, 511 insertions(+), 299 deletions(-) diff --git a/3rdparty/ccc/src/ccc/symbol_table.cpp b/3rdparty/ccc/src/ccc/symbol_table.cpp index d8b6db6428..863e23633f 100644 --- a/3rdparty/ccc/src/ccc/symbol_table.cpp +++ b/3rdparty/ccc/src/ccc/symbol_table.cpp @@ -100,8 +100,9 @@ Result> create_elf_symbol_table( Result import_symbol_tables( SymbolDatabase& database, - std::string module_name, const std::vector>& symbol_tables, + std::string module_name, + Address base_address, u32 importer_flags, DemanglerFunctions demangler, const std::atomic_bool* interrupt) @@ -109,7 +110,8 @@ Result import_symbol_tables( Result module_source = database.get_symbol_source("Symbol Table Importer"); CCC_RETURN_IF_ERROR(module_source); - Result module_symbol = database.modules.create_symbol(std::move(module_name), *module_source, nullptr); + Result module_symbol = database.modules.create_symbol( + std::move(module_name), base_address, *module_source, nullptr); CCC_RETURN_IF_ERROR(module_symbol); ModuleHandle module_handle = (*module_symbol)->handle(); diff --git a/3rdparty/ccc/src/ccc/symbol_table.h b/3rdparty/ccc/src/ccc/symbol_table.h index 72f5ccde11..84c8460053 100644 --- a/3rdparty/ccc/src/ccc/symbol_table.h +++ b/3rdparty/ccc/src/ccc/symbol_table.h @@ -71,8 +71,9 @@ Result> create_elf_symbol_table( // and to generate a module handle. Result import_symbol_tables( SymbolDatabase& database, - std::string module_name, const std::vector>& symbol_tables, + std::string module_name, + Address base_address, u32 importer_flags, DemanglerFunctions demangler, const std::atomic_bool* interrupt); diff --git a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.cpp b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.cpp index a0fe6cc038..f3fa4d1021 100644 --- a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.cpp +++ b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.cpp @@ -104,7 +104,7 @@ DebugAnalysisSettingsWidget::DebugAnalysisSettingsWidget(SettingsWindow* dialog, else { m_ui.symbolFileLabel->hide(); - m_ui.symbolFileList->hide(); + m_ui.symbolFileTable->hide(); m_ui.importSymbolFileButtons->hide(); } @@ -165,18 +165,19 @@ void DebugAnalysisSettingsWidget::parseSettingsFromWidgets(Pcsx2Config::DebugAna output.DemangleSymbols = m_ui.demangleSymbols->isChecked(); output.DemangleParameters = m_ui.demangleParameters->isChecked(); - for (int i = 0; i < m_ui.symbolFileList->count(); i++) + for (int i = 0; i < m_symbol_file_model->rowCount(); i++) { DebugExtraSymbolFile& file = output.ExtraSymbolFiles.emplace_back(); - file.Path = m_ui.symbolFileList->item(i)->text().toStdString(); + + file.Path = m_symbol_file_model->item(i, PATH_COLUMN)->text().toStdString(); + file.BaseAddress = m_symbol_file_model->item(i, BASE_ADDRESS_COLUMN)->text().toStdString(); + file.Condition = m_symbol_file_model->item(i, CONDITION_COLUMN)->text().toStdString(); } output.FunctionScanMode = static_cast(m_ui.functionScanMode->currentIndex()); output.CustomFunctionScanRange = m_ui.customAddressRange->isChecked(); output.FunctionScanStartAddress = m_ui.addressRangeStart->text().toStdString(); output.FunctionScanEndAddress = m_ui.addressRangeEnd->text().toStdString(); - - output.GenerateFunctionHashes = m_ui.grayOutOverwrittenFunctions->isChecked(); } void DebugAnalysisSettingsWidget::setupSymbolSourceGrid() @@ -187,27 +188,12 @@ void DebugAnalysisSettingsWidget::setupSymbolSourceGrid() { // Add symbol sources for which the user has already selected whether or // not they should be cleared. - int existing_symbol_source_count; - if (m_dialog) - existing_symbol_source_count = m_dialog->getEffectiveIntValue("Debugger/Analysis/SymbolSources", "Count", 0); - else - existing_symbol_source_count = Host::GetIntSettingValue("Debugger/Analysis/SymbolSources", "Count", 0); - + int existing_symbol_source_count = getIntSettingValue("Debugger/Analysis/SymbolSources", "Count", 0); for (int i = 0; i < existing_symbol_source_count; i++) { std::string section = "Debugger/Analysis/SymbolSources/" + std::to_string(i); - - std::string name; - if (m_dialog) - name = m_dialog->getEffectiveStringValue(section.c_str(), "Name", ""); - else - name = Host::GetStringSettingValue(section.c_str(), "Name", ""); - - bool value; - if (m_dialog) - value = m_dialog->getEffectiveBoolValue(section.c_str(), "ClearDuringAnalysis", false); - else - value = Host::GetBoolSettingValue(section.c_str(), "ClearDuringAnalysis", false); + std::string name = getStringSettingValue(section.c_str(), "Name", ""); + bool value = getBoolSettingValue(section.c_str(), "ClearDuringAnalysis", false); SymbolSourceTemp& source = m_symbol_sources[name]; source.previous_value = value; @@ -320,45 +306,100 @@ void DebugAnalysisSettingsWidget::saveSymbolSources() void DebugAnalysisSettingsWidget::setupSymbolFileList() { - int extra_symbol_file_count; - if (m_dialog) - extra_symbol_file_count = m_dialog->getEffectiveIntValue("Debugger/Analysis/ExtraSymbolFiles", "Count", 0); - else - extra_symbol_file_count = Host::GetIntSettingValue("Debugger/Analysis/ExtraSymbolFiles", "Count", 0); + m_symbol_file_model = new QStandardItemModel(0, SYMBOL_FILE_COLUMN_COUNT, m_ui.symbolFileTable); + QStringList headers; + headers.emplace_back(tr("Path")); + headers.emplace_back(tr("Base Address")); + headers.emplace_back(tr("Condition")); + m_symbol_file_model->setHorizontalHeaderLabels(headers); + + m_ui.symbolFileTable->setModel(m_symbol_file_model); + + m_ui.symbolFileTable->horizontalHeader()->setSectionResizeMode(PATH_COLUMN, QHeaderView::Stretch); + m_ui.symbolFileTable->horizontalHeader()->setSectionResizeMode(BASE_ADDRESS_COLUMN, QHeaderView::Fixed); + m_ui.symbolFileTable->horizontalHeader()->setSectionResizeMode(CONDITION_COLUMN, QHeaderView::Fixed); + + m_ui.symbolFileTable->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + + int extra_symbol_file_count = getIntSettingValue("Debugger/Analysis/ExtraSymbolFiles", "Count", 0); for (int i = 0; i < extra_symbol_file_count; i++) { std::string section = "Debugger/Analysis/ExtraSymbolFiles/" + std::to_string(i); - std::string path; - if (m_dialog) - path = m_dialog->getEffectiveStringValue(section.c_str(), "Path", ""); - else - path = Host::GetStringSettingValue(section.c_str(), "Path", ""); - m_ui.symbolFileList->addItem(QString::fromStdString(path)); + int row = m_symbol_file_model->rowCount(); + if (!m_symbol_file_model->insertRow(row)) + continue; + + QStandardItem* path_item = new QStandardItem(); + path_item->setText(QString::fromStdString(getStringSettingValue(section.c_str(), "Path", ""))); + m_symbol_file_model->setItem(row, PATH_COLUMN, path_item); + + QStandardItem* base_address_item = new QStandardItem(); + base_address_item->setText(QString::fromStdString(getStringSettingValue(section.c_str(), "BaseAddress"))); + m_symbol_file_model->setItem(row, BASE_ADDRESS_COLUMN, base_address_item); + + QStandardItem* condition_item = new QStandardItem(); + condition_item->setText(QString::fromStdString(getStringSettingValue(section.c_str(), "Condition"))); + m_symbol_file_model->setItem(row, CONDITION_COLUMN, condition_item); } connect(m_ui.addSymbolFile, &QPushButton::clicked, this, &DebugAnalysisSettingsWidget::addSymbolFile); connect(m_ui.removeSymbolFile, &QPushButton::clicked, this, &DebugAnalysisSettingsWidget::removeSymbolFile); + + connect(m_ui.symbolFileTable->selectionModel(), &QItemSelectionModel::selectionChanged, + this, &DebugAnalysisSettingsWidget::updateEnabledStates); + + connect(m_symbol_file_model, &QStandardItemModel::dataChanged, + this, &DebugAnalysisSettingsWidget::saveSymbolFiles); + connect(m_symbol_file_model, &QStandardItemModel::dataChanged, + this, &DebugAnalysisSettingsWidget::updateEnabledStates); } void DebugAnalysisSettingsWidget::addSymbolFile() { - QString path = QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, tr("Add Symbol File"))); - if (path.isEmpty()) + std::string path = Path::ToNativePath(QFileDialog::getOpenFileName(this, tr("Add Symbol File")).toStdString()); + if (path.empty()) return; - m_ui.symbolFileList->addItem(path); + std::string relative_path = Path::MakeRelative(path, EmuFolders::GameSettings); + if (!relative_path.starts_with("..")) + path = std::move(relative_path); + + int row = m_symbol_file_model->rowCount(); + if (!m_symbol_file_model->insertRow(row)) + return; + + QStandardItem* path_item = new QStandardItem(); + path_item->setText(QString::fromStdString(path)); + m_symbol_file_model->setItem(row, PATH_COLUMN, path_item); + + QStandardItem* base_address_item = new QStandardItem(); + base_address_item->setText(""); + m_symbol_file_model->setItem(row, BASE_ADDRESS_COLUMN, base_address_item); + + QStandardItem* condition_item = new QStandardItem(); + condition_item->setText(""); + m_symbol_file_model->setItem(row, CONDITION_COLUMN, condition_item); saveSymbolFiles(); + updateEnabledStates(); } void DebugAnalysisSettingsWidget::removeSymbolFile() { - for (QListWidgetItem* item : m_ui.symbolFileList->selectedItems()) - delete item; + QItemSelectionModel* selection_model = m_ui.symbolFileTable->selectionModel(); + if (!selection_model) + return; + + while (!selection_model->selectedIndexes().isEmpty()) + { + QModelIndex index = selection_model->selectedIndexes().first(); + m_symbol_file_model->removeRow(index.row(), index.parent()); + } saveSymbolFiles(); + updateEnabledStates(); } void DebugAnalysisSettingsWidget::saveSymbolFiles() @@ -380,17 +421,24 @@ void DebugAnalysisSettingsWidget::saveSymbolFiles() sif->RemoveSection("Debugger/Analysis/ExtraSymbolFiles"); - if (m_ui.symbolFileList->count() == 0) + if (m_symbol_file_model->rowCount() == 0) return; // Make new configuration entries. - sif->SetIntValue("Debugger/Analysis/ExtraSymbolFiles", "Count", m_ui.symbolFileList->count()); + sif->SetIntValue("Debugger/Analysis/ExtraSymbolFiles", "Count", m_symbol_file_model->rowCount()); - for (int i = 0; i < m_ui.symbolFileList->count(); i++) + for (int i = 0; i < m_symbol_file_model->rowCount(); i++) { std::string section = "Debugger/Analysis/ExtraSymbolFiles/" + std::to_string(i); - std::string path = m_ui.symbolFileList->item(i)->text().toStdString(); - sif->SetStringValue(section.c_str(), "Path", path.c_str()); + + if (QStandardItem* path_item = m_symbol_file_model->item(i, PATH_COLUMN)) + sif->SetStringValue(section.c_str(), "Path", path_item->text().toStdString().c_str()); + + if (QStandardItem* base_address_item = m_symbol_file_model->item(i, BASE_ADDRESS_COLUMN)) + sif->SetStringValue(section.c_str(), "BaseAddress", base_address_item->text().toStdString().c_str()); + + if (QStandardItem* condition_item = m_symbol_file_model->item(i, CONDITION_COLUMN)) + sif->SetStringValue(section.c_str(), "Condition", condition_item->text().toStdString().c_str()); } QtHost::SaveGameSettings(sif, true); @@ -423,5 +471,34 @@ void DebugAnalysisSettingsWidget::updateEnabledStates() m_ui.symbolSourceScrollArea->setEnabled(!m_ui.automaticallyClearSymbols->isChecked()); m_ui.symbolSourceErrorMessage->setEnabled(!m_ui.automaticallyClearSymbols->isChecked()); m_ui.demangleParameters->setEnabled(m_ui.demangleSymbols->isChecked()); + m_ui.removeSymbolFile->setEnabled( + m_ui.symbolFileTable->selectionModel() && m_ui.symbolFileTable->selectionModel()->hasSelection()); m_ui.customAddressRangeLineEdits->setEnabled(m_ui.customAddressRange->isChecked()); } + +std::string DebugAnalysisSettingsWidget::getStringSettingValue( + const char* section, const char* key, const char* default_value) +{ + if (m_dialog) + return m_dialog->getEffectiveStringValue(section, key, default_value); + + return Host::GetStringSettingValue(section, key, default_value); +} + +bool DebugAnalysisSettingsWidget::getBoolSettingValue( + const char* section, const char* key, bool default_value) +{ + if (m_dialog) + return m_dialog->getEffectiveBoolValue(section, key, default_value); + + return Host::GetBoolSettingValue(section, key, default_value); +} + +int DebugAnalysisSettingsWidget::getIntSettingValue( + const char* section, const char* key, int default_value) +{ + if (m_dialog) + return m_dialog->getEffectiveIntValue(section, key, default_value); + + return Host::GetIntSettingValue(section, key, default_value); +} diff --git a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h index 52d81c1f05..77192e65a3 100644 --- a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h +++ b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h @@ -6,6 +6,7 @@ #include "Config.h" +#include #include class SettingsWindow; @@ -16,7 +17,7 @@ class DebugAnalysisSettingsWidget : public QWidget public: // Create a widget that will discard any settings changed after it is - // closed, for use in the dialog opened by the "Reanalyze" button. + // closed, for use in the dialog opened by the "Analyze" button. DebugAnalysisSettingsWidget(QWidget* parent = nullptr); // Create a widget that will write back any settings changed to the config @@ -42,6 +43,10 @@ protected: void updateEnabledStates(); + std::string getStringSettingValue(const char* section, const char* key, const char* default_value = ""); + bool getBoolSettingValue(const char* section, const char* key, bool default_value = false); + int getIntSettingValue(const char* section, const char* key, int default_value = 0); + struct SymbolSourceTemp { QCheckBox* check_box = nullptr; @@ -49,8 +54,18 @@ protected: bool modified_by_user = false; }; + enum SymbolFileColumn + { + PATH_COLUMN = 0, + BASE_ADDRESS_COLUMN = 1, + CONDITION_COLUMN = 2, + SYMBOL_FILE_COLUMN_COUNT = 3 + }; + SettingsWindow* m_dialog = nullptr; std::map m_symbol_sources; + QStandardItemModel* m_symbol_file_model; + Ui::DebugAnalysisSettingsWidget m_ui; }; diff --git a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.ui b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.ui index 9f6575cbba..4b2759ef0b 100644 --- a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.ui +++ b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.ui @@ -171,7 +171,7 @@ - + 0 @@ -184,9 +184,27 @@ 100 - + + true + + + QAbstractItemView::SelectRows + + + Qt::ElideLeft + + false + + false + + + true + + + false + diff --git a/pcsx2/Config.h b/pcsx2/Config.h index fc64f1c22b..ca331e9768 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -208,6 +208,8 @@ struct DebugSymbolSource struct DebugExtraSymbolFile { std::string Path; + std::string BaseAddress; + std::string Condition; friend auto operator<=>(const DebugExtraSymbolFile& lhs, const DebugExtraSymbolFile& rhs) = default; }; @@ -1276,7 +1278,7 @@ struct Pcsx2Config EnableGameFixes : 1, // enables automatic game fixes SaveStateOnShutdown : 1, // default value for saving state on shutdown EnableDiscordPresence : 1, // enables discord rich presence integration - UseSavestateSelector: 1, + UseSavestateSelector : 1, InhibitScreensaver : 1, BackupSavestate : 1, McdFolderAutoManage : 1, diff --git a/pcsx2/DebugTools/DebugInterface.cpp b/pcsx2/DebugTools/DebugInterface.cpp index 8554ffa109..5e8a67fb20 100644 --- a/pcsx2/DebugTools/DebugInterface.cpp +++ b/pcsx2/DebugTools/DebugInterface.cpp @@ -34,227 +34,6 @@ enum ReferenceIndexType REF_INDEX_VFPU = 0x10000, REF_INDEX_VFPU_INT = 0x20000, REF_INDEX_IS_FLOAT = REF_INDEX_FPU | REF_INDEX_VFPU, - -}; - - -class MipsExpressionFunctions : public IExpressionFunctions -{ -public: - explicit MipsExpressionFunctions(DebugInterface* cpu, bool enumerateSymbols) - : m_cpu(cpu) - { - if (!enumerateSymbols) - return; - - m_cpu->GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { - for (const ccc::Function& function : database.functions) - m_mangled_function_names_to_handles.emplace(function.mangled_name(), function.handle()); - - for (const ccc::GlobalVariable& global : database.global_variables) - m_mangled_global_names_to_handles.emplace(global.mangled_name(), global.handle()); - }); - } - - virtual bool parseReference(char* str, u64& referenceIndex) - { - for (int i = 0; i < 32; i++) - { - char reg[8]; - std::snprintf(reg, std::size(reg), "r%d", i); - if (StringUtil::Strcasecmp(str, reg) == 0 || StringUtil::Strcasecmp(str, m_cpu->getRegisterName(0, i)) == 0) - { - referenceIndex = i; - return true; - } - - std::snprintf(reg, std::size(reg), "f%d", i); - if (StringUtil::Strcasecmp(str, reg) == 0) - { - referenceIndex = i | REF_INDEX_FPU; - return true; - } - } - - if (StringUtil::Strcasecmp(str, "pc") == 0) - { - referenceIndex = REF_INDEX_PC; - return true; - } - - if (StringUtil::Strcasecmp(str, "hi") == 0) - { - referenceIndex = REF_INDEX_HI; - return true; - } - - if (StringUtil::Strcasecmp(str, "lo") == 0) - { - referenceIndex = REF_INDEX_LO; - return true; - } - - if (StringUtil::Strcasecmp(str, "target") == 0) - { - referenceIndex = REF_INDEX_OPTARGET; - return true; - } - - if (StringUtil::Strcasecmp(str, "load") == 0) - { - referenceIndex = REF_INDEX_OPLOAD; - return true; - } - - if (StringUtil::Strcasecmp(str, "store") == 0) - { - referenceIndex = REF_INDEX_OPSTORE; - return true; - } - return false; - } - - virtual bool parseSymbol(char* str, u64& symbolValue) - { - bool success = false; - m_cpu->GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { - std::string name = str; - - // Check for mangled function names. - auto function_iterator = m_mangled_function_names_to_handles.find(name); - if (function_iterator != m_mangled_function_names_to_handles.end()) - { - const ccc::Function* function = database.functions.symbol_from_handle(function_iterator->second); - if (function && function->address().valid()) - { - symbolValue = function->address().value; - success = true; - return; - } - } - - // Check for mangled global variable names. - auto global_iterator = m_mangled_global_names_to_handles.find(name); - if (global_iterator != m_mangled_global_names_to_handles.end()) - { - const ccc::GlobalVariable* global = database.global_variables.symbol_from_handle(global_iterator->second); - if (global && global->address().valid()) - { - symbolValue = global->address().value; - success = true; - return; - } - } - - // Check for regular unmangled names. - const ccc::Symbol* symbol = database.symbol_with_name(name); - if (symbol && symbol->address().valid()) - { - symbolValue = symbol->address().value; - success = true; - return; - } - }); - - return success; - } - - virtual u64 getReferenceValue(u64 referenceIndex) - { - if (referenceIndex < 32) - return m_cpu->getRegister(0, referenceIndex)._u64[0]; - if (referenceIndex == REF_INDEX_PC) - return m_cpu->getPC(); - if (referenceIndex == REF_INDEX_HI) - return m_cpu->getHI()._u64[0]; - if (referenceIndex == REF_INDEX_LO) - return m_cpu->getLO()._u64[0]; - if (referenceIndex & REF_INDEX_IS_OPSL) - { - const u32 OP = m_cpu->read32(m_cpu->getPC()); - const R5900::OPCODE& opcode = R5900::GetInstruction(OP); - if (opcode.flags & IS_MEMORY) - { - // Fetch the address in the base register - u32 target = cpuRegs.GPR.r[(OP >> 21) & 0x1F].UD[0]; - // Add the offset (lower 16 bits) - target += static_cast(OP); - - if (referenceIndex & REF_INDEX_OPTARGET) - { - return target; - } - else if (referenceIndex & REF_INDEX_OPLOAD) - { - return (opcode.flags & IS_LOAD) ? target : 0; - } - else if (referenceIndex & REF_INDEX_OPSTORE) - { - return (opcode.flags & IS_STORE) ? target : 0; - } - } - return 0; - } - if (referenceIndex & REF_INDEX_FPU) - { - return m_cpu->getRegister(EECAT_FPR, referenceIndex & 0x1F)._u64[0]; - } - return -1; - } - - virtual ExpressionType getReferenceType(u64 referenceIndex) - { - if (referenceIndex & REF_INDEX_IS_FLOAT) - { - return EXPR_TYPE_FLOAT; - } - return EXPR_TYPE_UINT; - } - - virtual bool getMemoryValue(u32 address, int size, u64& dest, std::string& error) - { - switch (size) - { - case 1: - case 2: - case 4: - case 8: - break; - default: - error = StringUtil::StdStringFromFormat( - TRANSLATE("ExpressionParser", "Invalid memory access size %d."), size); - return false; - } - - if (address % size) - { - error = TRANSLATE("ExpressionParser", "Invalid memory access (unaligned)."); - return false; - } - - switch (size) - { - case 1: - dest = m_cpu->read8(address); - break; - case 2: - dest = m_cpu->read16(address); - break; - case 4: - dest = m_cpu->read32(address); - break; - case 8: - dest = m_cpu->read64(address); - break; - } - - return true; - } - -protected: - DebugInterface* m_cpu; - std::map m_mangled_function_names_to_handles; - std::map m_mangled_global_names_to_handles; }; // @@ -369,13 +148,13 @@ bool DebugInterface::evaluateExpression(const char* expression, u64& dest, std:: bool DebugInterface::initExpression(const char* exp, PostfixExpression& dest, std::string& error) { - MipsExpressionFunctions funcs(this, true); + MipsExpressionFunctions funcs(this, nullptr, true); return initPostfixExpression(exp, &funcs, dest, error); } bool DebugInterface::parseExpression(PostfixExpression& exp, u64& dest, std::string& error) { - MipsExpressionFunctions funcs(this, false); + MipsExpressionFunctions funcs(this, nullptr, false); return parsePostfixExpression(exp, &funcs, dest, error); } @@ -1306,3 +1085,239 @@ u64 ElfMemoryReader::read64(u32 address, bool& valid) return *result; } + +// +// MipsExpressionFunctions +// + +MipsExpressionFunctions::MipsExpressionFunctions( + DebugInterface* cpu, const ccc::SymbolDatabase* symbolDatabase, bool shouldEnumerateSymbols) + : m_cpu(cpu) + , m_database(symbolDatabase) +{ + if (!shouldEnumerateSymbols) + return; + + if (symbolDatabase) + { + enumerateSymbols(*symbolDatabase); + } + else + { + m_cpu->GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + enumerateSymbols(database); + }); + } +} + +void MipsExpressionFunctions::enumerateSymbols(const ccc::SymbolDatabase& database) +{ + // TODO: Add mangled symbol name maps to CCC and remove this. + + for (const ccc::Function& function : database.functions) + m_mangled_function_names_to_handles.emplace(function.mangled_name(), function.handle()); + + for (const ccc::GlobalVariable& global : database.global_variables) + m_mangled_global_names_to_handles.emplace(global.mangled_name(), global.handle()); +} + +bool MipsExpressionFunctions::parseReference(char* str, u64& referenceIndex) +{ + for (int i = 0; i < 32; i++) + { + char reg[8]; + std::snprintf(reg, std::size(reg), "r%d", i); + if (StringUtil::Strcasecmp(str, reg) == 0 || StringUtil::Strcasecmp(str, m_cpu->getRegisterName(0, i)) == 0) + { + referenceIndex = i; + return true; + } + + std::snprintf(reg, std::size(reg), "f%d", i); + if (StringUtil::Strcasecmp(str, reg) == 0) + { + referenceIndex = i | REF_INDEX_FPU; + return true; + } + } + + if (StringUtil::Strcasecmp(str, "pc") == 0) + { + referenceIndex = REF_INDEX_PC; + return true; + } + + if (StringUtil::Strcasecmp(str, "hi") == 0) + { + referenceIndex = REF_INDEX_HI; + return true; + } + + if (StringUtil::Strcasecmp(str, "lo") == 0) + { + referenceIndex = REF_INDEX_LO; + return true; + } + + if (StringUtil::Strcasecmp(str, "target") == 0) + { + referenceIndex = REF_INDEX_OPTARGET; + return true; + } + + if (StringUtil::Strcasecmp(str, "load") == 0) + { + referenceIndex = REF_INDEX_OPLOAD; + return true; + } + + if (StringUtil::Strcasecmp(str, "store") == 0) + { + referenceIndex = REF_INDEX_OPSTORE; + return true; + } + return false; +} + +bool MipsExpressionFunctions::parseSymbol(char* str, u64& symbolValue) +{ + if (m_database) + return parseSymbol(str, symbolValue, *m_database); + + bool success = false; + m_cpu->GetSymbolGuardian().Read([&](const ccc::SymbolDatabase& database) { + success = parseSymbol(str, symbolValue, database); + }); + return success; +} + +bool MipsExpressionFunctions::parseSymbol(char* str, u64& symbolValue, const ccc::SymbolDatabase& database) +{ + std::string name = str; + + // Check for mangled function names. + auto function_iterator = m_mangled_function_names_to_handles.find(name); + if (function_iterator != m_mangled_function_names_to_handles.end()) + { + const ccc::Function* function = database.functions.symbol_from_handle(function_iterator->second); + if (function && function->address().valid()) + { + symbolValue = function->address().value; + return true; + } + } + + // Check for mangled global variable names. + auto global_iterator = m_mangled_global_names_to_handles.find(name); + if (global_iterator != m_mangled_global_names_to_handles.end()) + { + const ccc::GlobalVariable* global = database.global_variables.symbol_from_handle(global_iterator->second); + if (global && global->address().valid()) + { + symbolValue = global->address().value; + return true; + } + } + + // Check for regular unmangled names. + const ccc::Symbol* symbol = database.symbol_with_name(name); + if (symbol && symbol->address().valid()) + { + symbolValue = symbol->address().value; + return true; + } + + return false; +} + +u64 MipsExpressionFunctions::getReferenceValue(u64 referenceIndex) +{ + if (referenceIndex < 32) + return m_cpu->getRegister(0, referenceIndex)._u64[0]; + if (referenceIndex == REF_INDEX_PC) + return m_cpu->getPC(); + if (referenceIndex == REF_INDEX_HI) + return m_cpu->getHI()._u64[0]; + if (referenceIndex == REF_INDEX_LO) + return m_cpu->getLO()._u64[0]; + if (referenceIndex & REF_INDEX_IS_OPSL) + { + const u32 OP = m_cpu->read32(m_cpu->getPC()); + const R5900::OPCODE& opcode = R5900::GetInstruction(OP); + if (opcode.flags & IS_MEMORY) + { + // Fetch the address in the base register + u32 target = cpuRegs.GPR.r[(OP >> 21) & 0x1F].UD[0]; + // Add the offset (lower 16 bits) + target += static_cast(OP); + + if (referenceIndex & REF_INDEX_OPTARGET) + { + return target; + } + else if (referenceIndex & REF_INDEX_OPLOAD) + { + return (opcode.flags & IS_LOAD) ? target : 0; + } + else if (referenceIndex & REF_INDEX_OPSTORE) + { + return (opcode.flags & IS_STORE) ? target : 0; + } + } + return 0; + } + if (referenceIndex & REF_INDEX_FPU) + { + return m_cpu->getRegister(EECAT_FPR, referenceIndex & 0x1F)._u64[0]; + } + return -1; +} + +ExpressionType MipsExpressionFunctions::getReferenceType(u64 referenceIndex) +{ + if (referenceIndex & REF_INDEX_IS_FLOAT) + { + return EXPR_TYPE_FLOAT; + } + return EXPR_TYPE_UINT; +} + +bool MipsExpressionFunctions::getMemoryValue(u32 address, int size, u64& dest, std::string& error) +{ + switch (size) + { + case 1: + case 2: + case 4: + case 8: + break; + default: + error = StringUtil::StdStringFromFormat( + TRANSLATE("ExpressionParser", "Invalid memory access size %d."), size); + return false; + } + + if (address % size) + { + error = TRANSLATE("ExpressionParser", "Invalid memory access (unaligned)."); + return false; + } + + switch (size) + { + case 1: + dest = m_cpu->read8(address); + break; + case 2: + dest = m_cpu->read16(address); + break; + case 4: + dest = m_cpu->read32(address); + break; + case 8: + dest = m_cpu->read64(address); + break; + } + + return true; +} diff --git a/pcsx2/DebugTools/DebugInterface.h b/pcsx2/DebugTools/DebugInterface.h index 72111fe0ba..2626becb5d 100644 --- a/pcsx2/DebugTools/DebugInterface.h +++ b/pcsx2/DebugTools/DebugInterface.h @@ -2,8 +2,8 @@ // SPDX-License-Identifier: GPL-3.0+ #pragma once -#include "DebugTools/BiosDebugData.h" -#include "MemoryTypes.h" + +#include "BiosDebugData.h" #include "ExpressionParser.h" #include "SymbolGuardian.h" #include "SymbolImporter.h" @@ -210,5 +210,26 @@ protected: const ccc::ElfFile& m_elf; }; +class MipsExpressionFunctions : public IExpressionFunctions +{ +public: + MipsExpressionFunctions( + DebugInterface* cpu, const ccc::SymbolDatabase* symbolDatabase, bool shouldEnumerateSymbols); + + bool parseReference(char* str, u64& referenceIndex) override; + bool parseSymbol(char* str, u64& symbolValue) override; + u64 getReferenceValue(u64 referenceIndex) override; + ExpressionType getReferenceType(u64 referenceIndex) override; + bool getMemoryValue(u32 address, int size, u64& dest, std::string& error) override; + +protected: + void enumerateSymbols(const ccc::SymbolDatabase& database); + bool parseSymbol(char* str, u64& symbolValue, const ccc::SymbolDatabase& database); + DebugInterface* m_cpu; + const ccc::SymbolDatabase* m_database; + std::map m_mangled_function_names_to_handles; + std::map m_mangled_global_names_to_handles; +}; + extern R5900DebugInterface r5900Debug; extern R3000DebugInterface r3000Debug; diff --git a/pcsx2/DebugTools/SymbolImporter.cpp b/pcsx2/DebugTools/SymbolImporter.cpp index 4f9388c6d3..5c651efccf 100644 --- a/pcsx2/DebugTools/SymbolImporter.cpp +++ b/pcsx2/DebugTools/SymbolImporter.cpp @@ -276,13 +276,6 @@ void SymbolImporter::ImportSymbols( const std::map& builtin_types, const std::atomic_bool* interrupt) { - ccc::DemanglerFunctions demangler; - if (options.DemangleSymbols) - { - demangler.cplus_demangle = cplus_demangle; - demangler.cplus_demangle_opname = cplus_demangle_opname; - } - u32 importer_flags = ccc::NO_MEMBER_FUNCTIONS | ccc::NO_OPTIMIZED_OUT_FUNCTIONS | @@ -291,6 +284,13 @@ void SymbolImporter::ImportSymbols( if (options.DemangleParameters) importer_flags |= ccc::DEMANGLE_PARAMETERS; + ccc::DemanglerFunctions demangler; + if (options.DemangleSymbols) + { + demangler.cplus_demangle = cplus_demangle; + demangler.cplus_demangle_opname = cplus_demangle_opname; + } + if (options.ImportSymbolsFromELF) { ccc::Result>> symbol_tables = elf.get_all_symbol_tables(); @@ -301,7 +301,7 @@ void SymbolImporter::ImportSymbols( else { ccc::Result module_handle = ccc::import_symbol_tables( - database, elf.name(), *symbol_tables, importer_flags, demangler, interrupt); + database, *symbol_tables, elf.name(), ccc::Address(), importer_flags, demangler, interrupt); if (!module_handle.success()) { ccc::report_error(module_handle.error()); @@ -311,7 +311,7 @@ void SymbolImporter::ImportSymbols( if (!nocash_path.empty() && options.ImportSymFileFromDefaultLocation) { - ccc::Result nocash_result = ImportNocashSymbols(database, nocash_path, builtin_types); + ccc::Result nocash_result = ImportNocashSymbols(database, nocash_path, 0, builtin_types); if (!nocash_result.success()) { Console.Error("Failed to import symbol file '%s': %s", @@ -319,14 +319,64 @@ void SymbolImporter::ImportSymbols( } } + ImportExtraSymbols(database, options, builtin_types, importer_flags, demangler, interrupt); + + Console.WriteLn("Imported %d symbols.", database.symbol_count()); + + return; +} + +void SymbolImporter::ImportExtraSymbols( + ccc::SymbolDatabase& database, + const Pcsx2Config::DebugAnalysisOptions& options, + const std::map& builtin_types, + u32 importer_flags, + const ccc::DemanglerFunctions& demangler, + const std::atomic_bool* interrupt) +{ + MipsExpressionFunctions expression_functions(&r5900Debug, &database, true); + for (const DebugExtraSymbolFile& extra_symbol_file : options.ExtraSymbolFiles) { if (*interrupt) return; - if (StringUtil::EndsWithNoCase(extra_symbol_file.Path, ".sym")) + std::string path = Path::ToNativePath(extra_symbol_file.Path); + if (!Path::IsAbsolute(path)) + path = Path::Combine(EmuFolders::GameSettings, path); + + if (!extra_symbol_file.Condition.empty()) { - ccc::Result nocash_result = ImportNocashSymbols(database, extra_symbol_file.Path, builtin_types); + u64 expression_result = 0; + std::string error; + if (!parseExpression(extra_symbol_file.Condition.c_str(), &expression_functions, expression_result, error)) + { + Console.Error("Failed to parse condition expression '%s' while importing extra symbol file '%s': %s.", + extra_symbol_file.Condition.c_str(), path.c_str(), error.c_str()); + } + + if (!expression_result) + continue; + } + + ccc::Address base_address; + if (!extra_symbol_file.BaseAddress.empty()) + { + u64 expression_result = 0; + std::string error; + if (!parseExpression(extra_symbol_file.BaseAddress.c_str(), &expression_functions, expression_result, error)) + { + Console.Error("Failed to parse base address expression '%s' while importing extra symbol file '%s': %s.", + extra_symbol_file.BaseAddress.c_str(), path.c_str(), error.c_str()); + } + + base_address = static_cast(expression_result); + } + + if (StringUtil::EndsWithNoCase(path, ".sym")) + { + ccc::Result nocash_result = ImportNocashSymbols( + database, path, base_address.get_or_zero(), builtin_types); if (!nocash_result.success()) { Console.Error("Failed to import symbol file '%s': %s", @@ -334,29 +384,30 @@ void SymbolImporter::ImportSymbols( } if (!*nocash_result) - Console.Error("Cannot open symbol file '%s'.", extra_symbol_file.Path.c_str()); + Console.Error("Cannot open symbol file '%s'.", path.c_str()); continue; } - Error error; - std::optional> image = FileSystem::ReadBinaryFile(extra_symbol_file.Path.c_str()); + std::optional> image = FileSystem::ReadBinaryFile(path.c_str()); if (!image.has_value()) { - Console.Error("Failed to read extra symbol file '%s'.", extra_symbol_file.Path.c_str()); + Console.Error("Failed to read extra symbol file '%s'.", path.c_str()); continue; } - std::string file_name(Path::GetFileName(extra_symbol_file.Path)); + std::string file_name(Path::GetFileName(path)); - ccc::Result> symbol_file = ccc::parse_symbol_file(std::move(*image), file_name.c_str()); + ccc::Result> symbol_file = ccc::parse_symbol_file( + std::move(*image), file_name.c_str()); if (!symbol_file.success()) { ccc::report_error(symbol_file.error()); continue; } - ccc::Result>> symbol_tables = (*symbol_file)->get_all_symbol_tables(); + ccc::Result>> symbol_tables = + (*symbol_file)->get_all_symbol_tables(); if (!symbol_tables.success()) { ccc::report_error(symbol_tables.error()); @@ -364,22 +415,19 @@ void SymbolImporter::ImportSymbols( } ccc::Result module_handle = ccc::import_symbol_tables( - database, elf.name(), *symbol_tables, importer_flags, demangler, interrupt); + database, *symbol_tables, (*symbol_file)->name(), base_address, importer_flags, demangler, interrupt); if (!module_handle.success()) { ccc::report_error(module_handle.error()); continue; } } - - Console.WriteLn("Imported %d symbols.", database.symbol_count()); - - return; } ccc::Result SymbolImporter::ImportNocashSymbols( ccc::SymbolDatabase& database, const std::string& file_path, + u32 base_address, const std::map& builtin_types) { auto file = FileSystem::OpenManagedCFile(file_path.c_str(), "r"); @@ -405,6 +453,8 @@ ccc::Result SymbolImporter::ImportNocashSymbols( if (address == 0 && strcmp(value, "0") == 0) continue; + address += base_address; + if (value[0] == '.') { // data directives diff --git a/pcsx2/DebugTools/SymbolImporter.h b/pcsx2/DebugTools/SymbolImporter.h index ff210cb07b..ad0496d3d0 100644 --- a/pcsx2/DebugTools/SymbolImporter.h +++ b/pcsx2/DebugTools/SymbolImporter.h @@ -46,9 +46,18 @@ public: const std::map& builtin_types, const std::atomic_bool* interrupt); + static void ImportExtraSymbols( + ccc::SymbolDatabase& database, + const Pcsx2Config::DebugAnalysisOptions& options, + const std::map& builtin_types, + u32 importer_flags, + const ccc::DemanglerFunctions& demangler, + const std::atomic_bool* interrupt); + static ccc::Result ImportNocashSymbols( ccc::SymbolDatabase& database, const std::string& file_path, + u32 base_address, const std::map& builtin_types); static std::unique_ptr GetBuiltInType( diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 071ff140eb..655c11af58 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -1625,6 +1625,8 @@ void Pcsx2Config::DebugAnalysisOptions::LoadSave(SettingsWrapper& wrap) file = ExtraSymbolFiles[i]; SettingsWrapEntryEx(file.Path, "Path"); + SettingsWrapEntryEx(file.BaseAddress, "BaseAddress"); + SettingsWrapEntryEx(file.Condition, "Condition"); if (wrap.IsLoading()) ExtraSymbolFiles.emplace_back(std::move(file));