From 2c3abe33d5ee0782dc4fafc447c3ae2fca32e188 Mon Sep 17 00:00:00 2001 From: chaoticgd <43898262+chaoticgd@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:22:12 +0100 Subject: [PATCH] Debugger: Add settings to control the analysis passes --- pcsx2-qt/CMakeLists.txt | 6 + pcsx2-qt/Debugger/AnalysisOptionsDialog.cpp | 35 ++ pcsx2-qt/Debugger/AnalysisOptionsDialog.h | 25 + pcsx2-qt/Debugger/AnalysisOptionsDialog.ui | 113 +++++ pcsx2-qt/Debugger/DebuggerWindow.cpp | 27 +- pcsx2-qt/Debugger/DebuggerWindow.h | 5 + pcsx2-qt/Debugger/DebuggerWindow.ui | 41 +- .../Debugger/SymbolTree/NewSymbolDialogs.cpp | 8 +- .../Debugger/SymbolTree/NewSymbolDialogs.h | 16 +- .../Debugger/SymbolTree/SymbolTreeWidgets.cpp | 3 +- .../Settings/DebugAnalysisSettingsWidget.cpp | 426 ++++++++++++++++++ .../Settings/DebugAnalysisSettingsWidget.h | 56 +++ .../Settings/DebugAnalysisSettingsWidget.ui | 407 +++++++++++++++++ pcsx2-qt/Settings/DebugSettingsWidget.cpp | 23 + pcsx2-qt/Settings/DebugSettingsWidget.h | 2 + pcsx2-qt/Settings/DebugSettingsWidget.ui | 135 +++++- pcsx2-qt/pcsx2-qt.vcxproj | 12 + pcsx2-qt/pcsx2-qt.vcxproj.filters | 24 + pcsx2/Config.h | 72 ++- pcsx2/DebugTools/DebugInterface.cpp | 8 +- pcsx2/DebugTools/DebugInterface.h | 6 +- pcsx2/DebugTools/MIPSAnalyst.cpp | 2 +- pcsx2/DebugTools/SymbolGuardian.cpp | 4 +- pcsx2/DebugTools/SymbolImporter.cpp | 261 +++++++++-- pcsx2/DebugTools/SymbolImporter.h | 37 +- pcsx2/IopBios.cpp | 3 + pcsx2/Pcsx2Config.cpp | 115 ++++- pcsx2/VMManager.cpp | 11 +- 28 files changed, 1749 insertions(+), 134 deletions(-) create mode 100644 pcsx2-qt/Debugger/AnalysisOptionsDialog.cpp create mode 100644 pcsx2-qt/Debugger/AnalysisOptionsDialog.h create mode 100644 pcsx2-qt/Debugger/AnalysisOptionsDialog.ui create mode 100644 pcsx2-qt/Settings/DebugAnalysisSettingsWidget.cpp create mode 100644 pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h create mode 100644 pcsx2-qt/Settings/DebugAnalysisSettingsWidget.ui diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index 0df63b628e..2d8a1af252 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -83,6 +83,9 @@ target_sources(pcsx2-qt PRIVATE Settings/ControllerSettingsWindow.h Settings/ControllerSettingsWindow.ui Settings/ControllerSettingWidgetBinder.h + Settings/DebugAnalysisSettingsWidget.cpp + Settings/DebugAnalysisSettingsWidget.h + Settings/DebugAnalysisSettingsWidget.ui Settings/DebugSettingsWidget.cpp Settings/DebugSettingsWidget.h Settings/DebugSettingsWidget.ui @@ -152,6 +155,9 @@ target_sources(pcsx2-qt PRIVATE Settings/USBBindingWidget_GunCon2.ui Settings/USBBindingWidget_RyojouhenCon.ui Settings/USBBindingWidget_ShinkansenCon.ui + Debugger/AnalysisOptionsDialog.cpp + Debugger/AnalysisOptionsDialog.h + Debugger/AnalysisOptionsDialog.ui Debugger/CpuWidget.cpp Debugger/CpuWidget.h Debugger/CpuWidget.ui diff --git a/pcsx2-qt/Debugger/AnalysisOptionsDialog.cpp b/pcsx2-qt/Debugger/AnalysisOptionsDialog.cpp new file mode 100644 index 0000000000..7274e5b97a --- /dev/null +++ b/pcsx2-qt/Debugger/AnalysisOptionsDialog.cpp @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "AnalysisOptionsDialog.h" + +#include "Host.h" +#include "DebugTools/SymbolImporter.h" + +AnalysisOptionsDialog::AnalysisOptionsDialog(QWidget* parent) + : QDialog(parent) +{ + m_ui.setupUi(this); + + m_analysis_settings = new DebugAnalysisSettingsWidget(); + + m_ui.analysisSettings->setLayout(new QVBoxLayout()); + m_ui.analysisSettings->layout()->setContentsMargins(0, 0, 0, 0); + m_ui.analysisSettings->layout()->addWidget(m_analysis_settings); + + connect(m_ui.analyseButton, &QPushButton::clicked, this, &AnalysisOptionsDialog::analyse); + connect(m_ui.closeButton, &QPushButton::clicked, this, &QDialog::reject); +} + +void AnalysisOptionsDialog::analyse() +{ + Pcsx2Config::DebugAnalysisOptions options; + m_analysis_settings->parseSettingsFromWidgets(options); + + Host::RunOnCPUThread([options]() { + R5900SymbolImporter.LoadAndAnalyseElf(options); + }); + + if (m_ui.closeCheckBox->isChecked()) + accept(); +} diff --git a/pcsx2-qt/Debugger/AnalysisOptionsDialog.h b/pcsx2-qt/Debugger/AnalysisOptionsDialog.h new file mode 100644 index 0000000000..87d977fdb5 --- /dev/null +++ b/pcsx2-qt/Debugger/AnalysisOptionsDialog.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once + +#include "Settings/DebugAnalysisSettingsWidget.h" + +#include + +#include "ui_AnalysisOptionsDialog.h" + +class AnalysisOptionsDialog : public QDialog +{ + Q_OBJECT + +public: + AnalysisOptionsDialog(QWidget* parent = nullptr); + +protected: + void analyse(); + + DebugAnalysisSettingsWidget* m_analysis_settings; + + Ui::AnalysisOptionsDialog m_ui; +}; diff --git a/pcsx2-qt/Debugger/AnalysisOptionsDialog.ui b/pcsx2-qt/Debugger/AnalysisOptionsDialog.ui new file mode 100644 index 0000000000..b71820c3f4 --- /dev/null +++ b/pcsx2-qt/Debugger/AnalysisOptionsDialog.ui @@ -0,0 +1,113 @@ + + + AnalysisOptionsDialog + + + + 0 + 0 + 500 + 750 + + + + + 500 + 650 + + + + Analysis Options + + + + + + + 0 + 0 + + + + Changes made here won't be saved. Edit these settings from the global or per-game settings dialogs to have your changes take effect for future analysis runs. + + + true + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 488 + 636 + + + + + + + + + + + Close dialog after analysis has started + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Analyze + + + + + + + Close + + + + + + + + + + diff --git a/pcsx2-qt/Debugger/DebuggerWindow.cpp b/pcsx2-qt/Debugger/DebuggerWindow.cpp index ea2264e3ce..4fba8fe861 100644 --- a/pcsx2-qt/Debugger/DebuggerWindow.cpp +++ b/pcsx2-qt/Debugger/DebuggerWindow.cpp @@ -5,9 +5,11 @@ #include "DebugTools/DebugInterface.h" #include "DebugTools/Breakpoints.h" +#include "DebugTools/SymbolImporter.h" #include "VMManager.h" #include "QtHost.h" #include "MainWindow.h" +#include "AnalysisOptionsDialog.h" DebuggerWindow::DebuggerWindow(QWidget* parent) : QMainWindow(parent) @@ -28,6 +30,7 @@ DebuggerWindow::DebuggerWindow(QWidget* parent) connect(m_ui.actionStepInto, &QAction::triggered, this, &DebuggerWindow::onStepInto); connect(m_ui.actionStepOver, &QAction::triggered, this, &DebuggerWindow::onStepOver); connect(m_ui.actionStepOut, &QAction::triggered, this, &DebuggerWindow::onStepOut); + connect(m_ui.actionAnalyse, &QAction::triggered, this, &DebuggerWindow::onAnalyse); connect(m_ui.actionOnTop, &QAction::triggered, [this] { this->setWindowFlags(this->windowFlags() ^ Qt::WindowStaysOnTopHint); this->show(); }); connect(g_emu_thread, &EmuThread::onVMPaused, this, &DebuggerWindow::onVMStateChanged); @@ -38,7 +41,7 @@ DebuggerWindow::DebuggerWindow(QWidget* parent) // We can't do this in the designer, but we want to right align the actionOnTop action in the toolbar QWidget* spacer = new QWidget(this); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - m_ui.toolBar->insertWidget(m_ui.actionOnTop, spacer); + m_ui.toolBar->insertWidget(m_ui.actionAnalyse, spacer); m_cpuWidget_r5900 = new CpuWidget(this, r5900Debug); m_cpuWidget_r3000 = new CpuWidget(this, r3000Debug); @@ -127,3 +130,25 @@ void DebuggerWindow::onStepOut() CpuWidget* currentCpu = static_cast(m_ui.cpuTabs->currentWidget()); currentCpu->onStepOut(); } + +void DebuggerWindow::onAnalyse() +{ + AnalysisOptionsDialog* dialog = new AnalysisOptionsDialog(this); + dialog->show(); +} + +void DebuggerWindow::showEvent(QShowEvent* event) +{ + Host::RunOnCPUThread([]() { + R5900SymbolImporter.OnDebuggerOpened(); + }); + QMainWindow::showEvent(event); +} + +void DebuggerWindow::hideEvent(QHideEvent* event) +{ + Host::RunOnCPUThread([]() { + R5900SymbolImporter.OnDebuggerClosed(); + }); + QMainWindow::hideEvent(event); +} diff --git a/pcsx2-qt/Debugger/DebuggerWindow.h b/pcsx2-qt/Debugger/DebuggerWindow.h index 653f24b8ac..07e46e2ee1 100644 --- a/pcsx2-qt/Debugger/DebuggerWindow.h +++ b/pcsx2-qt/Debugger/DebuggerWindow.h @@ -21,6 +21,11 @@ public slots: void onStepInto(); void onStepOver(); void onStepOut(); + void onAnalyse(); + +protected: + void showEvent(QShowEvent* event); + void hideEvent(QHideEvent *event); private: Ui::DebuggerWindow m_ui; diff --git a/pcsx2-qt/Debugger/DebuggerWindow.ui b/pcsx2-qt/Debugger/DebuggerWindow.ui index 1ecd58c831..6527494f07 100644 --- a/pcsx2-qt/Debugger/DebuggerWindow.ui +++ b/pcsx2-qt/Debugger/DebuggerWindow.ui @@ -29,12 +29,6 @@ Qt::PreventContextMenu - - Qt::ToolButtonTextBesideIcon - - - false - false @@ -44,6 +38,12 @@ 16 + + Qt::ToolButtonTextBesideIcon + + + false + TopToolBarArea @@ -54,11 +54,13 @@ + - + + .. Run @@ -66,7 +68,8 @@ - + + .. Step Into @@ -77,7 +80,8 @@ - + + .. Step Over @@ -88,7 +92,8 @@ - + + .. Step Out @@ -98,12 +103,13 @@ - - - true + + + .. + Always On Top @@ -111,6 +117,15 @@ Show this window on top + + + + .. + + + Analyze + + diff --git a/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.cpp b/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.cpp index 924cf548cb..186393bcce 100644 --- a/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.cpp +++ b/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.cpp @@ -337,7 +337,7 @@ void NewFunctionDialog::createSymbol() QString error_message; m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { - ccc::Result source = database.get_symbol_source("User-defined"); + ccc::Result source = database.get_symbol_source("User-Defined"); if (!source.success()) { error_message = tr("Cannot create symbol source."); @@ -398,7 +398,7 @@ void NewGlobalVariableDialog::createSymbol() QString error_message; m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) { - ccc::Result source = database.get_symbol_source("User-defined"); + ccc::Result source = database.get_symbol_source("User-Defined"); if (!source.success()) { error_message = tr("Cannot create symbol source."); @@ -508,7 +508,7 @@ void NewLocalVariableDialog::createSymbol() return; } - ccc::Result source = database.get_symbol_source("User-defined"); + ccc::Result source = database.get_symbol_source("User-Defined"); if (!source.success()) { error_message = tr("Cannot create symbol source."); @@ -622,7 +622,7 @@ void NewParameterVariableDialog::createSymbol() return; } - ccc::Result source = database.get_symbol_source("User-defined"); + ccc::Result source = database.get_symbol_source("User-Defined"); if (!source.success()) { error_message = tr("Cannot create symbol source."); diff --git a/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.h b/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.h index aaa724f9ec..6cad860bde 100644 --- a/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.h +++ b/pcsx2-qt/Debugger/SymbolTree/NewSymbolDialogs.h @@ -50,14 +50,14 @@ protected: protected slots: virtual bool parseUserInput() = 0; - + protected: virtual void createSymbol() = 0; void setupRegisterField(); void setupSizeField(); void setupFunctionField(); - + void connectInputWidgets(); void updateErrorMessage(QString error_message); @@ -75,14 +75,14 @@ protected: 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; + u32 m_alignment; std::vector m_functions; }; @@ -96,7 +96,7 @@ public: protected: bool parseUserInput() override; void createSymbol() override; - + std::string m_name; u32 m_address = 0; u32 m_size = 0; @@ -114,7 +114,7 @@ public: protected: bool parseUserInput() override; void createSymbol() override; - + std::string m_name; u32 m_address; std::unique_ptr m_type; @@ -130,7 +130,7 @@ public: protected: bool parseUserInput() override; void createSymbol() override; - + std::string m_name; std::variant m_storage; u32 m_address = 0; @@ -148,7 +148,7 @@ public: protected: bool parseUserInput() override; void createSymbol() override; - + std::string m_name; std::variant m_storage; std::unique_ptr m_type; diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp index 9b8b5f8d1d..5cbd0067bc 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeWidgets.cpp @@ -61,7 +61,8 @@ void SymbolTreeWidget::reset() m_ui.treeView->setColumnHidden(SymbolTreeModel::SIZE, !m_show_size_column || !m_show_size_column->isChecked()); - m_cpu.GetSymbolImporter().UpdateFunctionHashes(m_cpu); + if (m_cpu.GetSymbolImporter()) + m_cpu.GetSymbolImporter()->UpdateFunctionHashes(m_cpu); SymbolFilters filters; std::unique_ptr root; diff --git a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.cpp b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.cpp new file mode 100644 index 0000000000..11ab07c3f6 --- /dev/null +++ b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.cpp @@ -0,0 +1,426 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#include "DebugAnalysisSettingsWidget.h" + +#include "SettingsWindow.h" +#include "SettingWidgetBinder.h" + +#include "DebugTools/SymbolImporter.h" + +#include + +DebugAnalysisSettingsWidget::DebugAnalysisSettingsWidget(QWidget* parent) + : QWidget(parent) +{ + m_ui.setupUi(this); + + m_ui.automaticallyClearSymbols->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "AutomaticallySelectSymbolsToClear", true)); + + setupSymbolSourceGrid(); + + m_ui.importFromElf->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "ImportSymbolsFromELF", true)); + m_ui.importSymFileFromDefaultLocation->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "ImportSymFileFromDefaultLocation", true)); + m_ui.demangleSymbols->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "DemangleSymbols", true)); + m_ui.demangleParameters->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "DemangleParameters", true)); + + setupSymbolFileList(); + + std::string function_scan_mode = Host::GetStringSettingValue("Debugger/Analysis", "FunctionScanMode"); + for (int i = 0;; i++) + { + if (Pcsx2Config::DebugAnalysisOptions::FunctionScanModeNames[i] == nullptr) + break; + + if (function_scan_mode == Pcsx2Config::DebugAnalysisOptions::FunctionScanModeNames[i]) + m_ui.functionScanMode->setCurrentIndex(i); + } + + m_ui.customAddressRange->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "CustomFunctionScanRange", false)); + m_ui.addressRangeStart->setText(QString::fromStdString(Host::GetStringSettingValue("Debugger/Analysis", "FunctionScanStartAddress", "0"))); + m_ui.addressRangeEnd->setText(QString::fromStdString(Host::GetStringSettingValue("Debugger/Analysis", "FunctionScanEndAddress", "0"))); + + m_ui.grayOutOverwrittenFunctions->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "GenerateFunctionHashes", true)); + + connect(m_ui.automaticallyClearSymbols, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates); + connect(m_ui.demangleSymbols, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates); + connect(m_ui.customAddressRange, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates); + + updateEnabledStates(); +} + +DebugAnalysisSettingsWidget::DebugAnalysisSettingsWidget(SettingsWindow* dialog, QWidget* parent) + : QWidget(parent) + , m_dialog(dialog) +{ + SettingsInterface* sif = dialog->getSettingsInterface(); + + m_ui.setupUi(this); + + // Make sure the user doesn't select symbol sources from both the global + // settings and the per-game settings, as these settings will conflict with + // each other. It only really makes sense to modify these settings on a + // per-game basis anyway. + if (dialog->isPerGameSettings()) + { + SettingWidgetBinder::BindWidgetToBoolSetting( + sif, m_ui.automaticallyClearSymbols, "Debugger/Analysis", "AutomaticallySelectSymbolsToClear", true); + + m_dialog->registerWidgetHelp(m_ui.automaticallyClearSymbols, tr("Automatically Select Symbols To Clear"), tr("Checked"), + tr("Automatically delete symbols that were generated by any previous analysis runs.")); + + setupSymbolSourceGrid(); + } + else + { + m_ui.clearExistingSymbolsGroup->hide(); + } + + SettingWidgetBinder::BindWidgetToBoolSetting( + sif, m_ui.importFromElf, "Debugger/Analysis", "ImportSymbolsFromELF", true); + SettingWidgetBinder::BindWidgetToBoolSetting( + sif, m_ui.importSymFileFromDefaultLocation, "Debugger/Analysis", "ImportSymFileFromDefaultLocation", true); + SettingWidgetBinder::BindWidgetToBoolSetting( + sif, m_ui.demangleSymbols, "Debugger/Analysis", "DemangleSymbols", true); + SettingWidgetBinder::BindWidgetToBoolSetting( + sif, m_ui.demangleParameters, "Debugger/Analysis", "DemangleParameters", true); + + m_dialog->registerWidgetHelp(m_ui.importFromElf, tr("Import From ELF"), tr("Checked"), + tr("Import symbol tables stored in the game's boot ELF.")); + m_dialog->registerWidgetHelp(m_ui.importSymFileFromDefaultLocation, tr("Import Default .sym File"), tr("Checked"), + tr("Import symbols from a .sym file with the same name as the loaded ISO file on disk if such a file exists.")); + m_dialog->registerWidgetHelp(m_ui.demangleSymbols, tr("Demangle Symbols"), tr("Checked"), + tr("Demangle C++ symbols during the import process so that the function and global variable names shown in the " + "debugger are more readable.")); + m_dialog->registerWidgetHelp(m_ui.demangleParameters, tr("Demangle Parameters"), tr("Checked"), + tr("Include parameter lists in demangled function names.")); + + // Same as above. It only makes sense to load extra symbol files on a + // per-game basis. + if (dialog->isPerGameSettings()) + { + setupSymbolFileList(); + } + else + { + m_ui.symbolFileLabel->hide(); + m_ui.symbolFileList->hide(); + m_ui.importSymbolFileButtons->hide(); + } + + SettingWidgetBinder::BindWidgetToEnumSetting( + sif, m_ui.functionScanMode, "Debugger/Analysis", "FunctionScanMode", + Pcsx2Config::DebugAnalysisOptions::FunctionScanModeNames, DebugFunctionScanMode::SCAN_ELF); + + m_dialog->registerWidgetHelp(m_ui.functionScanMode, tr("Scan Mode"), tr("Scan ELF"), + tr("Choose where the function scanner looks to find functions. This option can be useful if the application " + "loads additional code at runtime.")); + + // Same as above. It only makes sense to set a custom memory range on a + // per-game basis. + if (dialog->isPerGameSettings()) + { + SettingWidgetBinder::BindWidgetToBoolSetting( + sif, m_ui.customAddressRange, "Debugger/Analysis", "CustomFunctionScanRange", false); + connect(m_ui.addressRangeStart, &QLineEdit::textChanged, this, &DebugAnalysisSettingsWidget::functionScanRangeChanged); + connect(m_ui.addressRangeEnd, &QLineEdit::textChanged, this, &DebugAnalysisSettingsWidget::functionScanRangeChanged); + + m_dialog->registerWidgetHelp(m_ui.customAddressRange, tr("Custom Address Range"), tr("Unchecked"), + tr("Whether to look for functions from the address range specified (Checked), or from the ELF segment " + "containing the entry point (Unchecked).")); + } + else + { + m_ui.customAddressRange->hide(); + m_ui.customAddressRangeLineEdits->hide(); + } + + SettingWidgetBinder::BindWidgetToBoolSetting( + sif, m_ui.grayOutOverwrittenFunctions, "Debugger/Analysis", "GenerateFunctionHashes", true); + + m_dialog->registerWidgetHelp(m_ui.grayOutOverwrittenFunctions, tr("Gray Out Symbols For Overwritten Functions"), tr("Checked"), + tr("Generate hashes for all the detected functions, and gray out the symbols displayed in the debugger for " + "functions that no longer match.")); + + connect(m_ui.automaticallyClearSymbols, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates); + connect(m_ui.demangleSymbols, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates); + connect(m_ui.customAddressRange, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates); + + updateEnabledStates(); +} + +void DebugAnalysisSettingsWidget::parseSettingsFromWidgets(Pcsx2Config::DebugAnalysisOptions& output) +{ + output.AutomaticallySelectSymbolsToClear = m_ui.automaticallyClearSymbols->isChecked(); + + for (const auto& [name, temp] : m_symbol_sources) + { + DebugSymbolSource& source = output.SymbolSources.emplace_back(); + source.Name = name; + source.ClearDuringAnalysis = temp.check_box->isChecked(); + } + + output.ImportSymbolsFromELF = m_ui.importFromElf->isChecked(); + output.ImportSymFileFromDefaultLocation = m_ui.importSymFileFromDefaultLocation->isChecked(); + output.DemangleSymbols = m_ui.demangleSymbols->isChecked(); + output.DemangleParameters = m_ui.demangleParameters->isChecked(); + + for (int i = 0; i < m_ui.symbolFileList->count(); i++) + { + DebugExtraSymbolFile& file = output.ExtraSymbolFiles.emplace_back(); + file.Path = m_ui.symbolFileList->item(i)->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() +{ + QGridLayout* layout = new QGridLayout(m_ui.symbolSourceGrid); + + if (!m_dialog || m_dialog->getSerial() == QtHost::GetCurrentGameSerial().toStdString()) + { + // 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); + + 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); + + SymbolSourceTemp& source = m_symbol_sources[name]; + source.previous_value = value; + source.modified_by_user = true; + } + + // Add any more symbol sources for which the user hasn't made a + // selection. These are separate since we don't want to have to store + // configuration data for them. + R5900SymbolGuardian.Read([&](const ccc::SymbolDatabase& database) { + for (const ccc::SymbolSource& symbol_source : database.symbol_sources) + { + if (m_symbol_sources.find(symbol_source.name()) == m_symbol_sources.end() && symbol_source.name() != "Built-In") + { + SymbolSourceTemp& source = m_symbol_sources[symbol_source.name()]; + source.previous_value = SymbolImporter::ShouldClearSymbolsFromSourceByDefault(symbol_source.name()); + source.modified_by_user = false; + } + } + }); + + if (m_symbol_sources.empty()) + { + m_ui.symbolSourceErrorMessage->setText(tr("No symbol sources in database.")); + m_ui.symbolSourceScrollArea->hide(); + return; + } + + // Create the check boxes. + int i = 0; + for (auto& [name, temp] : m_symbol_sources) + { + temp.check_box = new QCheckBox(QString::fromStdString(name)); + temp.check_box->setChecked(temp.previous_value); + layout->addWidget(temp.check_box, i / 2, i % 2); + + connect(temp.check_box, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::symbolSourceCheckStateChanged); + + i++; + } + } + else + { + m_ui.symbolSourceErrorMessage->setText(tr("Start this game to modify the symbol sources list.")); + m_ui.symbolSourceScrollArea->hide(); + return; + } + + m_ui.symbolSourceErrorMessage->hide(); +} + +void DebugAnalysisSettingsWidget::symbolSourceCheckStateChanged() +{ + QComboBox* combo_box = qobject_cast(sender()); + if (!combo_box) + return; + + auto temp = m_symbol_sources.find(combo_box->currentText().toStdString()); + if (temp == m_symbol_sources.end()) + return; + + temp->second.modified_by_user = true; + + saveSymbolSources(); +} + +void DebugAnalysisSettingsWidget::saveSymbolSources() +{ + if (!m_dialog) + return; + + SettingsInterface* sif = m_dialog->getSettingsInterface(); + if (!sif) + return; + + // Clean up old configuration entries. + int old_count = sif->GetIntValue("Debugger/Analysis/SymbolSources", "Count"); + for (int i = 0; i < old_count; i++) + { + std::string section = "Debugger/Analysis/SymbolSources/" + std::to_string(i); + sif->RemoveSection(section.c_str()); + } + + sif->RemoveSection("Debugger/Analysis/SymbolSources"); + + int symbol_sources_to_save = 0; + for (auto& [name, temp] : m_symbol_sources) + if (temp.modified_by_user) + symbol_sources_to_save++; + + if (symbol_sources_to_save == 0) + return; + + // Make new configuration entries. + sif->SetIntValue("Debugger/Analysis/SymbolSources", "Count", symbol_sources_to_save); + + int i = 0; + for (auto& [name, temp] : m_symbol_sources) + { + if (!temp.modified_by_user) + continue; + + std::string section = "Debugger/Analysis/SymbolSources/" + std::to_string(i); + sif->SetBoolValue(section.c_str(), "ClearDuringAnalysis", temp.check_box->isChecked()); + + i++; + } +} + +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); + + 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)); + } + + connect(m_ui.addSymbolFile, &QPushButton::clicked, this, &DebugAnalysisSettingsWidget::addSymbolFile); + connect(m_ui.removeSymbolFile, &QPushButton::clicked, this, &DebugAnalysisSettingsWidget::removeSymbolFile); +} + +void DebugAnalysisSettingsWidget::addSymbolFile() +{ + QString path = QFileDialog::getOpenFileName(this, tr("Add Symbol File")); + if (path.isEmpty()) + return; + + m_ui.symbolFileList->addItem(path); + + saveSymbolFiles(); +} + +void DebugAnalysisSettingsWidget::removeSymbolFile() +{ + for (QListWidgetItem* item : m_ui.symbolFileList->selectedItems()) + delete item; + + saveSymbolFiles(); +} + +void DebugAnalysisSettingsWidget::saveSymbolFiles() +{ + if (!m_dialog) + return; + + SettingsInterface* sif = m_dialog->getSettingsInterface(); + if (!sif) + return; + + // Clean up old configuration entries. + int old_count = sif->GetIntValue("Debugger/Analysis/ExtraSymbolFiles", "Count"); + for (int i = 0; i < old_count; i++) + { + std::string section = "Debugger/Analysis/ExtraSymbolFiles/" + std::to_string(i); + sif->RemoveSection(section.c_str()); + } + + sif->RemoveSection("Debugger/Analysis/ExtraSymbolFiles"); + + if (m_ui.symbolFileList->count() == 0) + return; + + // Make new configuration entries. + sif->SetIntValue("Debugger/Analysis/ExtraSymbolFiles", "Count", m_ui.symbolFileList->count()); + + for (int i = 0; i < m_ui.symbolFileList->count(); 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()); + } + + QtHost::SaveGameSettings(sif, true); + g_emu_thread->reloadGameSettings(); +} + +void DebugAnalysisSettingsWidget::functionScanRangeChanged() +{ + if (!m_dialog) + return; + + SettingsInterface* sif = m_dialog->getSettingsInterface(); + if (!sif) + return; + + QString start_address = m_ui.addressRangeStart->text(); + QString end_address = m_ui.addressRangeEnd->text(); + + bool ok; + + if (start_address.toUInt(&ok, 16), ok) + sif->SetStringValue("Debugger/Analysis", "FunctionScanStartAddress", start_address.toStdString().c_str()); + + if (end_address.toUInt(&ok, 16), ok) + sif->SetStringValue("Debugger/Analysis", "FunctionScanEndAddress", end_address.toStdString().c_str()); +} + +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.customAddressRangeLineEdits->setEnabled(m_ui.customAddressRange->isChecked()); +} diff --git a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h new file mode 100644 index 0000000000..52d81c1f05 --- /dev/null +++ b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team +// SPDX-License-Identifier: GPL-3.0+ + +#pragma once +#include "ui_DebugAnalysisSettingsWidget.h" + +#include "Config.h" + +#include + +class SettingsWindow; + +class DebugAnalysisSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + // Create a widget that will discard any settings changed after it is + // closed, for use in the dialog opened by the "Reanalyze" button. + DebugAnalysisSettingsWidget(QWidget* parent = nullptr); + + // Create a widget that will write back any settings changed to the config + // system, for use in the settings dialog. + DebugAnalysisSettingsWidget(SettingsWindow* dialog, QWidget* parent = nullptr); + + // Read all the analysis settings from the widget tree and store them in the + // output object. This is used by the analysis options dialog to start an + // analysis run manually. + void parseSettingsFromWidgets(Pcsx2Config::DebugAnalysisOptions& output); + +protected: + void setupSymbolSourceGrid(); + void symbolSourceCheckStateChanged(); + void saveSymbolSources(); + + void setupSymbolFileList(); + void addSymbolFile(); + void removeSymbolFile(); + void saveSymbolFiles(); + + void functionScanRangeChanged(); + + void updateEnabledStates(); + + struct SymbolSourceTemp + { + QCheckBox* check_box = nullptr; + bool previous_value = false; + bool modified_by_user = false; + }; + + SettingsWindow* m_dialog = nullptr; + std::map m_symbol_sources; + + Ui::DebugAnalysisSettingsWidget m_ui; +}; diff --git a/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.ui b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.ui new file mode 100644 index 0000000000..9f6575cbba --- /dev/null +++ b/pcsx2-qt/Settings/DebugAnalysisSettingsWidget.ui @@ -0,0 +1,407 @@ + + + DebugAnalysisSettingsWidget + + + + 0 + 0 + 500 + 750 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Clear Existing Symbols + + + + + + Automatically Select Symbols To Clear + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 150 + + + + true + + + + + 0 + 0 + 480 + 83 + + + + + + + + + <html><head/><body><p><br/></p></body></html> + + + Qt::AlignCenter + + + + + + + + + + + 0 + 0 + + + + Import Symbols + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Import From ELF + + + + + + + Demangle Symbols + + + + + + + Demangle Parameters + + + + + + + Import Default .sym File + + + + + + + + + + + 0 + 0 + + + + Import from file (.elf, .sym, etc): + + + + + + + + 0 + 0 + + + + + 16777215 + 100 + + + + false + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Add + + + + + + + Remove + + + + + + + + + + + + + + 0 + 0 + + + + Scan For Functions + + + + + + + + Scan Mode: + + + + + + + + Scan ELF + + + + + Scan Memory + + + + + Skip + + + + + + + + + + Custom Address Range: + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 40 + 20 + + + + + + + + Start: + + + + + + + + + + End: + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 40 + 20 + + + + + + + + + + + + + + + 0 + 0 + + + + Hash Functions + + + + + + Gray Out Symbols For Overwritten Functions + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/pcsx2-qt/Settings/DebugSettingsWidget.cpp b/pcsx2-qt/Settings/DebugSettingsWidget.cpp index 881dd49211..ad811926d4 100644 --- a/pcsx2-qt/Settings/DebugSettingsWidget.cpp +++ b/pcsx2-qt/Settings/DebugSettingsWidget.cpp @@ -2,6 +2,8 @@ // SPDX-License-Identifier: GPL-3.0+ #include "DebugSettingsWidget.h" + +#include "DebugAnalysisSettingsWidget.h" #include "QtUtils.h" #include "SettingWidgetBinder.h" #include "SettingsWindow.h" @@ -18,6 +20,27 @@ DebugSettingsWidget::DebugSettingsWidget(SettingsWindow* dialog, QWidget* parent m_ui.setupUi(this); + ////////////////////////////////////////////////////////////////////////// + // Analysis Settings + ////////////////////////////////////////////////////////////////////////// + SettingWidgetBinder::BindWidgetToEnumSetting( + sif, m_ui.analysisCondition, "Debugger/Analysis", "RunCondition", + Pcsx2Config::DebugAnalysisOptions::RunConditionNames, DebugAnalysisCondition::IF_DEBUGGER_IS_OPEN); + SettingWidgetBinder::BindWidgetToBoolSetting( + sif, m_ui.generateSymbolsForIRXExportTables, "Debugger/Analysis", "GenerateSymbolsForIRXExports", true); + + dialog->registerWidgetHelp(m_ui.analysisCondition, tr("Analyze Program"), tr("If Debugger Is Open"), + tr("Choose when the analysis passes should be run: Always (to save time when opening the debugger), If " + "Debugger Is Open (to save memory if you never open the debugger), or Never.")); + dialog->registerWidgetHelp(m_ui.generateSymbolsForIRXExportTables, tr("Generate Symbols for IRX Export Tables"), tr("Checked"), + tr("Hook IRX module loading/unloading and generate symbols for exported functions on the fly.")); + + m_analysis_settings = new DebugAnalysisSettingsWidget(dialog); + + m_ui.analysisSettings->setLayout(new QVBoxLayout()); + m_ui.analysisSettings->layout()->setContentsMargins(0, 0, 0, 0); + m_ui.analysisSettings->layout()->addWidget(m_analysis_settings); + ////////////////////////////////////////////////////////////////////////// // GS Settings ////////////////////////////////////////////////////////////////////////// diff --git a/pcsx2-qt/Settings/DebugSettingsWidget.h b/pcsx2-qt/Settings/DebugSettingsWidget.h index da0dc2f0b0..114df0656f 100644 --- a/pcsx2-qt/Settings/DebugSettingsWidget.h +++ b/pcsx2-qt/Settings/DebugSettingsWidget.h @@ -8,6 +8,7 @@ #include "ui_DebugSettingsWidget.h" class SettingsWindow; +class DebugAnalysisSettingsWidget; class DebugSettingsWidget : public QWidget { @@ -22,6 +23,7 @@ private Q_SLOTS: private: SettingsWindow* m_dialog; + DebugAnalysisSettingsWidget* m_analysis_settings; Ui::DebugSettingsWidget m_ui; }; diff --git a/pcsx2-qt/Settings/DebugSettingsWidget.ui b/pcsx2-qt/Settings/DebugSettingsWidget.ui index d1a0bd72a8..a2d60f979e 100644 --- a/pcsx2-qt/Settings/DebugSettingsWidget.ui +++ b/pcsx2-qt/Settings/DebugSettingsWidget.ui @@ -6,7 +6,7 @@ 0 0 - 861 + 527 501 @@ -24,20 +24,136 @@ 0 - + 0 true + + + Analysis + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + + 0 + 0 + 523 + 464 + + + + + + + + 0 + 0 + + + + These settings control what and when analysis passes should be performed on the program running in the virtual machine so that the resultant information can be shown in the debugger. + + + true + + + + + + + + 0 + 0 + + + + Analysis + + + + + + + + Automatically Analyze Program: + + + + + + + + Always + + + + + If Debugger Is Open + + + + + Never + + + + + + + + + + Generate Symbols For IRX Exports + + + + + + + + + + + 0 + 0 + + + + + + + + + + GS - + Draw Dumping @@ -175,19 +291,6 @@ - - - - Qt::Vertical - - - - 40 - 60 - - - - diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index 89f4a78558..f77da95952 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -97,6 +97,7 @@ + @@ -106,6 +107,7 @@ + @@ -192,6 +194,7 @@ + @@ -206,6 +209,7 @@ + @@ -263,7 +267,9 @@ + + @@ -392,6 +398,9 @@ Document + + Document + Document @@ -421,6 +430,9 @@ Document + + Document + Document diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters index fa3f65bbc8..b583ecaaf3 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj.filters +++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters @@ -257,12 +257,21 @@ Tools\Input Recording + + Settings + + + moc + Settings moc + + Debugger + Debugger @@ -296,6 +305,9 @@ Debugger\Models + + moc + moc @@ -520,9 +532,15 @@ Settings + + Settings + Settings + + Debugger + Debugger @@ -665,9 +683,15 @@ Settings + + Settings + Settings + + Debugger + Debugger diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 9bf084c372..94d54409b6 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -35,13 +35,13 @@ enum class CDVD_SourceType : uint8_t; namespace Pad { -enum class ControllerType : u8; + enum class ControllerType : u8; } /// Generic setting information which can be reused in multiple components. struct SettingInfo { - using GetOptionsCallback = std::vector>(*)(); + using GetOptionsCallback = std::vector> (*)(); enum class Type { @@ -190,6 +190,35 @@ enum class SpeedHack MaxCount, }; +enum class DebugAnalysisCondition +{ + ALWAYS, + IF_DEBUGGER_IS_OPEN, + NEVER +}; + +struct DebugSymbolSource +{ + std::string Name; + bool ClearDuringAnalysis = false; + + friend auto operator<=>(const DebugSymbolSource& lhs, const DebugSymbolSource& rhs) = default; +}; + +struct DebugExtraSymbolFile +{ + std::string Path; + + friend auto operator<=>(const DebugExtraSymbolFile& lhs, const DebugExtraSymbolFile& rhs) = default; +}; + +enum class DebugFunctionScanMode +{ + SCAN_ELF, + SCAN_MEMORY, + SKIP +}; + enum class AspectRatioType : u8 { Stretch, @@ -647,7 +676,7 @@ struct Pcsx2Config OsdShowInputs : 1, OsdShowFrameTimes : 1, OsdShowVersion : 1, - OsdShowVideoCapture: 1, + OsdShowVideoCapture : 1, OsdShowInputRec : 1, OsdShowHardwareInfo : 1, HWSpinGPUForReadbacks : 1, @@ -1007,6 +1036,7 @@ struct Pcsx2Config u32 WindowHeight; u32 MemoryViewBytesPerRow; + DebugOptions(); void LoadSave(SettingsWrapper& wrap); @@ -1014,6 +1044,37 @@ struct Pcsx2Config bool operator!=(const DebugOptions& right) const; }; + // ------------------------------------------------------------------------ + struct DebugAnalysisOptions + { + + static const char* RunConditionNames[]; + static const char* FunctionScanModeNames[]; + + DebugAnalysisCondition RunCondition = DebugAnalysisCondition::IF_DEBUGGER_IS_OPEN; + bool GenerateSymbolsForIRXExports = true; + + bool AutomaticallySelectSymbolsToClear = true; + std::vector SymbolSources; + + bool ImportSymbolsFromELF = true; + bool ImportSymFileFromDefaultLocation = true; + bool DemangleSymbols = true; + bool DemangleParameters = true; + std::vector ExtraSymbolFiles; + + DebugFunctionScanMode FunctionScanMode = DebugFunctionScanMode::SCAN_ELF; + bool CustomFunctionScanRange = false; + std::string FunctionScanStartAddress = "0"; + std::string FunctionScanEndAddress = "0"; + + bool GenerateFunctionHashes = true; + + void LoadSave(SettingsWrapper& wrap); + + friend auto operator<=>(const DebugAnalysisOptions& lhs, const DebugAnalysisOptions& rhs) = default; + }; + // ------------------------------------------------------------------------ struct EmulationSpeedOptions { @@ -1149,10 +1210,10 @@ struct Pcsx2Config { SavestateOptions(); void LoadSave(SettingsWrapper& wrap); - + SavestateCompressionMethod CompressionType = SavestateCompressionMethod::Zstandard; SavestateCompressionLevel CompressionRatio = SavestateCompressionLevel::Medium; - + bool operator==(const SavestateOptions& right) const; bool operator!=(const SavestateOptions& right) const; }; @@ -1192,6 +1253,7 @@ struct Pcsx2Config GamefixOptions Gamefixes; ProfilerOptions Profiler; DebugOptions Debugger; + DebugAnalysisOptions DebuggerAnalysis; EmulationSpeedOptions EmulationSpeed; SavestateOptions Savestate; SPU2Options SPU2; diff --git a/pcsx2/DebugTools/DebugInterface.cpp b/pcsx2/DebugTools/DebugInterface.cpp index 222c54110f..37da993322 100644 --- a/pcsx2/DebugTools/DebugInterface.cpp +++ b/pcsx2/DebugTools/DebugInterface.cpp @@ -894,9 +894,9 @@ SymbolGuardian& R5900DebugInterface::GetSymbolGuardian() const return R5900SymbolGuardian; } -SymbolImporter& R5900DebugInterface::GetSymbolImporter() const +SymbolImporter* R5900DebugInterface::GetSymbolImporter() const { - return R5900SymbolImporter; + return &R5900SymbolImporter; } std::vector> R5900DebugInterface::GetThreadList() const @@ -1218,9 +1218,9 @@ SymbolGuardian& R3000DebugInterface::GetSymbolGuardian() const return R3000SymbolGuardian; } -SymbolImporter& R3000DebugInterface::GetSymbolImporter() const +SymbolImporter* R3000DebugInterface::GetSymbolImporter() const { - return R3000SymbolImporter; + return nullptr; } std::vector> R3000DebugInterface::GetThreadList() const diff --git a/pcsx2/DebugTools/DebugInterface.h b/pcsx2/DebugTools/DebugInterface.h index 13b200d41c..fceedb9fea 100644 --- a/pcsx2/DebugTools/DebugInterface.h +++ b/pcsx2/DebugTools/DebugInterface.h @@ -83,7 +83,7 @@ public: virtual u32 getCycles() = 0; virtual BreakPointCpu getCpuType() = 0; virtual SymbolGuardian& GetSymbolGuardian() const = 0; - virtual SymbolImporter& GetSymbolImporter() const = 0; + virtual SymbolImporter* GetSymbolImporter() const = 0; virtual std::vector> GetThreadList() const = 0; bool evaluateExpression(const char* expression, u64& dest); @@ -139,7 +139,7 @@ public: void setPc(u32 newPc) override; void setRegister(int cat, int num, u128 newValue) override; SymbolGuardian& GetSymbolGuardian() const override; - SymbolImporter& GetSymbolImporter() const override; + SymbolImporter* GetSymbolImporter() const override; std::vector> GetThreadList() const override; std::string disasm(u32 address, bool simplify) override; @@ -182,7 +182,7 @@ public: void setPc(u32 newPc) override; void setRegister(int cat, int num, u128 newValue) override; SymbolGuardian& GetSymbolGuardian() const override; - SymbolImporter& GetSymbolImporter() const override; + SymbolImporter* GetSymbolImporter() const override; std::vector> GetThreadList() const override; std::string disasm(u32 address, bool simplify) override; diff --git a/pcsx2/DebugTools/MIPSAnalyst.cpp b/pcsx2/DebugTools/MIPSAnalyst.cpp index 0bedf562d8..5ae4f13d8e 100644 --- a/pcsx2/DebugTools/MIPSAnalyst.cpp +++ b/pcsx2/DebugTools/MIPSAnalyst.cpp @@ -308,7 +308,7 @@ namespace MIPSAnalyst currentFunction.end = addr + 4; functions.push_back(currentFunction); - ccc::Result source = database.get_symbol_source("Analysis"); + ccc::Result source = database.get_symbol_source("Function Scanner"); if (!source.success()) { Console.Error("MIPSAnalyst: %s", source.error().message.c_str()); return; diff --git a/pcsx2/DebugTools/SymbolGuardian.cpp b/pcsx2/DebugTools/SymbolGuardian.cpp index 6c2ddb78dd..d3d159dc02 100644 --- a/pcsx2/DebugTools/SymbolGuardian.cpp +++ b/pcsx2/DebugTools/SymbolGuardian.cpp @@ -8,13 +8,13 @@ SymbolGuardian R3000SymbolGuardian; void SymbolGuardian::Read(ReadCallback callback) const noexcept { - std::shared_lock l(m_big_symbol_lock); + std::shared_lock lock(m_big_symbol_lock); callback(m_database); } void SymbolGuardian::ReadWrite(ReadWriteCallback callback) noexcept { - std::unique_lock l(m_big_symbol_lock); + std::unique_lock lock(m_big_symbol_lock); callback(m_database); } diff --git a/pcsx2/DebugTools/SymbolImporter.cpp b/pcsx2/DebugTools/SymbolImporter.cpp index f35f887e5f..21781d3269 100644 --- a/pcsx2/DebugTools/SymbolImporter.cpp +++ b/pcsx2/DebugTools/SymbolImporter.cpp @@ -12,6 +12,7 @@ #include "common/Console.h" #include "common/Error.h" #include "common/FileSystem.h" +#include "common/Path.h" #include "common/StringUtil.h" #include "common/Threading.h" @@ -22,7 +23,6 @@ #include -SymbolImporter R3000SymbolImporter(R3000SymbolGuardian); SymbolImporter R5900SymbolImporter(R5900SymbolGuardian); struct DefaultBuiltInType @@ -84,7 +84,40 @@ SymbolImporter::SymbolImporter(SymbolGuardian& guardian) void SymbolImporter::OnElfChanged(std::vector elf, const std::string& elf_file_name) { Reset(); - AnalyseElf(std::move(elf), elf_file_name); + + if (EmuConfig.DebuggerAnalysis.RunCondition == DebugAnalysisCondition::NEVER) + { + m_symbol_table_loaded_on_boot = false; + return; + } + + if (!m_debugger_open && EmuConfig.DebuggerAnalysis.RunCondition == DebugAnalysisCondition::IF_DEBUGGER_IS_OPEN) + { + m_symbol_table_loaded_on_boot = false; + return; + } + + AnalyseElf(std::move(elf), elf_file_name, EmuConfig.DebuggerAnalysis); + + m_symbol_table_loaded_on_boot = true; +} + +void SymbolImporter::OnDebuggerOpened() +{ + m_debugger_open = true; + + if (EmuConfig.DebuggerAnalysis.RunCondition == DebugAnalysisCondition::NEVER) + return; + + if (m_symbol_table_loaded_on_boot) + return; + + LoadAndAnalyseElf(EmuConfig.DebuggerAnalysis); +} + +void SymbolImporter::OnDebuggerClosed() +{ + m_debugger_open = false; } void SymbolImporter::Reset() @@ -94,7 +127,7 @@ void SymbolImporter::Reset() m_guardian.ReadWrite([&](ccc::SymbolDatabase& database) { database.clear(); - ccc::Result source = database.get_symbol_source("Built-in"); + ccc::Result source = database.get_symbol_source("Built-In"); if (!source.success()) return; @@ -116,7 +149,24 @@ void SymbolImporter::Reset() }); } -void SymbolImporter::AnalyseElf(std::vector elf, const std::string& elf_file_name) +void SymbolImporter::LoadAndAnalyseElf(Pcsx2Config::DebugAnalysisOptions options) +{ + const std::string& elf_path = VMManager::GetCurrentELF(); + + Error error; + ElfObject elfo; + if (elf_path.empty() || !cdvdLoadElf(&elfo, elf_path, false, &error)) + { + if (!elf_path.empty()) + Console.Error(fmt::format("Failed to read ELF for symbol import: {}: {}", elf_path, error.GetDescription())); + return; + } + + AnalyseElf(elfo.ReleaseData(), elf_path, options); +} + +void SymbolImporter::AnalyseElf( + std::vector elf, const std::string& elf_file_name, Pcsx2Config::DebugAnalysisOptions options) { // Search for a .sym file to load symbols from. std::string nocash_path; @@ -143,36 +193,33 @@ void SymbolImporter::AnalyseElf(std::vector elf, const std::string& elf_file ShutdownWorkerThread(); - m_import_thread = std::thread([this, nocash_path, worker_symbol_file = std::move(symbol_file)]() { + m_import_thread = std::thread([this, nocash_path, options, worker_symbol_file = std::move(symbol_file)]() { Threading::SetNameOfCurrentThread("Symbol Worker"); ccc::SymbolDatabase temp_database; - ImportSymbolTables(temp_database, worker_symbol_file, &m_interrupt_import_thread); + + ImportSymbols(temp_database, worker_symbol_file, nocash_path, options, &m_interrupt_import_thread); if (m_interrupt_import_thread) return; - ImportNocashSymbols(temp_database, nocash_path); + if (options.GenerateFunctionHashes) + ComputeOriginalFunctionHashes(temp_database, worker_symbol_file.elf()); if (m_interrupt_import_thread) return; - const ccc::ElfProgramHeader* entry_segment = worker_symbol_file.elf().entry_point_segment(); - if (entry_segment) - { - ElfMemoryReader reader(worker_symbol_file.elf()); - MIPSAnalyst::ScanForFunctions(temp_database, reader, entry_segment->vaddr, entry_segment->vaddr + entry_segment->filesz); - } - - if (m_interrupt_import_thread) - return; - - ComputeOriginalFunctionHashes(temp_database, worker_symbol_file.elf()); + ScanForFunctions(temp_database, worker_symbol_file, options); if (m_interrupt_import_thread) return; m_guardian.ReadWrite([&](ccc::SymbolDatabase& database) { + ClearExistingSymbols(database, options); + + if (m_interrupt_import_thread) + return; + database.merge_from(temp_database); }); }); @@ -180,55 +227,145 @@ void SymbolImporter::AnalyseElf(std::vector elf, const std::string& elf_file void SymbolImporter::ShutdownWorkerThread() { - m_interrupt_import_thread = true; if (m_import_thread.joinable()) + { + m_interrupt_import_thread = true; m_import_thread.join(); - m_interrupt_import_thread = false; + m_interrupt_import_thread = false; + } } - -ccc::ModuleHandle SymbolImporter::ImportSymbolTables( - ccc::SymbolDatabase& database, const ccc::SymbolFile& symbol_file, const std::atomic_bool* interrupt) +void SymbolImporter::ClearExistingSymbols(ccc::SymbolDatabase& database, const Pcsx2Config::DebugAnalysisOptions& options) { - ccc::Result>> symbol_tables = symbol_file.get_all_symbol_tables(); - if (!symbol_tables.success()) + std::vector sources_to_destroy; + for (const ccc::SymbolSource& source : database.symbol_sources) { - ccc::report_error(symbol_tables.error()); - return ccc::ModuleHandle(); + bool should_destroy = ShouldClearSymbolsFromSourceByDefault(source.name()); + + for (const DebugSymbolSource& source_config : options.SymbolSources) + if (source_config.Name == source.name()) + should_destroy = source_config.ClearDuringAnalysis; + + if (should_destroy) + sources_to_destroy.emplace_back(source.handle()); } + for (ccc::SymbolSourceHandle handle : sources_to_destroy) + database.destroy_symbols_from_source(handle, true); +} + +bool SymbolImporter::ShouldClearSymbolsFromSourceByDefault(const std::string& source_name) +{ + return source_name.find("Symbol Table") != std::string::npos || + source_name == "ELF Section Headers" || + source_name == "Function Scanner" || + source_name == "Nocash Symbols"; +} + +void SymbolImporter::ImportSymbols( + ccc::SymbolDatabase& database, + const ccc::ElfSymbolFile& elf, + const std::string& nocash_path, + const Pcsx2Config::DebugAnalysisOptions& options, + const std::atomic_bool* interrupt) +{ ccc::DemanglerFunctions demangler; - demangler.cplus_demangle = cplus_demangle; - demangler.cplus_demangle_opname = cplus_demangle_opname; + if (options.DemangleSymbols) + { + demangler.cplus_demangle = cplus_demangle; + demangler.cplus_demangle_opname = cplus_demangle_opname; + } u32 importer_flags = - ccc::DEMANGLE_PARAMETERS | - ccc::DEMANGLE_RETURN_TYPE | ccc::NO_MEMBER_FUNCTIONS | ccc::NO_OPTIMIZED_OUT_FUNCTIONS | ccc::UNIQUE_FUNCTIONS; - ccc::Result module_handle = ccc::import_symbol_tables( - database, symbol_file.name(), *symbol_tables, importer_flags, demangler, interrupt); - if (!module_handle.success()) + if (options.DemangleParameters) + importer_flags |= ccc::DEMANGLE_PARAMETERS; + + if (options.ImportSymbolsFromELF) { - ccc::report_error(module_handle.error()); - return ccc::ModuleHandle(); + ccc::Result>> symbol_tables = elf.get_all_symbol_tables(); + if (!symbol_tables.success()) + { + ccc::report_error(symbol_tables.error()); + } + else + { + ccc::Result module_handle = ccc::import_symbol_tables( + database, elf.name(), *symbol_tables, importer_flags, demangler, interrupt); + if (!module_handle.success()) + { + ccc::report_error(module_handle.error()); + } + } + } + + if (!nocash_path.empty() && options.ImportSymFileFromDefaultLocation) + { + if (!ImportNocashSymbols(database, nocash_path)) + Console.Error("Failed to read symbol file from default location '%s'.", nocash_path.c_str()); + } + + for (const DebugExtraSymbolFile& extra_symbol_file : options.ExtraSymbolFiles) + { + if (*interrupt) + return; + + if (StringUtil::EndsWithNoCase(extra_symbol_file.Path, ".sym")) + { + if (!ImportNocashSymbols(database, extra_symbol_file.Path)) + Console.Error("Failed to read extra symbol file '%s'.", extra_symbol_file.Path.c_str()); + continue; + } + + Error error; + std::optional> image = FileSystem::ReadBinaryFile(extra_symbol_file.Path.c_str()); + if (!image.has_value()) + { + Console.Error("Failed to read extra symbol file '%s'.", extra_symbol_file.Path.c_str()); + continue; + } + + std::string file_name(Path::GetFileName(extra_symbol_file.Path)); + + 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 = elf.get_all_symbol_tables(); + if (!symbol_tables.success()) + { + ccc::report_error(symbol_tables.error()); + continue; + } + + ccc::Result module_handle = ccc::import_symbol_tables( + database, elf.name(), *symbol_tables, importer_flags, demangler, interrupt); + if (!module_handle.success()) + { + ccc::report_error(module_handle.error()); + continue; + } } Console.WriteLn("Imported %d symbols.", database.symbol_count()); - return *module_handle; + return; } -bool SymbolImporter::ImportNocashSymbols(ccc::SymbolDatabase& database, const std::string& file_name) +bool SymbolImporter::ImportNocashSymbols(ccc::SymbolDatabase& database, const std::string& file_path) { - ccc::Result source = database.get_symbol_source("Nocash Symbols"); - if (!source.success()) + FILE* f = FileSystem::OpenCFile(file_path.c_str(), "r"); + if (!f) return false; - FILE* f = FileSystem::OpenCFile(file_name.c_str(), "r"); - if (!f) + ccc::Result source = database.get_symbol_source("Nocash Symbols"); + if (!source.success()) return false; while (!feof(f)) @@ -344,6 +481,46 @@ bool SymbolImporter::ImportNocashSymbols(ccc::SymbolDatabase& database, const st return true; } +void SymbolImporter::ScanForFunctions( + ccc::SymbolDatabase& database, const ccc::ElfSymbolFile& elf, const Pcsx2Config::DebugAnalysisOptions& options) +{ + u32 start_address = 0; + u32 end_address = 0; + if (options.CustomFunctionScanRange) + { + start_address = static_cast(std::stoull(options.FunctionScanStartAddress.c_str(), nullptr, 16)); + end_address = static_cast(std::stoull(options.FunctionScanEndAddress.c_str(), nullptr, 16)); + } + else + { + const ccc::ElfProgramHeader* entry_segment = elf.elf().entry_point_segment(); + if (!entry_segment) + return; + + start_address = entry_segment->vaddr; + end_address = entry_segment->vaddr + entry_segment->filesz; + } + + switch (options.FunctionScanMode) + { + case DebugFunctionScanMode::SCAN_ELF: + { + ElfMemoryReader reader(elf.elf()); + MIPSAnalyst::ScanForFunctions(database, reader, start_address, end_address); + break; + } + case DebugFunctionScanMode::SCAN_MEMORY: + { + MIPSAnalyst::ScanForFunctions(database, r5900Debug, start_address, end_address); + break; + } + case DebugFunctionScanMode::SKIP: + { + break; + } + } +} + void SymbolImporter::ComputeOriginalFunctionHashes(ccc::SymbolDatabase& database, const ccc::ElfFile& elf) { for (ccc::Function& function : database.functions) diff --git a/pcsx2/DebugTools/SymbolImporter.h b/pcsx2/DebugTools/SymbolImporter.h index 9ef761ffe0..cac10ff176 100644 --- a/pcsx2/DebugTools/SymbolImporter.h +++ b/pcsx2/DebugTools/SymbolImporter.h @@ -3,16 +3,11 @@ #pragma once +#include "Config.h" #include "SymbolGuardian.h" class DebugInterface; -struct SymbolImporterOptions -{ - std::vector symbols_to_destroy; - -}; - class SymbolImporter { public: @@ -22,23 +17,37 @@ public: // that are used to determine when symbol tables should be loaded, and // should be called from the CPU thread. void OnElfChanged(std::vector elf, const std::string& elf_file_name); - - void AutoAnalyse(); + void OnDebuggerOpened(); + void OnDebuggerClosed(); // Delete all stored symbols and create some default built-ins. Should be // called from the CPU thread. void Reset(); + // Load the current ELF file and call AnalyseElf on it. Should be called + // from the CPU thread. + void LoadAndAnalyseElf(Pcsx2Config::DebugAnalysisOptions options); + // Import symbols from the ELF file, nocash symbols, and scan for functions. // Should be called from the CPU thread. - void AnalyseElf(std::vector elf, const std::string& elf_file_name); + void AnalyseElf(std::vector elf, const std::string& elf_file_name, Pcsx2Config::DebugAnalysisOptions options); // Interrupt the import thread. Should be called from the CPU thread. void ShutdownWorkerThread(); - static ccc::ModuleHandle ImportSymbolTables( - ccc::SymbolDatabase& database, const ccc::SymbolFile& symbol_file, const std::atomic_bool* interrupt); - static bool ImportNocashSymbols(ccc::SymbolDatabase& database, const std::string& file_name); + static void ClearExistingSymbols(ccc::SymbolDatabase& database, const Pcsx2Config::DebugAnalysisOptions& options); + static bool ShouldClearSymbolsFromSourceByDefault(const std::string& source_name); + + static void ImportSymbols( + ccc::SymbolDatabase& database, + const ccc::ElfSymbolFile& elf, + const std::string& nocash_path, + const Pcsx2Config::DebugAnalysisOptions& options, + const std::atomic_bool* interrupt); + static bool ImportNocashSymbols(ccc::SymbolDatabase& database, const std::string& file_path); + + static void ScanForFunctions( + ccc::SymbolDatabase& database, const ccc::ElfSymbolFile& elf, const Pcsx2Config::DebugAnalysisOptions& options); // Compute original hashes for all the functions based on the code stored in // the ELF file. @@ -51,9 +60,11 @@ public: protected: SymbolGuardian& m_guardian; + bool m_symbol_table_loaded_on_boot = false; + bool m_debugger_open = false; + std::thread m_import_thread; std::atomic_bool m_interrupt_import_thread = false; }; -extern SymbolImporter R3000SymbolImporter; extern SymbolImporter R5900SymbolImporter; diff --git a/pcsx2/IopBios.cpp b/pcsx2/IopBios.cpp index 5c4e770ad4..62ff199bc5 100644 --- a/pcsx2/IopBios.cpp +++ b/pcsx2/IopBios.cpp @@ -1086,6 +1086,9 @@ namespace R3000A void LoadFuncs(u32 a0reg) { + if (!EmuConfig.DebuggerAnalysis.GenerateSymbolsForIRXExports) + return; + const std::string modname = iopMemReadString(a0reg + 12, 8); s32 version_major = iopMemRead8(a0reg + 9); s32 version_minor = iopMemRead8(a0reg + 8); diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 9c5deebc60..3800d5f147 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -324,7 +324,7 @@ void Pcsx2Config::SpeedhackOptions::LoadSave(SettingsWrapper& wrap) EECycleSkip = std::min(EECycleSkip, MAX_EE_CYCLE_SKIP); } - Pcsx2Config::ProfilerOptions::ProfilerOptions() +Pcsx2Config::ProfilerOptions::ProfilerOptions() : bitset(0xfffffffe) { } @@ -587,7 +587,7 @@ const char* Pcsx2Config::GSOptions::GetRendererName(GSRendererType type) { switch (type) { - // clang-format off + // clang-format off case GSRendererType::Auto: return "Auto"; case GSRendererType::DX11: return "Direct3D 11"; case GSRendererType::DX12: return "Direct3D 12"; @@ -1025,7 +1025,7 @@ bool Pcsx2Config::GSOptions::UseHardwareRenderer() const static constexpr const std::array s_spu2_sync_mode_names = { "Disabled", - "TimeStretch" + "TimeStretch", }; static constexpr const std::array s_spu2_sync_mode_display_names = { TRANSLATE_NOOP("Pcsx2Config", "Disabled (Noisy)"), @@ -1111,7 +1111,7 @@ void Pcsx2Config::SPU2Options::LoadSave(SettingsWrapper& wrap) SettingsWrapEntry(DeviceName); StreamParameters.LoadSave(wrap, CURRENT_SETTINGS_SECTION); } - } +} bool Pcsx2Config::SPU2Options::operator!=(const SPU2Options& right) const { @@ -1338,7 +1338,7 @@ void Pcsx2Config::GamefixOptions::Set(GamefixId id, bool enabled) { switch (id) { - // clang-format off + // clang-format off case Fix_VuAddSub: VuAddSubHack = enabled; break; case Fix_FpuMultiply: FpuMulHack = enabled; break; case Fix_XGKick: XgKickHack = enabled; break; @@ -1376,7 +1376,7 @@ bool Pcsx2Config::GamefixOptions::Get(GamefixId id) const { switch (id) { - // clang-format off + // clang-format off case Fix_VuAddSub: return VuAddSubHack; case Fix_FpuMultiply: return FpuMulHack; case Fix_XGKick: return XgKickHack; @@ -1425,7 +1425,6 @@ void Pcsx2Config::GamefixOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapBitBool(FullVU0SyncHack); } - Pcsx2Config::DebugOptions::DebugOptions() { ShowDebuggerOnStart = false; @@ -1457,7 +1456,92 @@ bool Pcsx2Config::DebugOptions::operator!=(const DebugOptions& right) const bool Pcsx2Config::DebugOptions::operator==(const DebugOptions& right) const { - return OpEqu(bitset) && OpEqu(FontWidth) && OpEqu(FontHeight) && OpEqu(WindowWidth) && OpEqu(WindowHeight) && OpEqu(MemoryViewBytesPerRow); + return OpEqu(bitset) && + OpEqu(FontWidth) && + OpEqu(FontHeight) && + OpEqu(WindowWidth) && + OpEqu(WindowHeight) && + OpEqu(MemoryViewBytesPerRow); +} + +const char* Pcsx2Config::DebugAnalysisOptions::RunConditionNames[] = { + "Always", + "If Debugger Is Open", + "Never", + nullptr, +}; + +const char* Pcsx2Config::DebugAnalysisOptions::FunctionScanModeNames[] = { + "Scan From ELF", + "Scan From Memory", + "Skip", + nullptr, +}; + +void Pcsx2Config::DebugAnalysisOptions::LoadSave(SettingsWrapper& wrap) +{ + { + SettingsWrapSection("Debugger/Analysis"); + + SettingsWrapEnumEx(RunCondition, "RunCondition", RunConditionNames); + SettingsWrapBitBool(GenerateSymbolsForIRXExports); + + SettingsWrapBitBool(AutomaticallySelectSymbolsToClear); + + SettingsWrapBitBool(ImportSymbolsFromELF); + SettingsWrapBitBool(DemangleSymbols); + SettingsWrapBitBool(DemangleParameters); + + SettingsWrapEnumEx(FunctionScanMode, "FunctionScanMode", FunctionScanModeNames); + SettingsWrapBitBool(CustomFunctionScanRange); + SettingsWrapEntry(FunctionScanStartAddress); + SettingsWrapEntry(FunctionScanEndAddress); + + SettingsWrapBitBool(GenerateFunctionHashes); + } + + int symbolSourceCount = static_cast(SymbolSources.size()); + { + SettingsWrapSection("Debugger/Analysis/SymbolSources"); + SettingsWrapEntryEx(symbolSourceCount, "Count"); + } + + for (int i = 0; i < symbolSourceCount; i++) + { + std::string section = "Debugger/Analysis/SymbolSources/" + std::to_string(i); + SettingsWrapSection(section.c_str()); + + DebugSymbolSource Source; + if (wrap.IsSaving()) + Source = SymbolSources[i]; + + SettingsWrapEntryEx(Source.Name, "Name"); + SettingsWrapBitBoolEx(Source.ClearDuringAnalysis, "ClearDuringAnalysis"); + + if (wrap.IsLoading()) + SymbolSources.emplace_back(std::move(Source)); + } + + int extraSymbolFileCount = static_cast(ExtraSymbolFiles.size()); + { + SettingsWrapSection("Debugger/Analysis/ExtraSymbolFiles"); + SettingsWrapEntryEx(extraSymbolFileCount, "Count"); + } + + for (int i = 0; i < extraSymbolFileCount; i++) + { + std::string section = "Debugger/Analysis/ExtraSymbolFiles/" + std::to_string(i); + SettingsWrapSection(section.c_str()); + + DebugExtraSymbolFile file; + if (wrap.IsSaving()) + file = ExtraSymbolFiles[i]; + + SettingsWrapEntryEx(file.Path, "Path"); + + if (wrap.IsLoading()) + ExtraSymbolFiles.emplace_back(std::move(file)); + } } Pcsx2Config::SavestateOptions::SavestateOptions() @@ -1778,6 +1862,7 @@ void Pcsx2Config::LoadSaveCore(SettingsWrapper& wrap) Savestate.LoadSave(wrap); Debugger.LoadSave(wrap); + DebuggerAnalysis.LoadSave(wrap); Trace.LoadSave(wrap); Achievements.LoadSave(wrap); @@ -1905,13 +1990,13 @@ void EmuFolders::SetAppRoot() bool EmuFolders::SetResourcesDirectory() { -#ifndef __APPLE__ - #ifndef PCSX2_APP_DATADIR - // On Windows/Linux, these are in the binary directory. - Resources = Path::Combine(AppRoot, "resources"); - #else - Resources = Path::Canonicalize(Path::Combine(AppRoot, PCSX2_APP_DATADIR "/resources")); - #endif +#ifndef __APPLE__ +#ifndef PCSX2_APP_DATADIR + // On Windows/Linux, these are in the binary directory. + Resources = Path::Combine(AppRoot, "resources"); +#else + Resources = Path::Canonicalize(Path::Combine(AppRoot, PCSX2_APP_DATADIR "/resources")); +#endif #else // On macOS, this is in the bundle resources directory. const std::string program_path = FileSystem::GetProgramPath(); diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index a0cd190fae..c2a08c1189 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -447,7 +447,6 @@ void VMManager::Internal::CPUThreadShutdown() // Ensure emulog gets flushed. Log::SetFileOutputLevel(LOGLEVEL_NONE, std::string()); - R3000SymbolImporter.ShutdownWorkerThread(); R5900SymbolImporter.ShutdownWorkerThread(); } @@ -1413,9 +1412,9 @@ bool VMManager::Initialize(VMBootParameters boot_params) Achievements::ConfirmHardcoreModeDisableAsync(trigger, [boot_params = std::move(boot_params)](bool approved) mutable { - if (approved && Initialize(std::move(boot_params))) - SetState(VMState::Running); - }); + if (approved && Initialize(std::move(boot_params))) + SetState(VMState::Running); + }); return false; } @@ -3098,7 +3097,7 @@ void VMManager::WarnAboutUnsafeSettings() append(ICON_FA_TACHOMETER_ALT, TRANSLATE_SV("VMManager", "Cycle rate/skip is not at default, this may crash or make games run too slow.")); } - + const bool is_sw_renderer = EmuConfig.GS.Renderer == GSRendererType::SW; if (!is_sw_renderer) { @@ -3615,7 +3614,7 @@ void VMManager::UpdateDiscordPresence(bool update_session_time) rp.largeImageKey = "4k-pcsx2"; rp.largeImageText = "PCSX2 PS2 Emulator"; rp.startTimestamp = s_discord_presence_time_epoch; - rp.details = s_title.empty() ? TRANSLATE("VMManager","No Game Running") : s_title.c_str(); + rp.details = s_title.empty() ? TRANSLATE("VMManager", "No Game Running") : s_title.c_str(); std::string state_string;