Merge pull request #8682 from lat9nq/dumpy
yuzu qt: Add option to create Windows crash dumps
This commit is contained in:
commit
44ccec7846
24 changed files with 407 additions and 95 deletions
|
@ -21,6 +21,7 @@ cmake .. \
|
||||||
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
|
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
|
||||||
-DENABLE_QT_TRANSLATION=ON \
|
-DENABLE_QT_TRANSLATION=ON \
|
||||||
-DUSE_CCACHE=ON \
|
-DUSE_CCACHE=ON \
|
||||||
|
-DYUZU_CRASH_DUMPS=ON \
|
||||||
-DYUZU_USE_BUNDLED_SDL2=OFF \
|
-DYUZU_USE_BUNDLED_SDL2=OFF \
|
||||||
-DYUZU_USE_EXTERNAL_SDL2=OFF \
|
-DYUZU_USE_EXTERNAL_SDL2=OFF \
|
||||||
-DYUZU_TESTS=OFF \
|
-DYUZU_TESTS=OFF \
|
||||||
|
|
|
@ -65,8 +65,8 @@ if ("$env:GITHUB_ACTIONS" -eq "true") {
|
||||||
# None of the other GHA builds are including source, so commenting out today
|
# None of the other GHA builds are including source, so commenting out today
|
||||||
#Copy-Item $MSVC_SOURCE_TARXZ -Destination "artifacts"
|
#Copy-Item $MSVC_SOURCE_TARXZ -Destination "artifacts"
|
||||||
|
|
||||||
# Are debug symbols important?
|
# Debugging symbols
|
||||||
# cp .\build\bin\yuzu*.pdb .\pdb\
|
cp .\build\bin\yuzu*.pdb .\artifacts\
|
||||||
|
|
||||||
# Write out a tag BUILD_TAG to environment for the Upload step
|
# Write out a tag BUILD_TAG to environment for the Upload step
|
||||||
# We're getting ${{ github.event.number }} as $env:PR_NUMBER"
|
# We're getting ${{ github.event.number }} as $env:PR_NUMBER"
|
||||||
|
|
|
@ -9,7 +9,7 @@ parameters:
|
||||||
steps:
|
steps:
|
||||||
- script: choco install vulkan-sdk
|
- script: choco install vulkan-sdk
|
||||||
displayName: 'Install vulkan-sdk'
|
displayName: 'Install vulkan-sdk'
|
||||||
- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release .. && cd ..
|
- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_CRASH_DUMPS=ON .. && cd ..
|
||||||
displayName: 'Configure CMake'
|
displayName: 'Configure CMake'
|
||||||
- task: MSBuild@1
|
- task: MSBuild@1
|
||||||
displayName: 'Build'
|
displayName: 'Build'
|
||||||
|
|
2
.github/workflows/verify.yml
vendored
2
.github/workflows/verify.yml
vendored
|
@ -104,7 +104,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
glslangValidator --version
|
glslangValidator --version
|
||||||
mkdir build
|
mkdir build
|
||||||
cmake . -B build -GNinja -DCMAKE_TOOLCHAIN_FILE="CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DGIT_BRANCH=pr-verify
|
cmake . -B build -GNinja -DCMAKE_TOOLCHAIN_FILE="CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DGIT_BRANCH=pr-verify -DYUZU_CRASH_DUMPS=ON
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build
|
run: cmake --build build
|
||||||
- name: Cache Summary
|
- name: Cache Summary
|
||||||
|
|
|
@ -38,6 +38,8 @@ option(YUZU_USE_BUNDLED_OPUS "Compile bundled opus" ON)
|
||||||
|
|
||||||
option(YUZU_TESTS "Compile tests" ON)
|
option(YUZU_TESTS "Compile tests" ON)
|
||||||
|
|
||||||
|
CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF)
|
||||||
|
|
||||||
option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}")
|
option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}")
|
||||||
|
|
||||||
option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON)
|
option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON)
|
||||||
|
@ -46,6 +48,9 @@ if (YUZU_USE_BUNDLED_VCPKG)
|
||||||
if (YUZU_TESTS)
|
if (YUZU_TESTS)
|
||||||
list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests")
|
list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests")
|
||||||
endif()
|
endif()
|
||||||
|
if (YUZU_CRASH_DUMPS)
|
||||||
|
list(APPEND VCPKG_MANIFEST_FEATURES "dbghelp")
|
||||||
|
endif()
|
||||||
|
|
||||||
include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
|
include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
|
||||||
elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
|
elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
|
||||||
|
@ -447,6 +452,13 @@ elseif (WIN32)
|
||||||
# PSAPI is the Process Status API
|
# PSAPI is the Process Status API
|
||||||
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
|
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (YUZU_CRASH_DUMPS)
|
||||||
|
find_library(DBGHELP_LIBRARY dbghelp)
|
||||||
|
if ("${DBGHELP_LIBRARY}" STREQUAL "DBGHELP_LIBRARY-NOTFOUND")
|
||||||
|
message(FATAL_ERROR "YUZU_CRASH_DUMPS enabled but dbghelp library not found")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
|
elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
|
||||||
set(PLATFORM_LIBRARIES rt)
|
set(PLATFORM_LIBRARIES rt)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -530,6 +530,7 @@ struct Values {
|
||||||
Setting<bool> use_debug_asserts{false, "use_debug_asserts"};
|
Setting<bool> use_debug_asserts{false, "use_debug_asserts"};
|
||||||
Setting<bool> use_auto_stub{false, "use_auto_stub"};
|
Setting<bool> use_auto_stub{false, "use_auto_stub"};
|
||||||
Setting<bool> enable_all_controllers{false, "enable_all_controllers"};
|
Setting<bool> enable_all_controllers{false, "enable_all_controllers"};
|
||||||
|
Setting<bool> create_crash_dumps{false, "create_crash_dumps"};
|
||||||
|
|
||||||
// Miscellaneous
|
// Miscellaneous
|
||||||
Setting<std::string> log_filter{"*:Info", "log_filter"};
|
Setting<std::string> log_filter{"*:Info", "log_filter"};
|
||||||
|
|
|
@ -208,6 +208,16 @@ add_executable(yuzu
|
||||||
yuzu.rc
|
yuzu.rc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (WIN32 AND YUZU_CRASH_DUMPS)
|
||||||
|
target_sources(yuzu PRIVATE
|
||||||
|
mini_dump.cpp
|
||||||
|
mini_dump.h
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY})
|
||||||
|
target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP)
|
||||||
|
endif()
|
||||||
|
|
||||||
file(GLOB COMPAT_LIST
|
file(GLOB COMPAT_LIST
|
||||||
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
|
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
|
||||||
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
|
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
|
||||||
|
|
|
@ -63,7 +63,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
|
||||||
InputCommon::InputSubsystem* input_subsystem_, Core::System& system_)
|
InputCommon::InputSubsystem* input_subsystem_, Core::System& system_)
|
||||||
: QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
|
: QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
|
||||||
parameters(std::move(parameters_)), input_subsystem{input_subsystem_},
|
parameters(std::move(parameters_)), input_subsystem{input_subsystem_},
|
||||||
input_profiles(std::make_unique<InputProfiles>(system_)), system{system_} {
|
input_profiles(std::make_unique<InputProfiles>()), system{system_} {
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
player_widgets = {
|
player_widgets = {
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
|
|
||||||
namespace FS = Common::FS;
|
namespace FS = Common::FS;
|
||||||
|
|
||||||
Config::Config(Core::System& system_, const std::string& config_name, ConfigType config_type)
|
Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) {
|
||||||
: type(config_type), system{system_} {
|
|
||||||
global = config_type == ConfigType::GlobalConfig;
|
global = config_type == ConfigType::GlobalConfig;
|
||||||
|
|
||||||
Initialize(config_name);
|
Initialize(config_name);
|
||||||
|
@ -546,6 +545,7 @@ void Config::ReadDebuggingValues() {
|
||||||
ReadBasicSetting(Settings::values.use_debug_asserts);
|
ReadBasicSetting(Settings::values.use_debug_asserts);
|
||||||
ReadBasicSetting(Settings::values.use_auto_stub);
|
ReadBasicSetting(Settings::values.use_auto_stub);
|
||||||
ReadBasicSetting(Settings::values.enable_all_controllers);
|
ReadBasicSetting(Settings::values.enable_all_controllers);
|
||||||
|
ReadBasicSetting(Settings::values.create_crash_dumps);
|
||||||
|
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
}
|
}
|
||||||
|
@ -1161,6 +1161,7 @@ void Config::SaveDebuggingValues() {
|
||||||
WriteBasicSetting(Settings::values.use_debug_asserts);
|
WriteBasicSetting(Settings::values.use_debug_asserts);
|
||||||
WriteBasicSetting(Settings::values.disable_macro_jit);
|
WriteBasicSetting(Settings::values.disable_macro_jit);
|
||||||
WriteBasicSetting(Settings::values.enable_all_controllers);
|
WriteBasicSetting(Settings::values.enable_all_controllers);
|
||||||
|
WriteBasicSetting(Settings::values.create_crash_dumps);
|
||||||
|
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
}
|
}
|
||||||
|
@ -1547,7 +1548,6 @@ void Config::Reload() {
|
||||||
ReadValues();
|
ReadValues();
|
||||||
// To apply default value changes
|
// To apply default value changes
|
||||||
SaveValues();
|
SaveValues();
|
||||||
system.ApplySettings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::Save() {
|
void Config::Save() {
|
||||||
|
|
|
@ -25,7 +25,7 @@ public:
|
||||||
InputProfile,
|
InputProfile,
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit Config(Core::System& system_, const std::string& config_name = "qt-config",
|
explicit Config(const std::string& config_name = "qt-config",
|
||||||
ConfigType config_type = ConfigType::GlobalConfig);
|
ConfigType config_type = ConfigType::GlobalConfig);
|
||||||
~Config();
|
~Config();
|
||||||
|
|
||||||
|
@ -194,8 +194,6 @@ private:
|
||||||
std::unique_ptr<QSettings> qt_config;
|
std::unique_ptr<QSettings> qt_config;
|
||||||
std::string qt_config_loc;
|
std::string qt_config_loc;
|
||||||
bool global;
|
bool global;
|
||||||
|
|
||||||
Core::System& system;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// These metatype declarations cannot be in common/settings.h because core is devoid of QT
|
// These metatype declarations cannot be in common/settings.h because core is devoid of QT
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include "common/fs/path_util.h"
|
#include "common/fs/path_util.h"
|
||||||
#include "common/logging/backend.h"
|
#include "common/logging/backend.h"
|
||||||
|
@ -26,6 +27,16 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent)
|
||||||
|
|
||||||
connect(ui->toggle_gdbstub, &QCheckBox::toggled,
|
connect(ui->toggle_gdbstub, &QCheckBox::toggled,
|
||||||
[&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); });
|
[&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); });
|
||||||
|
|
||||||
|
connect(ui->create_crash_dumps, &QCheckBox::stateChanged, [&](int) {
|
||||||
|
if (crash_dump_warning_shown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QMessageBox::warning(this, tr("Restart Required"),
|
||||||
|
tr("yuzu is required to restart in order to apply this setting."),
|
||||||
|
QMessageBox::Ok, QMessageBox::Ok);
|
||||||
|
crash_dump_warning_shown = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigureDebug::~ConfigureDebug() = default;
|
ConfigureDebug::~ConfigureDebug() = default;
|
||||||
|
@ -71,7 +82,14 @@ void ConfigureDebug::SetConfiguration() {
|
||||||
ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue());
|
ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue());
|
||||||
#else
|
#else
|
||||||
ui->disable_web_applet->setEnabled(false);
|
ui->disable_web_applet->setEnabled(false);
|
||||||
ui->disable_web_applet->setText(QString::fromUtf8("Web applet not compiled"));
|
ui->disable_web_applet->setText(tr("Web applet not compiled"));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef YUZU_DBGHELP
|
||||||
|
ui->create_crash_dumps->setChecked(Settings::values.create_crash_dumps.GetValue());
|
||||||
|
#else
|
||||||
|
ui->create_crash_dumps->setEnabled(false);
|
||||||
|
ui->create_crash_dumps->setText(tr("MiniDump creation not compiled"));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +102,7 @@ void ConfigureDebug::ApplyConfiguration() {
|
||||||
Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked();
|
Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked();
|
||||||
Settings::values.reporting_services = ui->reporting_services->isChecked();
|
Settings::values.reporting_services = ui->reporting_services->isChecked();
|
||||||
Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked();
|
Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked();
|
||||||
|
Settings::values.create_crash_dumps = ui->create_crash_dumps->isChecked();
|
||||||
Settings::values.quest_flag = ui->quest_flag->isChecked();
|
Settings::values.quest_flag = ui->quest_flag->isChecked();
|
||||||
Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked();
|
Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked();
|
||||||
Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
|
Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
|
||||||
|
|
|
@ -32,4 +32,6 @@ private:
|
||||||
std::unique_ptr<Ui::ConfigureDebug> ui;
|
std::unique_ptr<Ui::ConfigureDebug> ui;
|
||||||
|
|
||||||
const Core::System& system;
|
const Core::System& system;
|
||||||
|
|
||||||
|
bool crash_dump_warning_shown{false};
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,60 +7,60 @@
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget">
|
<widget class="QWidget">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_1">
|
<layout class="QVBoxLayout" name="verticalLayout_1">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Debugger</string>
|
<string>Debugger</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
<widget class="QCheckBox" name="toggle_gdbstub">
|
||||||
<item>
|
<property name="text">
|
||||||
<widget class="QCheckBox" name="toggle_gdbstub">
|
<string>Enable GDB Stub</string>
|
||||||
<property name="text">
|
</property>
|
||||||
<string>Enable GDB Stub</string>
|
</widget>
|
||||||
</property>
|
</item>
|
||||||
</widget>
|
<item>
|
||||||
</item>
|
<spacer name="horizontalSpacer">
|
||||||
<item>
|
<property name="orientation">
|
||||||
<spacer name="horizontalSpacer">
|
<enum>Qt::Horizontal</enum>
|
||||||
<property name="orientation">
|
</property>
|
||||||
<enum>Qt::Horizontal</enum>
|
<property name="sizeHint" stdset="0">
|
||||||
</property>
|
<size>
|
||||||
<property name="sizeHint" stdset="0">
|
<width>40</width>
|
||||||
<size>
|
<height>20</height>
|
||||||
<width>40</width>
|
</size>
|
||||||
<height>20</height>
|
</property>
|
||||||
</size>
|
</spacer>
|
||||||
</property>
|
</item>
|
||||||
</spacer>
|
<item>
|
||||||
</item>
|
<widget class="QLabel" name="label_11">
|
||||||
<item>
|
<property name="text">
|
||||||
<widget class="QLabel" name="label_11">
|
<string>Port:</string>
|
||||||
<property name="text">
|
</property>
|
||||||
<string>Port:</string>
|
</widget>
|
||||||
</property>
|
</item>
|
||||||
</widget>
|
<item>
|
||||||
</item>
|
<widget class="QSpinBox" name="gdbport_spinbox">
|
||||||
<item>
|
<property name="minimum">
|
||||||
<widget class="QSpinBox" name="gdbport_spinbox">
|
<number>1024</number>
|
||||||
<property name="minimum">
|
</property>
|
||||||
<number>1024</number>
|
<property name="maximum">
|
||||||
</property>
|
<number>65535</number>
|
||||||
<property name="maximum">
|
</property>
|
||||||
<number>65535</number>
|
</widget>
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</item>
|
||||||
</item>
|
</layout>
|
||||||
</layout>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox_2">
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
|
@ -231,6 +231,13 @@
|
||||||
<string>Debugging</string>
|
<string>Debugging</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_3">
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QCheckBox" name="reporting_services">
|
||||||
|
<property name="text">
|
||||||
|
<string>Enable Verbose Reporting Services**</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QCheckBox" name="fs_access_log">
|
<widget class="QCheckBox" name="fs_access_log">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -238,20 +245,20 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="0" column="1">
|
||||||
<widget class="QCheckBox" name="dump_audio_commands">
|
<widget class="QCheckBox" name="dump_audio_commands">
|
||||||
<property name="text">
|
|
||||||
<string>Dump Audio Commands To Console**</string>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string>
|
<string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Dump Audio Commands To Console**</string>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="2" column="1">
|
||||||
<widget class="QCheckBox" name="reporting_services">
|
<widget class="QCheckBox" name="create_crash_dumps">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Enable Verbose Reporting Services**</string>
|
<string>Create Minidump After Crash</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -340,7 +347,6 @@
|
||||||
<tabstop>disable_loop_safety_checks</tabstop>
|
<tabstop>disable_loop_safety_checks</tabstop>
|
||||||
<tabstop>fs_access_log</tabstop>
|
<tabstop>fs_access_log</tabstop>
|
||||||
<tabstop>reporting_services</tabstop>
|
<tabstop>reporting_services</tabstop>
|
||||||
<tabstop>dump_audio_commands</tabstop>
|
|
||||||
<tabstop>quest_flag</tabstop>
|
<tabstop>quest_flag</tabstop>
|
||||||
<tabstop>enable_cpu_debugging</tabstop>
|
<tabstop>enable_cpu_debugging</tabstop>
|
||||||
<tabstop>use_debug_asserts</tabstop>
|
<tabstop>use_debug_asserts</tabstop>
|
||||||
|
|
|
@ -65,7 +65,7 @@ void OnDockedModeChanged(bool last_state, bool new_state, Core::System& system)
|
||||||
|
|
||||||
ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent)
|
ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent)
|
||||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
|
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
|
||||||
profiles(std::make_unique<InputProfiles>(system_)), system{system_} {
|
profiles(std::make_unique<InputProfiles>()), system{system_} {
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
|
||||||
const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name));
|
const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name));
|
||||||
const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename())
|
const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename())
|
||||||
: fmt::format("{:016X}", title_id);
|
: fmt::format("{:016X}", title_id);
|
||||||
game_config =
|
game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig);
|
||||||
std::make_unique<Config>(system, config_file_name, Config::ConfigType::PerGameConfig);
|
|
||||||
|
|
||||||
addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this);
|
addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this);
|
||||||
audio_tab = std::make_unique<ConfigureAudio>(system_, this);
|
audio_tab = std::make_unique<ConfigureAudio>(system_, this);
|
||||||
|
|
|
@ -27,7 +27,7 @@ std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
InputProfiles::InputProfiles(Core::System& system_) : system{system_} {
|
InputProfiles::InputProfiles() {
|
||||||
const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input";
|
const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input";
|
||||||
|
|
||||||
if (!FS::IsDir(input_profile_loc)) {
|
if (!FS::IsDir(input_profile_loc)) {
|
||||||
|
@ -43,8 +43,8 @@ InputProfiles::InputProfiles(Core::System& system_) : system{system_} {
|
||||||
|
|
||||||
if (IsINI(filename) && IsProfileNameValid(name_without_ext)) {
|
if (IsINI(filename) && IsProfileNameValid(name_without_ext)) {
|
||||||
map_profiles.insert_or_assign(
|
map_profiles.insert_or_assign(
|
||||||
name_without_ext, std::make_unique<Config>(system, name_without_ext,
|
name_without_ext,
|
||||||
Config::ConfigType::InputProfile));
|
std::make_unique<Config>(name_without_ext, Config::ConfigType::InputProfile));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -80,8 +80,7 @@ bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t p
|
||||||
}
|
}
|
||||||
|
|
||||||
map_profiles.insert_or_assign(
|
map_profiles.insert_or_assign(
|
||||||
profile_name,
|
profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile));
|
||||||
std::make_unique<Config>(system, profile_name, Config::ConfigType::InputProfile));
|
|
||||||
|
|
||||||
return SaveProfile(profile_name, player_index);
|
return SaveProfile(profile_name, player_index);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ class Config;
|
||||||
class InputProfiles {
|
class InputProfiles {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit InputProfiles(Core::System& system_);
|
explicit InputProfiles();
|
||||||
virtual ~InputProfiles();
|
virtual ~InputProfiles();
|
||||||
|
|
||||||
std::vector<std::string> GetInputProfileNames();
|
std::vector<std::string> GetInputProfileNames();
|
||||||
|
@ -31,6 +31,4 @@ private:
|
||||||
bool ProfileExistsInMap(const std::string& profile_name) const;
|
bool ProfileExistsInMap(const std::string& profile_name) const;
|
||||||
|
|
||||||
std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles;
|
std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles;
|
||||||
|
|
||||||
Core::System& system;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -138,6 +138,10 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||||
#include "yuzu/uisettings.h"
|
#include "yuzu/uisettings.h"
|
||||||
#include "yuzu/util/clickable_label.h"
|
#include "yuzu/util/clickable_label.h"
|
||||||
|
|
||||||
|
#ifdef YUZU_DBGHELP
|
||||||
|
#include "yuzu/mini_dump.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace Common::Literals;
|
using namespace Common::Literals;
|
||||||
|
|
||||||
#ifdef USE_DISCORD_PRESENCE
|
#ifdef USE_DISCORD_PRESENCE
|
||||||
|
@ -269,10 +273,9 @@ bool GMainWindow::CheckDarkMode() {
|
||||||
#endif // __linux__
|
#endif // __linux__
|
||||||
}
|
}
|
||||||
|
|
||||||
GMainWindow::GMainWindow(bool has_broken_vulkan)
|
GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan)
|
||||||
: ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()},
|
: ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()},
|
||||||
input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
|
input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::move(config_)},
|
||||||
config{std::make_unique<Config>(*system)},
|
|
||||||
vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
|
vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
|
||||||
provider{std::make_unique<FileSys::ManualContentProvider>()} {
|
provider{std::make_unique<FileSys::ManualContentProvider>()} {
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
@ -1637,7 +1640,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
|
||||||
const auto config_file_name = title_id == 0
|
const auto config_file_name = title_id == 0
|
||||||
? Common::FS::PathToUTF8String(file_path.filename())
|
? Common::FS::PathToUTF8String(file_path.filename())
|
||||||
: fmt::format("{:016X}", title_id);
|
: fmt::format("{:016X}", title_id);
|
||||||
Config per_game_config(*system, config_file_name, Config::ConfigType::PerGameConfig);
|
Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
|
||||||
|
system->ApplySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save configurations
|
// Save configurations
|
||||||
|
@ -2981,7 +2985,7 @@ void GMainWindow::OnConfigure() {
|
||||||
|
|
||||||
Settings::values.disabled_addons.clear();
|
Settings::values.disabled_addons.clear();
|
||||||
|
|
||||||
config = std::make_unique<Config>(*system);
|
config = std::make_unique<Config>();
|
||||||
UISettings::values.reset_to_defaults = false;
|
UISettings::values.reset_to_defaults = false;
|
||||||
|
|
||||||
UISettings::values.game_dirs = std::move(old_game_dirs);
|
UISettings::values.game_dirs = std::move(old_game_dirs);
|
||||||
|
@ -3042,6 +3046,7 @@ void GMainWindow::OnConfigure() {
|
||||||
|
|
||||||
UpdateStatusButtons();
|
UpdateStatusButtons();
|
||||||
controller_dialog->refreshConfiguration();
|
controller_dialog->refreshConfiguration();
|
||||||
|
system->ApplySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnConfigureTas() {
|
void GMainWindow::OnConfigureTas() {
|
||||||
|
@ -4082,7 +4087,24 @@ void GMainWindow::changeEvent(QEvent* event) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
std::unique_ptr<Config> config = std::make_unique<Config>();
|
||||||
bool has_broken_vulkan = false;
|
bool has_broken_vulkan = false;
|
||||||
|
bool is_child = false;
|
||||||
|
if (CheckEnvVars(&is_child)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef YUZU_DBGHELP
|
||||||
|
PROCESS_INFORMATION pi;
|
||||||
|
if (!is_child && Settings::values.create_crash_dumps.GetValue() &&
|
||||||
|
MiniDump::SpawnDebuggee(argv[0], pi)) {
|
||||||
|
// Delete the config object so that it doesn't save when the program exits
|
||||||
|
config.reset(nullptr);
|
||||||
|
MiniDump::DebugDebuggee(pi);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (StartupChecks(argv[0], &has_broken_vulkan)) {
|
if (StartupChecks(argv[0], &has_broken_vulkan)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -4135,7 +4157,7 @@ int main(int argc, char* argv[]) {
|
||||||
// generating shaders
|
// generating shaders
|
||||||
setlocale(LC_ALL, "C");
|
setlocale(LC_ALL, "C");
|
||||||
|
|
||||||
GMainWindow main_window{has_broken_vulkan};
|
GMainWindow main_window{std::move(config), has_broken_vulkan};
|
||||||
// After settings have been loaded by GMainWindow, apply the filter
|
// After settings have been loaded by GMainWindow, apply the filter
|
||||||
main_window.show();
|
main_window.show();
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ class GMainWindow : public QMainWindow {
|
||||||
public:
|
public:
|
||||||
void filterBarSetChecked(bool state);
|
void filterBarSetChecked(bool state);
|
||||||
void UpdateUITheme();
|
void UpdateUITheme();
|
||||||
explicit GMainWindow(bool has_broken_vulkan);
|
explicit GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan);
|
||||||
~GMainWindow() override;
|
~GMainWindow() override;
|
||||||
|
|
||||||
bool DropAction(QDropEvent* event);
|
bool DropAction(QDropEvent* event);
|
||||||
|
|
202
src/yuzu/mini_dump.cpp
Normal file
202
src/yuzu/mini_dump.cpp
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <ctime>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include "yuzu/mini_dump.h"
|
||||||
|
#include "yuzu/startup_checks.h"
|
||||||
|
|
||||||
|
// dbghelp.h must be included after windows.h
|
||||||
|
#include <dbghelp.h>
|
||||||
|
|
||||||
|
namespace MiniDump {
|
||||||
|
|
||||||
|
void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
|
||||||
|
EXCEPTION_POINTERS* pep) {
|
||||||
|
char file_name[255];
|
||||||
|
const std::time_t the_time = std::time(nullptr);
|
||||||
|
std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time));
|
||||||
|
|
||||||
|
// Open the file
|
||||||
|
HANDLE file_handle = CreateFileA(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
|
||||||
|
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||||
|
|
||||||
|
if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) {
|
||||||
|
fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the minidump
|
||||||
|
const MINIDUMP_TYPE dump_type = MiniDumpNormal;
|
||||||
|
|
||||||
|
const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle,
|
||||||
|
dump_type, (pep != 0) ? info : 0, 0, 0);
|
||||||
|
|
||||||
|
if (write_dump_status) {
|
||||||
|
fmt::print(stderr, "MiniDump created: {}", file_name);
|
||||||
|
} else {
|
||||||
|
fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the file
|
||||||
|
CloseHandle(file_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) {
|
||||||
|
EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
|
||||||
|
|
||||||
|
HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId);
|
||||||
|
if (thread_handle == nullptr) {
|
||||||
|
fmt::print(stderr, "OpenThread failed ({})", GetLastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get child process context
|
||||||
|
CONTEXT context = {};
|
||||||
|
context.ContextFlags = CONTEXT_ALL;
|
||||||
|
if (!GetThreadContext(thread_handle, &context)) {
|
||||||
|
fmt::print(stderr, "GetThreadContext failed ({})", GetLastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create exception pointers for minidump
|
||||||
|
EXCEPTION_POINTERS ep;
|
||||||
|
ep.ExceptionRecord = &record;
|
||||||
|
ep.ContextRecord = &context;
|
||||||
|
|
||||||
|
MINIDUMP_EXCEPTION_INFORMATION info;
|
||||||
|
info.ThreadId = deb_ev.dwThreadId;
|
||||||
|
info.ExceptionPointers = &ep;
|
||||||
|
info.ClientPointers = false;
|
||||||
|
|
||||||
|
CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep);
|
||||||
|
|
||||||
|
if (CloseHandle(thread_handle) == 0) {
|
||||||
|
fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) {
|
||||||
|
std::memset(&pi, 0, sizeof(pi));
|
||||||
|
|
||||||
|
// Don't debug if we are already being debugged
|
||||||
|
if (IsDebuggerPresent()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SpawnChild(arg0, &pi, 0)) {
|
||||||
|
fmt::print(stderr, "warning: continuing without crash dumps");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool can_debug = DebugActiveProcess(pi.dwProcessId);
|
||||||
|
if (!can_debug) {
|
||||||
|
fmt::print(stderr,
|
||||||
|
"warning: DebugActiveProcess failed ({}), continuing without crash dumps",
|
||||||
|
GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* ExceptionName(DWORD exception) {
|
||||||
|
switch (exception) {
|
||||||
|
case EXCEPTION_ACCESS_VIOLATION:
|
||||||
|
return "EXCEPTION_ACCESS_VIOLATION";
|
||||||
|
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
||||||
|
return "EXCEPTION_DATATYPE_MISALIGNMENT";
|
||||||
|
case EXCEPTION_BREAKPOINT:
|
||||||
|
return "EXCEPTION_BREAKPOINT";
|
||||||
|
case EXCEPTION_SINGLE_STEP:
|
||||||
|
return "EXCEPTION_SINGLE_STEP";
|
||||||
|
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
||||||
|
return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
|
||||||
|
case EXCEPTION_FLT_DENORMAL_OPERAND:
|
||||||
|
return "EXCEPTION_FLT_DENORMAL_OPERAND";
|
||||||
|
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
||||||
|
return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
|
||||||
|
case EXCEPTION_FLT_INEXACT_RESULT:
|
||||||
|
return "EXCEPTION_FLT_INEXACT_RESULT";
|
||||||
|
case EXCEPTION_FLT_INVALID_OPERATION:
|
||||||
|
return "EXCEPTION_FLT_INVALID_OPERATION";
|
||||||
|
case EXCEPTION_FLT_OVERFLOW:
|
||||||
|
return "EXCEPTION_FLT_OVERFLOW";
|
||||||
|
case EXCEPTION_FLT_STACK_CHECK:
|
||||||
|
return "EXCEPTION_FLT_STACK_CHECK";
|
||||||
|
case EXCEPTION_FLT_UNDERFLOW:
|
||||||
|
return "EXCEPTION_FLT_UNDERFLOW";
|
||||||
|
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
||||||
|
return "EXCEPTION_INT_DIVIDE_BY_ZERO";
|
||||||
|
case EXCEPTION_INT_OVERFLOW:
|
||||||
|
return "EXCEPTION_INT_OVERFLOW";
|
||||||
|
case EXCEPTION_PRIV_INSTRUCTION:
|
||||||
|
return "EXCEPTION_PRIV_INSTRUCTION";
|
||||||
|
case EXCEPTION_IN_PAGE_ERROR:
|
||||||
|
return "EXCEPTION_IN_PAGE_ERROR";
|
||||||
|
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||||
|
return "EXCEPTION_ILLEGAL_INSTRUCTION";
|
||||||
|
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
|
||||||
|
return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
|
||||||
|
case EXCEPTION_STACK_OVERFLOW:
|
||||||
|
return "EXCEPTION_STACK_OVERFLOW";
|
||||||
|
case EXCEPTION_INVALID_DISPOSITION:
|
||||||
|
return "EXCEPTION_INVALID_DISPOSITION";
|
||||||
|
case EXCEPTION_GUARD_PAGE:
|
||||||
|
return "EXCEPTION_GUARD_PAGE";
|
||||||
|
case EXCEPTION_INVALID_HANDLE:
|
||||||
|
return "EXCEPTION_INVALID_HANDLE";
|
||||||
|
default:
|
||||||
|
return "unknown exception type";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugDebuggee(PROCESS_INFORMATION& pi) {
|
||||||
|
DEBUG_EVENT deb_ev = {};
|
||||||
|
|
||||||
|
while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) {
|
||||||
|
const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE);
|
||||||
|
if (!wait_success) {
|
||||||
|
fmt::print(stderr, "error: WaitForDebugEvent failed ({})", GetLastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (deb_ev.dwDebugEventCode) {
|
||||||
|
case OUTPUT_DEBUG_STRING_EVENT:
|
||||||
|
case CREATE_PROCESS_DEBUG_EVENT:
|
||||||
|
case CREATE_THREAD_DEBUG_EVENT:
|
||||||
|
case EXIT_PROCESS_DEBUG_EVENT:
|
||||||
|
case EXIT_THREAD_DEBUG_EVENT:
|
||||||
|
case LOAD_DLL_DEBUG_EVENT:
|
||||||
|
case RIP_EVENT:
|
||||||
|
case UNLOAD_DLL_DEBUG_EVENT:
|
||||||
|
// Continue on all other debug events
|
||||||
|
ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE);
|
||||||
|
break;
|
||||||
|
case EXCEPTION_DEBUG_EVENT:
|
||||||
|
EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
|
||||||
|
|
||||||
|
// We want to generate a crash dump if we are seeing the same exception again.
|
||||||
|
if (!deb_ev.u.Exception.dwFirstChance) {
|
||||||
|
fmt::print(stderr, "Creating MiniDump on ExceptionCode: 0x{:08x} {}\n",
|
||||||
|
record.ExceptionCode, ExceptionName(record.ExceptionCode));
|
||||||
|
DumpFromDebugEvent(deb_ev, pi);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue without handling the exception.
|
||||||
|
// Lets the debuggee use its own exception handler.
|
||||||
|
// - If one does not exist, we will see the exception once more where we make a minidump
|
||||||
|
// for. Then when it reaches here again, yuzu will probably crash.
|
||||||
|
// - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an
|
||||||
|
// infinite loop of exceptions.
|
||||||
|
ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MiniDump
|
19
src/yuzu/mini_dump.h
Normal file
19
src/yuzu/mini_dump.h
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <dbghelp.h>
|
||||||
|
|
||||||
|
namespace MiniDump {
|
||||||
|
|
||||||
|
void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
|
||||||
|
EXCEPTION_POINTERS* pep);
|
||||||
|
|
||||||
|
void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi);
|
||||||
|
bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi);
|
||||||
|
void DebugDebuggee(PROCESS_INFORMATION& pi);
|
||||||
|
|
||||||
|
} // namespace MiniDump
|
|
@ -31,19 +31,36 @@ void CheckVulkan() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
|
bool CheckEnvVars(bool* is_child) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// Check environment variable to see if we are the child
|
// Check environment variable to see if we are the child
|
||||||
char variable_contents[8];
|
char variable_contents[8];
|
||||||
const DWORD startup_check_var =
|
const DWORD startup_check_var =
|
||||||
GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8);
|
GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8);
|
||||||
if (startup_check_var > 0 && std::strncmp(variable_contents, "ON", 8) == 0) {
|
if (startup_check_var > 0 && std::strncmp(variable_contents, ENV_VAR_ENABLED_TEXT, 8) == 0) {
|
||||||
CheckVulkan();
|
CheckVulkan();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't perform startup checks if we are a child process
|
||||||
|
char is_child_s[8];
|
||||||
|
const DWORD is_child_len = GetEnvironmentVariableA(IS_CHILD_ENV_VAR, is_child_s, 8);
|
||||||
|
if (is_child_len > 0 && std::strncmp(is_child_s, ENV_VAR_ENABLED_TEXT, 8) == 0) {
|
||||||
|
*is_child = true;
|
||||||
|
return false;
|
||||||
|
} else if (!SetEnvironmentVariableA(IS_CHILD_ENV_VAR, ENV_VAR_ENABLED_TEXT)) {
|
||||||
|
std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n",
|
||||||
|
IS_CHILD_ENV_VAR, GetLastError());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
|
||||||
|
#ifdef _WIN32
|
||||||
// Set the startup variable for child processes
|
// Set the startup variable for child processes
|
||||||
const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, "ON");
|
const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, ENV_VAR_ENABLED_TEXT);
|
||||||
if (!env_var_set) {
|
if (!env_var_set) {
|
||||||
std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n",
|
std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n",
|
||||||
STARTUP_CHECK_ENV_VAR, GetLastError());
|
STARTUP_CHECK_ENV_VAR, GetLastError());
|
||||||
|
@ -53,7 +70,7 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
|
||||||
PROCESS_INFORMATION process_info;
|
PROCESS_INFORMATION process_info;
|
||||||
std::memset(&process_info, '\0', sizeof(process_info));
|
std::memset(&process_info, '\0', sizeof(process_info));
|
||||||
|
|
||||||
if (!SpawnChild(arg0, &process_info)) {
|
if (!SpawnChild(arg0, &process_info, 0)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +123,7 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) {
|
bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags) {
|
||||||
STARTUPINFOA startup_info;
|
STARTUPINFOA startup_info;
|
||||||
|
|
||||||
std::memset(&startup_info, '\0', sizeof(startup_info));
|
std::memset(&startup_info, '\0', sizeof(startup_info));
|
||||||
|
@ -120,7 +137,7 @@ bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) {
|
||||||
nullptr, // lpProcessAttributes
|
nullptr, // lpProcessAttributes
|
||||||
nullptr, // lpThreadAttributes
|
nullptr, // lpThreadAttributes
|
||||||
false, // bInheritHandles
|
false, // bInheritHandles
|
||||||
0, // dwCreationFlags
|
flags, // dwCreationFlags
|
||||||
nullptr, // lpEnvironment
|
nullptr, // lpEnvironment
|
||||||
nullptr, // lpCurrentDirectory
|
nullptr, // lpCurrentDirectory
|
||||||
&startup_info, // lpStartupInfo
|
&startup_info, // lpStartupInfo
|
||||||
|
|
|
@ -7,11 +7,14 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
constexpr char IS_CHILD_ENV_VAR[] = "YUZU_IS_CHILD";
|
||||||
constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS";
|
constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS";
|
||||||
|
constexpr char ENV_VAR_ENABLED_TEXT[] = "ON";
|
||||||
|
|
||||||
void CheckVulkan();
|
void CheckVulkan();
|
||||||
|
bool CheckEnvVars(bool* is_child);
|
||||||
bool StartupChecks(const char* arg0, bool* has_broken_vulkan);
|
bool StartupChecks(const char* arg0, bool* has_broken_vulkan);
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi);
|
bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -31,6 +31,10 @@
|
||||||
"yuzu-tests": {
|
"yuzu-tests": {
|
||||||
"description": "Compile tests",
|
"description": "Compile tests",
|
||||||
"dependencies": [ "catch2" ]
|
"dependencies": [ "catch2" ]
|
||||||
|
},
|
||||||
|
"dbghelp": {
|
||||||
|
"description": "Compile Windows crash dump (Minidump) support",
|
||||||
|
"dependencies": [ "dbghelp" ]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
|
|
Loading…
Reference in a new issue