Debugger: Add settings to control the analysis passes

This commit is contained in:
chaoticgd 2024-10-02 16:22:12 +01:00 committed by Ty
parent 0fd7e1d7c2
commit 2c3abe33d5
28 changed files with 1749 additions and 134 deletions

View File

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

View File

@ -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();
}

View File

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "Settings/DebugAnalysisSettingsWidget.h"
#include <QtWidgets/QDialog>
#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;
};

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AnalysisOptionsDialog</class>
<widget class="QDialog" name="AnalysisOptionsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>750</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>650</height>
</size>
</property>
<property name="windowTitle">
<string>Analysis Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>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.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="analysisSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>488</width>
<height>636</height>
</rect>
</property>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="buttons">
<item>
<widget class="QCheckBox" name="closeCheckBox">
<property name="text">
<string>Close dialog after analysis has started</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="analyseButton">
<property name="text">
<string>Analyze</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="closeButton">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -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<CpuWidget*>(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);
}

View File

@ -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;

View File

@ -29,12 +29,6 @@
<property name="contextMenuPolicy">
<enum>Qt::PreventContextMenu</enum>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="floatable">
<bool>false</bool>
</property>
<property name="movable">
<bool>false</bool>
</property>
@ -44,6 +38,12 @@
<height>16</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="floatable">
<bool>false</bool>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
@ -54,11 +54,13 @@
<addaction name="actionStepInto"/>
<addaction name="actionStepOver"/>
<addaction name="actionStepOut"/>
<addaction name="actionAnalyse"/>
<addaction name="actionOnTop"/>
</widget>
<action name="actionRun">
<property name="icon">
<iconset theme="play-line"/>
<iconset theme="play-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Run</string>
@ -66,7 +68,8 @@
</action>
<action name="actionStepInto">
<property name="icon">
<iconset theme="debug-step-into-line"/>
<iconset theme="debug-step-into-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Step Into</string>
@ -77,7 +80,8 @@
</action>
<action name="actionStepOver">
<property name="icon">
<iconset theme="debug-step-over-line"/>
<iconset theme="debug-step-over-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Step Over</string>
@ -88,7 +92,8 @@
</action>
<action name="actionStepOut">
<property name="icon">
<iconset theme="debug-step-out-line"/>
<iconset theme="debug-step-out-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Step Out</string>
@ -98,12 +103,13 @@
</property>
</action>
<action name="actionOnTop">
<property name="icon">
<iconset theme="pin-filled"/>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset theme="pin-filled">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Always On Top</string>
</property>
@ -111,6 +117,15 @@
<string>Show this window on top</string>
</property>
</action>
<action name="actionAnalyse">
<property name="icon">
<iconset theme="restart-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Analyze</string>
</property>
</action>
</widget>
<resources/>
<connections/>

View File

@ -337,7 +337,7 @@ void NewFunctionDialog::createSymbol()
QString error_message;
m_cpu.GetSymbolGuardian().ReadWrite([&](ccc::SymbolDatabase& database) {
ccc::Result<ccc::SymbolSourceHandle> source = database.get_symbol_source("User-defined");
ccc::Result<ccc::SymbolSourceHandle> 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<ccc::SymbolSourceHandle> source = database.get_symbol_source("User-defined");
ccc::Result<ccc::SymbolSourceHandle> 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<ccc::SymbolSourceHandle> source = database.get_symbol_source("User-defined");
ccc::Result<ccc::SymbolSourceHandle> 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<ccc::SymbolSourceHandle> source = database.get_symbol_source("User-defined");
ccc::Result<ccc::SymbolSourceHandle> source = database.get_symbol_source("User-Defined");
if (!source.success())
{
error_message = tr("Cannot create symbol source.");

View File

@ -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<ccc::FunctionHandle> 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<ccc::ast::Node> m_type;
@ -130,7 +130,7 @@ public:
protected:
bool parseUserInput() override;
void createSymbol() override;
std::string m_name;
std::variant<ccc::GlobalStorage, ccc::RegisterStorage, ccc::StackStorage> m_storage;
u32 m_address = 0;
@ -148,7 +148,7 @@ public:
protected:
bool parseUserInput() override;
void createSymbol() override;
std::string m_name;
std::variant<ccc::RegisterStorage, ccc::StackStorage> m_storage;
std::unique_ptr<ccc::ast::Node> m_type;

View File

@ -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<SymbolTreeNode> root;

View File

@ -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 <QtWidgets/QFileDialog>
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<DebugFunctionScanMode>(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("<i>No symbol sources in database.</i>"));
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("<i>Start this game to modify the symbol sources list.</i>"));
m_ui.symbolSourceScrollArea->hide();
return;
}
m_ui.symbolSourceErrorMessage->hide();
}
void DebugAnalysisSettingsWidget::symbolSourceCheckStateChanged()
{
QComboBox* combo_box = qobject_cast<QComboBox*>(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());
}

View File

@ -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 <QtWidgets/QDialog>
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<std::string, SymbolSourceTemp> m_symbol_sources;
Ui::DebugAnalysisSettingsWidget m_ui;
};

View File

@ -0,0 +1,407 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DebugAnalysisSettingsWidget</class>
<widget class="QWidget" name="DebugAnalysisSettingsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>750</height>
</rect>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="clearExistingSymbolsGroup">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Clear Existing Symbols</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QCheckBox" name="automaticallyClearSymbols">
<property name="text">
<string>Automatically Select Symbols To Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="symbolSourceScrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>150</height>
</size>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="symbolSourceGrid">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>83</height>
</rect>
</property>
</widget>
</widget>
</item>
<item>
<widget class="QLabel" name="symbolSourceErrorMessage">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="importSymbolTablesGroup">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Import Symbols</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QWidget" name="importSymbolTableCheckBoxes" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="importSymbolTableCheckBoxesLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QCheckBox" name="importFromElf">
<property name="text">
<string>Import From ELF</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="demangleSymbols">
<property name="text">
<string>Demangle Symbols</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="demangleParameters">
<property name="text">
<string>Demangle Parameters</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="importSymFileFromDefaultLocation">
<property name="text">
<string>Import Default .sym File</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="symbolFileLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Import from file (.elf, .sym, etc):</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="symbolFileList">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
<property name="sortingEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="importSymbolFileButtons" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="importSymbolFileButtonsLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="importSymbolFileSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="addSymbolFile">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeSymbolFile">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="scanForFunctionsGroup">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Scan For Functions</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QFormLayout" name="functionScanForm">
<item row="0" column="0">
<widget class="QLabel" name="functionScanLabel">
<property name="text">
<string>Scan Mode:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="functionScanMode">
<item>
<property name="text">
<string>Scan ELF</string>
</property>
</item>
<item>
<property name="text">
<string>Scan Memory</string>
</property>
</item>
<item>
<property name="text">
<string>Skip</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="customAddressRange">
<property name="text">
<string>Custom Address Range:</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="customAddressRangeLineEdits" native="true">
<layout class="QHBoxLayout" name="memoryRangeLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="startSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Maximum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="addressRangeStartLabel">
<property name="text">
<string>Start:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="addressRangeStart"/>
</item>
<item>
<widget class="QLabel" name="addressRangeEndLabel">
<property name="text">
<string>End:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="addressRangeEnd"/>
</item>
<item>
<spacer name="endSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Maximum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Hash Functions</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QCheckBox" name="grayOutOverwrittenFunctions">
<property name="text">
<string>Gray Out Symbols For Overwritten Functions</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -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
//////////////////////////////////////////////////////////////////////////

View File

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

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>861</width>
<width>527</width>
<height>501</height>
</rect>
</property>
@ -24,20 +24,136 @@
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="gsTab">
<widget class="QTabWidget" name="tabs">
<property name="currentIndex">
<number>0</number>
</property>
<property name="documentMode">
<bool>true</bool>
</property>
<widget class="QWidget" name="analysisTabWidget">
<attribute name="title">
<string>Analysis</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="analysisScrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="analysisScrollAreaContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>523</width>
<height>464</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="analysisLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>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.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="analysisGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Analysis</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QFormLayout" name="analysisForm">
<item row="0" column="0">
<widget class="QLabel" name="analysisConditionLabel">
<property name="text">
<string>Automatically Analyze Program:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="analysisCondition">
<item>
<property name="text">
<string>Always</string>
</property>
</item>
<item>
<property name="text">
<string>If Debugger Is Open</string>
</property>
</item>
<item>
<property name="text">
<string>Never</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="generateSymbolsForIRXExportTables">
<property name="text">
<string>Generate Symbols For IRX Exports</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="analysisSettings" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="gsTabWidget">
<attribute name="title">
<string>GS</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox">
<widget class="QGroupBox" name="drawDumpingGroupBox">
<property name="title">
<string>Draw Dumping</string>
</property>
@ -175,19 +291,6 @@
</widget>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>60</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>

View File

@ -97,6 +97,7 @@
<ClCompile Include="EarlyHardwareCheck.cpp" />
<ClCompile Include="LogWindow.cpp" />
<ClCompile Include="QtProgressCallback.cpp" />
<ClCompile Include="Settings\DebugAnalysisSettingsWidget.cpp" />
<ClCompile Include="Settings\DebugSettingsWidget.cpp" />
<ClCompile Include="Settings\FolderSettingsWidget.cpp" />
<ClCompile Include="Settings\GameCheatSettingsWidget.cpp" />
@ -106,6 +107,7 @@
<ClCompile Include="Themes.cpp" />
<ClCompile Include="Tools\InputRecording\InputRecordingViewer.cpp" />
<ClCompile Include="Tools\InputRecording\NewInputRecordingDlg.cpp" />
<ClCompile Include="Debugger\AnalysisOptionsDialog.cpp" />
<ClCompile Include="Debugger\CpuWidget.cpp" />
<ClCompile Include="Debugger\DebuggerWindow.cpp" />
<ClCompile Include="Debugger\DisassemblyWidget.cpp" />
@ -192,6 +194,7 @@
<QtMoc Include="Debugger\SymbolTree\TypeString.h" />
<ClInclude Include="Settings\ControllerSettingWidgetBinder.h" />
<QtMoc Include="Settings\FolderSettingsWidget.h" />
<QtMoc Include="Settings\DebugAnalysisSettingsWidget.h" />
<QtMoc Include="Settings\DebugSettingsWidget.h" />
<QtMoc Include="Settings\GameCheatSettingsWidget.h" />
<QtMoc Include="Settings\GamePatchSettingsWidget.h" />
@ -206,6 +209,7 @@
<QtMoc Include="Tools\InputRecording\NewInputRecordingDlg.h" />
<QtMoc Include="Tools\InputRecording\InputRecordingViewer.h" />
<ClInclude Include="QtUtils.h" />
<QtMoc Include="Debugger\AnalysisOptionsDialog.h" />
<QtMoc Include="Debugger\CpuWidget.h" />
<QtMoc Include="Debugger\DebuggerWindow.h" />
<QtMoc Include="Debugger\DisassemblyWidget.h" />
@ -263,7 +267,9 @@
<ClCompile Include="$(IntDir)Settings\moc_GameSummaryWidget.cpp" />
<ClCompile Include="$(IntDir)Settings\moc_AchievementLoginDialog.cpp" />
<ClCompile Include="$(IntDir)Settings\moc_AchievementSettingsWidget.cpp" />
<ClCompile Include="$(IntDir)Settings\moc_DebugAnalysisSettingsWidget.cpp" />
<ClCompile Include="$(IntDir)Settings\moc_DebugSettingsWidget.cpp" />
<ClCompile Include="$(IntDir)Debugger\moc_AnalysisOptionsDialog.cpp" />
<ClCompile Include="$(IntDir)Debugger\moc_DebuggerWindow.cpp" />
<ClCompile Include="$(IntDir)Debugger\moc_CpuWidget.cpp" />
<ClCompile Include="$(IntDir)Debugger\moc_DisassemblyWidget.cpp" />
@ -392,6 +398,9 @@
<QtUi Include="Tools\InputRecording\InputRecordingViewer.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="Debugger\AnalysisOptionsDialog.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="Debugger\DebuggerWindow.ui">
<FileType>Document</FileType>
</QtUi>
@ -421,6 +430,9 @@
<QtTs Include="Translations\pcsx2-qt_en.ts">
<FileType>Document</FileType>
</QtTs>
<QtUi Include="Settings\DebugAnalysisSettingsWidget.ui">
<FileType>Document</FileType>
</QtUi>
<QtUi Include="Settings\DebugSettingsWidget.ui">
<FileType>Document</FileType>
</QtUi>

View File

@ -257,12 +257,21 @@
<ClCompile Include="Tools\InputRecording\InputRecordingViewer.cpp">
<Filter>Tools\Input Recording</Filter>
</ClCompile>
<ClCompile Include="Settings\DebugAnalysisSettingsWidget.cpp">
<Filter>Settings</Filter>
</ClCompile>
<ClCompile Include="$(IntDir)Settings\moc_DebugAnalysisSettingsWidget.cpp">
<Filter>moc</Filter>
</ClCompile>
<ClCompile Include="Settings\DebugSettingsWidget.cpp">
<Filter>Settings</Filter>
</ClCompile>
<ClCompile Include="$(IntDir)Settings\moc_DebugSettingsWidget.cpp">
<Filter>moc</Filter>
</ClCompile>
<ClCompile Include="Debugger\AnalysisOptionsDialog.cpp">
<Filter>Debugger</Filter>
</ClCompile>
<ClCompile Include="Debugger\DebuggerWindow.cpp">
<Filter>Debugger</Filter>
</ClCompile>
@ -296,6 +305,9 @@
<ClCompile Include="Debugger\Models\SavedAddressesModel.cpp">
<Filter>Debugger\Models</Filter>
</ClCompile>
<ClCompile Include="$(IntDir)Debugger\moc_AnalysisOptionsDialog.cpp">
<Filter>moc</Filter>
</ClCompile>
<ClCompile Include="$(IntDir)Debugger\moc_CpuWidget.cpp">
<Filter>moc</Filter>
</ClCompile>
@ -520,9 +532,15 @@
<Filter>Settings</Filter>
</QtMoc>
<QtMoc Include="Tools\InputRecording\InputRecordingViewer.h" />
<QtMoc Include="Settings\DebugAnalysisSettingsWidget.h">
<Filter>Settings</Filter>
</QtMoc>
<QtMoc Include="Settings\DebugSettingsWidget.h">
<Filter>Settings</Filter>
</QtMoc>
<QtMoc Include="Debugger\AnalysisOptionsDialog.h">
<Filter>Debugger</Filter>
</QtMoc>
<QtMoc Include="Debugger\DebuggerWindow.h">
<Filter>Debugger</Filter>
</QtMoc>
@ -665,9 +683,15 @@
<QtUi Include="Settings\USBDeviceWidget.ui">
<Filter>Settings</Filter>
</QtUi>
<QtUi Include="Settings\DebugAnalysisSettingsWidget.ui">
<Filter>Settings</Filter>
</QtUi>
<QtUi Include="Settings\DebugSettingsWidget.ui">
<Filter>Settings</Filter>
</QtUi>
<QtUi Include="Debugger\AnalysisOptionsDialog.ui">
<Filter>Debugger</Filter>
</QtUi>
<QtUi Include="Debugger\DebuggerWindow.ui">
<Filter>Debugger</Filter>
</QtUi>

View File

@ -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<std::pair<std::string, std::string>>(*)();
using GetOptionsCallback = std::vector<std::pair<std::string, std::string>> (*)();
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<DebugSymbolSource> SymbolSources;
bool ImportSymbolsFromELF = true;
bool ImportSymFileFromDefaultLocation = true;
bool DemangleSymbols = true;
bool DemangleParameters = true;
std::vector<DebugExtraSymbolFile> 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;

View File

@ -894,9 +894,9 @@ SymbolGuardian& R5900DebugInterface::GetSymbolGuardian() const
return R5900SymbolGuardian;
}
SymbolImporter& R5900DebugInterface::GetSymbolImporter() const
SymbolImporter* R5900DebugInterface::GetSymbolImporter() const
{
return R5900SymbolImporter;
return &R5900SymbolImporter;
}
std::vector<std::unique_ptr<BiosThread>> 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<std::unique_ptr<BiosThread>> R3000DebugInterface::GetThreadList() const

View File

@ -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<std::unique_ptr<BiosThread>> 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<std::unique_ptr<BiosThread>> 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<std::unique_ptr<BiosThread>> GetThreadList() const override;
std::string disasm(u32 address, bool simplify) override;

View File

@ -308,7 +308,7 @@ namespace MIPSAnalyst
currentFunction.end = addr + 4;
functions.push_back(currentFunction);
ccc::Result<ccc::SymbolSourceHandle> source = database.get_symbol_source("Analysis");
ccc::Result<ccc::SymbolSourceHandle> source = database.get_symbol_source("Function Scanner");
if (!source.success()) {
Console.Error("MIPSAnalyst: %s", source.error().message.c_str());
return;

View File

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

View File

@ -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 <demangle.h>
SymbolImporter R3000SymbolImporter(R3000SymbolGuardian);
SymbolImporter R5900SymbolImporter(R5900SymbolGuardian);
struct DefaultBuiltInType
@ -84,7 +84,40 @@ SymbolImporter::SymbolImporter(SymbolGuardian& guardian)
void SymbolImporter::OnElfChanged(std::vector<u8> 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<ccc::SymbolSourceHandle> source = database.get_symbol_source("Built-in");
ccc::Result<ccc::SymbolSourceHandle> source = database.get_symbol_source("Built-In");
if (!source.success())
return;
@ -116,7 +149,24 @@ void SymbolImporter::Reset()
});
}
void SymbolImporter::AnalyseElf(std::vector<u8> 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<u8> 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<u8> 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<u8> 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<std::vector<std::unique_ptr<ccc::SymbolTable>>> symbol_tables = symbol_file.get_all_symbol_tables();
if (!symbol_tables.success())
std::vector<ccc::SymbolSourceHandle> 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<ccc::ModuleHandle> 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<std::vector<std::unique_ptr<ccc::SymbolTable>>> symbol_tables = elf.get_all_symbol_tables();
if (!symbol_tables.success())
{
ccc::report_error(symbol_tables.error());
}
else
{
ccc::Result<ccc::ModuleHandle> 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<std::vector<u8>> 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<std::unique_ptr<ccc::SymbolFile>> 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<std::vector<std::unique_ptr<ccc::SymbolTable>>> symbol_tables = elf.get_all_symbol_tables();
if (!symbol_tables.success())
{
ccc::report_error(symbol_tables.error());
continue;
}
ccc::Result<ccc::ModuleHandle> 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<ccc::SymbolSourceHandle> 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<ccc::SymbolSourceHandle> 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<u32>(std::stoull(options.FunctionScanStartAddress.c_str(), nullptr, 16));
end_address = static_cast<u32>(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)

View File

@ -3,16 +3,11 @@
#pragma once
#include "Config.h"
#include "SymbolGuardian.h"
class DebugInterface;
struct SymbolImporterOptions
{
std::vector<ccc::SymbolSourceHandle> 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<u8> 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<u8> elf, const std::string& elf_file_name);
void AnalyseElf(std::vector<u8> 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;

View File

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

View File

@ -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<int>(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<int>(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();

View File

@ -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;