summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorliamwhite <liamwhite@users.noreply.github.com>2023-10-30 04:41:13 +0100
committerGitHub <noreply@github.com>2023-10-30 04:41:13 +0100
commiteec3d356b69711b34145d16e669e25403880774e (patch)
tree55ea35e1fe80f8539ec7fd7ec2aaa3164a5679d1 /src
parentMerge pull request #11911 from german77/leak_event (diff)
parentqt: implement automatic crash dump support (diff)
downloadyuzu-eec3d356b69711b34145d16e669e25403880774e.tar
yuzu-eec3d356b69711b34145d16e669e25403880774e.tar.gz
yuzu-eec3d356b69711b34145d16e669e25403880774e.tar.bz2
yuzu-eec3d356b69711b34145d16e669e25403880774e.tar.lz
yuzu-eec3d356b69711b34145d16e669e25403880774e.tar.xz
yuzu-eec3d356b69711b34145d16e669e25403880774e.tar.zst
yuzu-eec3d356b69711b34145d16e669e25403880774e.zip
Diffstat (limited to 'src')
-rw-r--r--src/common/fs/fs_paths.h1
-rw-r--r--src/common/fs/path_util.cpp1
-rw-r--r--src/common/fs/path_util.h1
-rw-r--r--src/common/settings.h1
-rw-r--r--src/yuzu/CMakeLists.txt10
-rw-r--r--src/yuzu/breakpad.cpp77
-rw-r--r--src/yuzu/breakpad.h10
-rw-r--r--src/yuzu/configuration/configure_debug.cpp18
-rw-r--r--src/yuzu/configuration/configure_debug.ui7
-rw-r--r--src/yuzu/main.cpp19
-rw-r--r--src/yuzu/mini_dump.cpp202
-rw-r--r--src/yuzu/mini_dump.h19
12 files changed, 101 insertions, 265 deletions
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h
index 441c8af97..bcf447089 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -13,6 +13,7 @@
#define AMIIBO_DIR "amiibo"
#define CACHE_DIR "cache"
#define CONFIG_DIR "config"
+#define CRASH_DUMPS_DIR "crash_dumps"
#define DUMP_DIR "dump"
#define KEYS_DIR "keys"
#define LOAD_DIR "load"
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index 0abd81a45..0c4c88cde 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -119,6 +119,7 @@ public:
GenerateYuzuPath(YuzuPath::AmiiboDir, yuzu_path / AMIIBO_DIR);
GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache);
GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config);
+ GenerateYuzuPath(YuzuPath::CrashDumpsDir, yuzu_path / CRASH_DUMPS_DIR);
GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR);
GenerateYuzuPath(YuzuPath::KeysDir, yuzu_path / KEYS_DIR);
GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index 63801c924..2874ea738 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -15,6 +15,7 @@ enum class YuzuPath {
AmiiboDir, // Where Amiibo backups are stored.
CacheDir, // Where cached filesystem data is stored.
ConfigDir, // Where config files are stored.
+ CrashDumpsDir, // Where crash dumps are stored.
DumpDir, // Where dumped data is stored.
KeysDir, // Where key files are stored.
LoadDir, // Where cheat/mod files are stored.
diff --git a/src/common/settings.h b/src/common/settings.h
index 236e33bee..9317075f7 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -505,7 +505,6 @@ struct Values {
linkage, false, "use_auto_stub", Category::Debugging, Specialization::Default, false};
Setting<bool> enable_all_controllers{linkage, false, "enable_all_controllers",
Category::Debugging};
- Setting<bool> create_crash_dumps{linkage, false, "create_crash_dumps", Category::Debugging};
Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
// Miscellaneous
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 34208ed74..33e1fb663 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -227,14 +227,14 @@ add_executable(yuzu
yuzu.rc
)
-if (WIN32 AND YUZU_CRASH_DUMPS)
+if (YUZU_CRASH_DUMPS)
target_sources(yuzu PRIVATE
- mini_dump.cpp
- mini_dump.h
+ breakpad.cpp
+ breakpad.h
)
- target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY})
- target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP)
+ target_link_libraries(yuzu PRIVATE libbreakpad_client)
+ target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS)
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
diff --git a/src/yuzu/breakpad.cpp b/src/yuzu/breakpad.cpp
new file mode 100644
index 000000000..0f6a71ab0
--- /dev/null
+++ b/src/yuzu/breakpad.cpp
@@ -0,0 +1,77 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <ranges>
+
+#if defined(_WIN32)
+#include <client/windows/handler/exception_handler.h>
+#elif defined(__linux__)
+#include <client/linux/handler/exception_handler.h>
+#else
+#error Minidump creation not supported on this platform
+#endif
+
+#include "common/fs/fs_paths.h"
+#include "common/fs/path_util.h"
+#include "yuzu/breakpad.h"
+
+namespace Breakpad {
+
+static void PruneDumpDirectory(const std::filesystem::path& dump_path) {
+ // Code in this function should be exception-safe.
+ struct Entry {
+ std::filesystem::path path;
+ std::filesystem::file_time_type last_write_time;
+ };
+ std::vector<Entry> existing_dumps;
+
+ // Get existing entries.
+ std::error_code ec;
+ std::filesystem::directory_iterator dir(dump_path, ec);
+ for (auto& entry : dir) {
+ if (entry.is_regular_file()) {
+ existing_dumps.push_back(Entry{
+ .path = entry.path(),
+ .last_write_time = entry.last_write_time(ec),
+ });
+ }
+ }
+
+ // Sort descending by creation date.
+ std::ranges::stable_sort(existing_dumps, [](const auto& a, const auto& b) {
+ return a.last_write_time > b.last_write_time;
+ });
+
+ // Delete older dumps.
+ for (size_t i = 5; i < existing_dumps.size(); i++) {
+ std::filesystem::remove(existing_dumps[i].path, ec);
+ }
+}
+
+#if defined(__linux__)
+[[noreturn]] bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context,
+ bool succeeded) {
+ // Prevent time- and space-consuming core dumps from being generated, as we have
+ // already generated a minidump and a core file will not be useful anyway.
+ _exit(1);
+}
+#endif
+
+void InstallCrashHandler() {
+ // Write crash dumps to profile directory.
+ const auto dump_path = GetYuzuPath(Common::FS::YuzuPath::CrashDumpsDir);
+ PruneDumpDirectory(dump_path);
+
+#if defined(_WIN32)
+ // TODO: If we switch to MinGW builds for Windows, this needs to be wrapped in a C API.
+ static google_breakpad::ExceptionHandler eh{dump_path, nullptr, nullptr, nullptr,
+ google_breakpad::ExceptionHandler::HANDLER_ALL};
+#elif defined(__linux__)
+ static google_breakpad::MinidumpDescriptor descriptor{dump_path};
+ static google_breakpad::ExceptionHandler eh{descriptor, nullptr, DumpCallback,
+ nullptr, true, -1};
+#endif
+}
+
+} // namespace Breakpad
diff --git a/src/yuzu/breakpad.h b/src/yuzu/breakpad.h
new file mode 100644
index 000000000..0f911aa9c
--- /dev/null
+++ b/src/yuzu/breakpad.h
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+namespace Breakpad {
+
+void InstallCrashHandler();
+
+}
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index b22fda746..ef421c754 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -27,16 +27,6 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent)
connect(ui->toggle_gdbstub, &QCheckBox::toggled,
[&]() { 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;
@@ -89,13 +79,6 @@ void ConfigureDebug::SetConfiguration() {
ui->disable_web_applet->setEnabled(false);
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
}
void ConfigureDebug::ApplyConfiguration() {
@@ -107,7 +90,6 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked();
Settings::values.reporting_services = ui->reporting_services->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.use_debug_asserts = ui->use_debug_asserts->isChecked();
Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 66b8b7459..76fe98924 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -471,13 +471,6 @@
</property>
</widget>
</item>
- <item row="4" column="0">
- <widget class="QCheckBox" name="create_crash_dumps">
- <property name="text">
- <string>Create Minidump After Crash</string>
- </property>
- </widget>
- </item>
<item row="3" column="0">
<widget class="QCheckBox" name="dump_audio_commands">
<property name="toolTip">
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 816d804c4..7cc11ae3b 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -159,8 +159,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/util/clickable_label.h"
#include "yuzu/vk_device_info.h"
-#ifdef YUZU_DBGHELP
-#include "yuzu/mini_dump.h"
+#ifdef YUZU_CRASH_DUMPS
+#include "yuzu/breakpad.h"
#endif
using namespace Common::Literals;
@@ -5187,22 +5187,15 @@ int main(int argc, char* argv[]) {
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,
Settings::values.perform_vulkan_check.GetValue())) {
return 0;
}
+#ifdef YUZU_CRASH_DUMPS
+ Breakpad::InstallCrashHandler();
+#endif
+
Common::DetachedTasks detached_tasks;
MicroProfileOnThreadCreate("Frontend");
SCOPE_EXIT({ MicroProfileShutdown(); });
diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp
deleted file mode 100644
index a34dc6a9c..000000000
--- a/src/yuzu/mini_dump.cpp
+++ /dev/null
@@ -1,202 +0,0 @@
-// 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
diff --git a/src/yuzu/mini_dump.h b/src/yuzu/mini_dump.h
deleted file mode 100644
index d6b6cca84..000000000
--- a/src/yuzu/mini_dump.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// 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