summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar liamwhite2023-10-29 23:41:13 -0400
committerGravatar GitHub2023-10-29 23:41:13 -0400
commiteec3d356b69711b34145d16e669e25403880774e (patch)
tree55ea35e1fe80f8539ec7fd7ec2aaa3164a5679d1
parentMerge pull request #11911 from german77/leak_event (diff)
parentqt: implement automatic crash dump support (diff)
downloadyuzu-eec3d356b69711b34145d16e669e25403880774e.tar.gz
yuzu-eec3d356b69711b34145d16e669e25403880774e.tar.xz
yuzu-eec3d356b69711b34145d16e669e25403880774e.zip
Merge pull request #11689 from liamwhite/breakpad
qt: implement automatic crash dump support
-rwxr-xr-x.ci/scripts/clang/docker.sh1
-rwxr-xr-x.ci/scripts/linux/docker.sh1
-rwxr-xr-x.ci/scripts/windows/docker.sh1
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt24
-rw-r--r--externals/CMakeLists.txt102
m---------externals/breakpad0
m---------externals/dynarmic0
-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
-rw-r--r--vcpkg.json4
21 files changed, 221 insertions, 281 deletions
diff --git a/.ci/scripts/clang/docker.sh b/.ci/scripts/clang/docker.sh
index 51769545e..f878e24e1 100755
--- a/.ci/scripts/clang/docker.sh
+++ b/.ci/scripts/clang/docker.sh
@@ -19,6 +19,7 @@ cmake .. \
19 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ 19 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
20 -DENABLE_QT_TRANSLATION=ON \ 20 -DENABLE_QT_TRANSLATION=ON \
21 -DUSE_DISCORD_PRESENCE=ON \ 21 -DUSE_DISCORD_PRESENCE=ON \
22 -DYUZU_CRASH_DUMPS=ON \
22 -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \ 23 -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \
23 -DYUZU_USE_BUNDLED_FFMPEG=ON \ 24 -DYUZU_USE_BUNDLED_FFMPEG=ON \
24 -GNinja 25 -GNinja
diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh
index a16577b27..7bba01d62 100755
--- a/.ci/scripts/linux/docker.sh
+++ b/.ci/scripts/linux/docker.sh
@@ -23,6 +23,7 @@ cmake .. \
23 -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \ 23 -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \
24 -DYUZU_USE_BUNDLED_FFMPEG=ON \ 24 -DYUZU_USE_BUNDLED_FFMPEG=ON \
25 -DYUZU_ENABLE_LTO=ON \ 25 -DYUZU_ENABLE_LTO=ON \
26 -DYUZU_CRASH_DUMPS=ON \
26 -GNinja 27 -GNinja
27 28
28ninja 29ninja
diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh
index 45f75c874..44023600d 100755
--- a/.ci/scripts/windows/docker.sh
+++ b/.ci/scripts/windows/docker.sh
@@ -17,7 +17,6 @@ cmake .. \
17 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ 17 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
18 -DENABLE_QT_TRANSLATION=ON \ 18 -DENABLE_QT_TRANSLATION=ON \
19 -DUSE_CCACHE=ON \ 19 -DUSE_CCACHE=ON \
20 -DYUZU_CRASH_DUMPS=ON \
21 -DYUZU_USE_BUNDLED_SDL2=OFF \ 20 -DYUZU_USE_BUNDLED_SDL2=OFF \
22 -DYUZU_USE_EXTERNAL_SDL2=OFF \ 21 -DYUZU_USE_EXTERNAL_SDL2=OFF \
23 -DYUZU_TESTS=OFF \ 22 -DYUZU_TESTS=OFF \
diff --git a/.gitmodules b/.gitmodules
index 361f4845b..fdddb0d3a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -58,3 +58,6 @@
58[submodule "VulkanMemoryAllocator"] 58[submodule "VulkanMemoryAllocator"]
59 path = externals/VulkanMemoryAllocator 59 path = externals/VulkanMemoryAllocator
60 url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git 60 url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
61[submodule "breakpad"]
62 path = externals/breakpad
63 url = https://github.com/yuzu-emu/breakpad.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 150c78d64..9c35e0946 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,7 +52,7 @@ option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android"
52 52
53CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF) 53CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF)
54 54
55CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF) 55CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF)
56 56
57option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}") 57option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}")
58 58
@@ -139,9 +139,6 @@ if (YUZU_USE_BUNDLED_VCPKG)
139 if (YUZU_TESTS) 139 if (YUZU_TESTS)
140 list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests") 140 list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests")
141 endif() 141 endif()
142 if (YUZU_CRASH_DUMPS)
143 list(APPEND VCPKG_MANIFEST_FEATURES "dbghelp")
144 endif()
145 if (ENABLE_WEB_SERVICE) 142 if (ENABLE_WEB_SERVICE)
146 list(APPEND VCPKG_MANIFEST_FEATURES "web-service") 143 list(APPEND VCPKG_MANIFEST_FEATURES "web-service")
147 endif() 144 endif()
@@ -551,6 +548,18 @@ if (NOT YUZU_USE_BUNDLED_FFMPEG)
551 find_package(FFmpeg 4.3 REQUIRED QUIET COMPONENTS ${FFmpeg_COMPONENTS}) 548 find_package(FFmpeg 4.3 REQUIRED QUIET COMPONENTS ${FFmpeg_COMPONENTS})
552endif() 549endif()
553 550
551if (WIN32 AND YUZU_CRASH_DUMPS)
552 set(BREAKPAD_VER "breakpad-c89f9dd")
553 download_bundled_external("breakpad/" ${BREAKPAD_VER} BREAKPAD_PREFIX)
554
555 set(BREAKPAD_CLIENT_INCLUDE_DIR "${BREAKPAD_PREFIX}/include")
556 set(BREAKPAD_CLIENT_LIBRARY "${BREAKPAD_PREFIX}/lib/libbreakpad_client.lib")
557
558 add_library(libbreakpad_client INTERFACE IMPORTED)
559 target_link_libraries(libbreakpad_client INTERFACE "${BREAKPAD_CLIENT_LIBRARY}")
560 target_include_directories(libbreakpad_client INTERFACE "${BREAKPAD_CLIENT_INCLUDE_DIR}")
561endif()
562
554# Prefer the -pthread flag on Linux. 563# Prefer the -pthread flag on Linux.
555set(THREADS_PREFER_PTHREAD_FLAG ON) 564set(THREADS_PREFER_PTHREAD_FLAG ON)
556find_package(Threads REQUIRED) 565find_package(Threads REQUIRED)
@@ -570,13 +579,6 @@ elseif (WIN32)
570 # PSAPI is the Process Status API 579 # PSAPI is the Process Status API
571 set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version) 580 set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
572 endif() 581 endif()
573
574 if (YUZU_CRASH_DUMPS)
575 find_library(DBGHELP_LIBRARY dbghelp)
576 if ("${DBGHELP_LIBRARY}" STREQUAL "DBGHELP_LIBRARY-NOTFOUND")
577 message(FATAL_ERROR "YUZU_CRASH_DUMPS enabled but dbghelp library not found")
578 endif()
579 endif()
580elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$") 582elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
581 set(PLATFORM_LIBRARIES rt) 583 set(PLATFORM_LIBRARIES rt)
582endif() 584endif()
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 6e5bfbba6..61baabb03 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -189,3 +189,105 @@ if (ANDROID)
189 add_subdirectory(libadrenotools) 189 add_subdirectory(libadrenotools)
190 endif() 190 endif()
191endif() 191endif()
192
193# Breakpad
194# https://github.com/microsoft/vcpkg/blob/master/ports/breakpad/CMakeLists.txt
195if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client)
196 set(BREAKPAD_WIN32_DEFINES
197 NOMINMAX
198 UNICODE
199 WIN32_LEAN_AND_MEAN
200 _CRT_SECURE_NO_WARNINGS
201 _CRT_SECURE_NO_DEPRECATE
202 _CRT_NONSTDC_NO_DEPRECATE
203 )
204
205 # libbreakpad
206 add_library(libbreakpad STATIC)
207 file(GLOB_RECURSE LIBBREAKPAD_SOURCES breakpad/src/processor/*.cc)
208 file(GLOB_RECURSE LIBDISASM_SOURCES breakpad/src/third_party/libdisasm/*.c)
209 list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "_unittest|_selftest|synth_minidump|/tests|/testdata|/solaris|microdump_stackwalk|minidump_dump|minidump_stackwalk")
210 if (WIN32)
211 list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/linux|/mac|/android")
212 target_compile_definitions(libbreakpad PRIVATE ${BREAKPAD_WIN32_DEFINES})
213 target_include_directories(libbreakpad PRIVATE "${CMAKE_GENERATOR_INSTANCE}/DIA SDK/include")
214 elseif (APPLE)
215 list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/linux|/windows|/android")
216 else()
217 list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/mac|/windows|/android")
218 endif()
219 target_sources(libbreakpad PRIVATE ${LIBBREAKPAD_SOURCES} ${LIBDISASM_SOURCES})
220 target_include_directories(libbreakpad
221 PUBLIC
222 ${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src
223 ${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src/third_party/libdisasm
224 )
225
226 # libbreakpad_client
227 add_library(libbreakpad_client STATIC)
228 file(GLOB LIBBREAKPAD_COMMON_SOURCES breakpad/src/common/*.cc breakpad/src/common/*.c breakpad/src/client/*.cc)
229
230 if (WIN32)
231 file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/windows/*.cc breakpad/src/common/windows/*.cc)
232 list(FILTER LIBBREAKPAD_COMMON_SOURCES EXCLUDE REGEX "language.cc|path_helper.cc|stabs_to_module.cc|stabs_reader.cc|minidump_file_writer.cc")
233 target_include_directories(libbreakpad_client PRIVATE "${CMAKE_GENERATOR_INSTANCE}/DIA SDK/include")
234 target_compile_definitions(libbreakpad_client PRIVATE ${BREAKPAD_WIN32_DEFINES})
235 elseif (APPLE)
236 target_compile_definitions(libbreakpad_client PRIVATE HAVE_MACH_O_NLIST_H)
237 file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/mac/*.cc breakpad/src/common/mac/*.cc)
238 list(APPEND LIBBREAKPAD_CLIENT_SOURCES breakpad/src/common/mac/MachIPC.mm)
239 else()
240 target_compile_definitions(libbreakpad_client PUBLIC -DHAVE_A_OUT_H)
241 file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/linux/*.cc breakpad/src/common/linux/*.cc)
242 endif()
243 list(APPEND LIBBREAKPAD_CLIENT_SOURCES ${LIBBREAKPAD_COMMON_SOURCES})
244 list(FILTER LIBBREAKPAD_CLIENT_SOURCES EXCLUDE REGEX "/sender|/tests|/unittests|/testcases|_unittest|_test")
245 target_sources(libbreakpad_client PRIVATE ${LIBBREAKPAD_CLIENT_SOURCES})
246 target_include_directories(libbreakpad_client PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src)
247
248 if (WIN32)
249 target_link_libraries(libbreakpad_client PRIVATE wininet.lib)
250 elseif (APPLE)
251 find_library(CoreFoundation_FRAMEWORK CoreFoundation)
252 target_link_libraries(libbreakpad_client PRIVATE ${CoreFoundation_FRAMEWORK})
253 else()
254 find_library(PTHREAD_LIBRARIES pthread)
255 target_compile_definitions(libbreakpad_client PRIVATE HAVE_GETCONTEXT=1)
256 if (PTHREAD_LIBRARIES)
257 target_link_libraries(libbreakpad_client PRIVATE ${PTHREAD_LIBRARIES})
258 endif()
259 endif()
260
261 # Host tools for symbol processing
262 if (LINUX)
263 find_package(ZLIB REQUIRED)
264
265 add_executable(minidump_stackwalk breakpad/src/processor/minidump_stackwalk.cc)
266 target_link_libraries(minidump_stackwalk PRIVATE libbreakpad libbreakpad_client)
267
268 add_executable(dump_syms
269 breakpad/src/common/dwarf_cfi_to_module.cc
270 breakpad/src/common/dwarf_cu_to_module.cc
271 breakpad/src/common/dwarf_line_to_module.cc
272 breakpad/src/common/dwarf_range_list_handler.cc
273 breakpad/src/common/language.cc
274 breakpad/src/common/module.cc
275 breakpad/src/common/path_helper.cc
276 breakpad/src/common/stabs_reader.cc
277 breakpad/src/common/stabs_to_module.cc
278 breakpad/src/common/dwarf/bytereader.cc
279 breakpad/src/common/dwarf/dwarf2diehandler.cc
280 breakpad/src/common/dwarf/dwarf2reader.cc
281 breakpad/src/common/dwarf/elf_reader.cc
282 breakpad/src/common/linux/crc32.cc
283 breakpad/src/common/linux/dump_symbols.cc
284 breakpad/src/common/linux/elf_symbols_to_module.cc
285 breakpad/src/common/linux/elfutils.cc
286 breakpad/src/common/linux/file_id.cc
287 breakpad/src/common/linux/linux_libc_support.cc
288 breakpad/src/common/linux/memory_mapped_file.cc
289 breakpad/src/common/linux/safe_readlink.cc
290 breakpad/src/tools/linux/dump_syms/dump_syms.cc)
291 target_link_libraries(dump_syms PRIVATE libbreakpad_client ZLIB::ZLIB)
292 endif()
293endif()
diff --git a/externals/breakpad b/externals/breakpad
new file mode 160000
Subproject c89f9dddc793f19910ef06c13e4fd240da4e7a5
diff --git a/externals/dynarmic b/externals/dynarmic
Subproject 7da378033a7764f955516f75194856d87bbcd7a Subproject 0df09e2f6b61c2d7ad2f2053d4f020a5c33e037
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 @@
13#define AMIIBO_DIR "amiibo" 13#define AMIIBO_DIR "amiibo"
14#define CACHE_DIR "cache" 14#define CACHE_DIR "cache"
15#define CONFIG_DIR "config" 15#define CONFIG_DIR "config"
16#define CRASH_DUMPS_DIR "crash_dumps"
16#define DUMP_DIR "dump" 17#define DUMP_DIR "dump"
17#define KEYS_DIR "keys" 18#define KEYS_DIR "keys"
18#define LOAD_DIR "load" 19#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:
119 GenerateYuzuPath(YuzuPath::AmiiboDir, yuzu_path / AMIIBO_DIR); 119 GenerateYuzuPath(YuzuPath::AmiiboDir, yuzu_path / AMIIBO_DIR);
120 GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache); 120 GenerateYuzuPath(YuzuPath::CacheDir, yuzu_path_cache);
121 GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config); 121 GenerateYuzuPath(YuzuPath::ConfigDir, yuzu_path_config);
122 GenerateYuzuPath(YuzuPath::CrashDumpsDir, yuzu_path / CRASH_DUMPS_DIR);
122 GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR); 123 GenerateYuzuPath(YuzuPath::DumpDir, yuzu_path / DUMP_DIR);
123 GenerateYuzuPath(YuzuPath::KeysDir, yuzu_path / KEYS_DIR); 124 GenerateYuzuPath(YuzuPath::KeysDir, yuzu_path / KEYS_DIR);
124 GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); 125 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 {
15 AmiiboDir, // Where Amiibo backups are stored. 15 AmiiboDir, // Where Amiibo backups are stored.
16 CacheDir, // Where cached filesystem data is stored. 16 CacheDir, // Where cached filesystem data is stored.
17 ConfigDir, // Where config files are stored. 17 ConfigDir, // Where config files are stored.
18 CrashDumpsDir, // Where crash dumps are stored.
18 DumpDir, // Where dumped data is stored. 19 DumpDir, // Where dumped data is stored.
19 KeysDir, // Where key files are stored. 20 KeysDir, // Where key files are stored.
20 LoadDir, // Where cheat/mod files are stored. 21 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 {
505 linkage, false, "use_auto_stub", Category::Debugging, Specialization::Default, false}; 505 linkage, false, "use_auto_stub", Category::Debugging, Specialization::Default, false};
506 Setting<bool> enable_all_controllers{linkage, false, "enable_all_controllers", 506 Setting<bool> enable_all_controllers{linkage, false, "enable_all_controllers",
507 Category::Debugging}; 507 Category::Debugging};
508 Setting<bool> create_crash_dumps{linkage, false, "create_crash_dumps", Category::Debugging};
509 Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging}; 508 Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
510 509
511 // Miscellaneous 510 // 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
227 yuzu.rc 227 yuzu.rc
228) 228)
229 229
230if (WIN32 AND YUZU_CRASH_DUMPS) 230if (YUZU_CRASH_DUMPS)
231 target_sources(yuzu PRIVATE 231 target_sources(yuzu PRIVATE
232 mini_dump.cpp 232 breakpad.cpp
233 mini_dump.h 233 breakpad.h
234 ) 234 )
235 235
236 target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY}) 236 target_link_libraries(yuzu PRIVATE libbreakpad_client)
237 target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP) 237 target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS)
238endif() 238endif()
239 239
240if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 240if (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 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <ranges>
6
7#if defined(_WIN32)
8#include <client/windows/handler/exception_handler.h>
9#elif defined(__linux__)
10#include <client/linux/handler/exception_handler.h>
11#else
12#error Minidump creation not supported on this platform
13#endif
14
15#include "common/fs/fs_paths.h"
16#include "common/fs/path_util.h"
17#include "yuzu/breakpad.h"
18
19namespace Breakpad {
20
21static void PruneDumpDirectory(const std::filesystem::path& dump_path) {
22 // Code in this function should be exception-safe.
23 struct Entry {
24 std::filesystem::path path;
25 std::filesystem::file_time_type last_write_time;
26 };
27 std::vector<Entry> existing_dumps;
28
29 // Get existing entries.
30 std::error_code ec;
31 std::filesystem::directory_iterator dir(dump_path, ec);
32 for (auto& entry : dir) {
33 if (entry.is_regular_file()) {
34 existing_dumps.push_back(Entry{
35 .path = entry.path(),
36 .last_write_time = entry.last_write_time(ec),
37 });
38 }
39 }
40
41 // Sort descending by creation date.
42 std::ranges::stable_sort(existing_dumps, [](const auto& a, const auto& b) {
43 return a.last_write_time > b.last_write_time;
44 });
45
46 // Delete older dumps.
47 for (size_t i = 5; i < existing_dumps.size(); i++) {
48 std::filesystem::remove(existing_dumps[i].path, ec);
49 }
50}
51
52#if defined(__linux__)
53[[noreturn]] bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context,
54 bool succeeded) {
55 // Prevent time- and space-consuming core dumps from being generated, as we have
56 // already generated a minidump and a core file will not be useful anyway.
57 _exit(1);
58}
59#endif
60
61void InstallCrashHandler() {
62 // Write crash dumps to profile directory.
63 const auto dump_path = GetYuzuPath(Common::FS::YuzuPath::CrashDumpsDir);
64 PruneDumpDirectory(dump_path);
65
66#if defined(_WIN32)
67 // TODO: If we switch to MinGW builds for Windows, this needs to be wrapped in a C API.
68 static google_breakpad::ExceptionHandler eh{dump_path, nullptr, nullptr, nullptr,
69 google_breakpad::ExceptionHandler::HANDLER_ALL};
70#elif defined(__linux__)
71 static google_breakpad::MinidumpDescriptor descriptor{dump_path};
72 static google_breakpad::ExceptionHandler eh{descriptor, nullptr, DumpCallback,
73 nullptr, true, -1};
74#endif
75}
76
77} // 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 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6namespace Breakpad {
7
8void InstallCrashHandler();
9
10}
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)
27 27
28 connect(ui->toggle_gdbstub, &QCheckBox::toggled, 28 connect(ui->toggle_gdbstub, &QCheckBox::toggled,
29 [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); }); 29 [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); });
30
31 connect(ui->create_crash_dumps, &QCheckBox::stateChanged, [&](int) {
32 if (crash_dump_warning_shown) {
33 return;
34 }
35 QMessageBox::warning(this, tr("Restart Required"),
36 tr("yuzu is required to restart in order to apply this setting."),
37 QMessageBox::Ok, QMessageBox::Ok);
38 crash_dump_warning_shown = true;
39 });
40} 30}
41 31
42ConfigureDebug::~ConfigureDebug() = default; 32ConfigureDebug::~ConfigureDebug() = default;
@@ -89,13 +79,6 @@ void ConfigureDebug::SetConfiguration() {
89 ui->disable_web_applet->setEnabled(false); 79 ui->disable_web_applet->setEnabled(false);
90 ui->disable_web_applet->setText(tr("Web applet not compiled")); 80 ui->disable_web_applet->setText(tr("Web applet not compiled"));
91#endif 81#endif
92
93#ifdef YUZU_DBGHELP
94 ui->create_crash_dumps->setChecked(Settings::values.create_crash_dumps.GetValue());
95#else
96 ui->create_crash_dumps->setEnabled(false);
97 ui->create_crash_dumps->setText(tr("MiniDump creation not compiled"));
98#endif
99} 82}
100 83
101void ConfigureDebug::ApplyConfiguration() { 84void ConfigureDebug::ApplyConfiguration() {
@@ -107,7 +90,6 @@ void ConfigureDebug::ApplyConfiguration() {
107 Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); 90 Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked();
108 Settings::values.reporting_services = ui->reporting_services->isChecked(); 91 Settings::values.reporting_services = ui->reporting_services->isChecked();
109 Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked(); 92 Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked();
110 Settings::values.create_crash_dumps = ui->create_crash_dumps->isChecked();
111 Settings::values.quest_flag = ui->quest_flag->isChecked(); 93 Settings::values.quest_flag = ui->quest_flag->isChecked();
112 Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); 94 Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked();
113 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); 95 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 @@
471 </property> 471 </property>
472 </widget> 472 </widget>
473 </item> 473 </item>
474 <item row="4" column="0">
475 <widget class="QCheckBox" name="create_crash_dumps">
476 <property name="text">
477 <string>Create Minidump After Crash</string>
478 </property>
479 </widget>
480 </item>
481 <item row="3" column="0"> 474 <item row="3" column="0">
482 <widget class="QCheckBox" name="dump_audio_commands"> 475 <widget class="QCheckBox" name="dump_audio_commands">
483 <property name="toolTip"> 476 <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
159#include "yuzu/util/clickable_label.h" 159#include "yuzu/util/clickable_label.h"
160#include "yuzu/vk_device_info.h" 160#include "yuzu/vk_device_info.h"
161 161
162#ifdef YUZU_DBGHELP 162#ifdef YUZU_CRASH_DUMPS
163#include "yuzu/mini_dump.h" 163#include "yuzu/breakpad.h"
164#endif 164#endif
165 165
166using namespace Common::Literals; 166using namespace Common::Literals;
@@ -5187,22 +5187,15 @@ int main(int argc, char* argv[]) {
5187 return 0; 5187 return 0;
5188 } 5188 }
5189 5189
5190#ifdef YUZU_DBGHELP
5191 PROCESS_INFORMATION pi;
5192 if (!is_child && Settings::values.create_crash_dumps.GetValue() &&
5193 MiniDump::SpawnDebuggee(argv[0], pi)) {
5194 // Delete the config object so that it doesn't save when the program exits
5195 config.reset(nullptr);
5196 MiniDump::DebugDebuggee(pi);
5197 return 0;
5198 }
5199#endif
5200
5201 if (StartupChecks(argv[0], &has_broken_vulkan, 5190 if (StartupChecks(argv[0], &has_broken_vulkan,
5202 Settings::values.perform_vulkan_check.GetValue())) { 5191 Settings::values.perform_vulkan_check.GetValue())) {
5203 return 0; 5192 return 0;
5204 } 5193 }
5205 5194
5195#ifdef YUZU_CRASH_DUMPS
5196 Breakpad::InstallCrashHandler();
5197#endif
5198
5206 Common::DetachedTasks detached_tasks; 5199 Common::DetachedTasks detached_tasks;
5207 MicroProfileOnThreadCreate("Frontend"); 5200 MicroProfileOnThreadCreate("Frontend");
5208 SCOPE_EXIT({ MicroProfileShutdown(); }); 5201 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 @@
1// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <cstdio>
5#include <cstring>
6#include <ctime>
7#include <filesystem>
8#include <fmt/format.h>
9#include <windows.h>
10#include "yuzu/mini_dump.h"
11#include "yuzu/startup_checks.h"
12
13// dbghelp.h must be included after windows.h
14#include <dbghelp.h>
15
16namespace MiniDump {
17
18void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
19 EXCEPTION_POINTERS* pep) {
20 char file_name[255];
21 const std::time_t the_time = std::time(nullptr);
22 std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time));
23
24 // Open the file
25 HANDLE file_handle = CreateFileA(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
26 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
27
28 if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) {
29 fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError());
30 return;
31 }
32
33 // Create the minidump
34 const MINIDUMP_TYPE dump_type = MiniDumpNormal;
35
36 const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle,
37 dump_type, (pep != 0) ? info : 0, 0, 0);
38
39 if (write_dump_status) {
40 fmt::print(stderr, "MiniDump created: {}", file_name);
41 } else {
42 fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError());
43 }
44
45 // Close the file
46 CloseHandle(file_handle);
47}
48
49void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) {
50 EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
51
52 HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId);
53 if (thread_handle == nullptr) {
54 fmt::print(stderr, "OpenThread failed ({})", GetLastError());
55 return;
56 }
57
58 // Get child process context
59 CONTEXT context = {};
60 context.ContextFlags = CONTEXT_ALL;
61 if (!GetThreadContext(thread_handle, &context)) {
62 fmt::print(stderr, "GetThreadContext failed ({})", GetLastError());
63 return;
64 }
65
66 // Create exception pointers for minidump
67 EXCEPTION_POINTERS ep;
68 ep.ExceptionRecord = &record;
69 ep.ContextRecord = &context;
70
71 MINIDUMP_EXCEPTION_INFORMATION info;
72 info.ThreadId = deb_ev.dwThreadId;
73 info.ExceptionPointers = &ep;
74 info.ClientPointers = false;
75
76 CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep);
77
78 if (CloseHandle(thread_handle) == 0) {
79 fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError());
80 }
81}
82
83bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) {
84 std::memset(&pi, 0, sizeof(pi));
85
86 // Don't debug if we are already being debugged
87 if (IsDebuggerPresent()) {
88 return false;
89 }
90
91 if (!SpawnChild(arg0, &pi, 0)) {
92 fmt::print(stderr, "warning: continuing without crash dumps");
93 return false;
94 }
95
96 const bool can_debug = DebugActiveProcess(pi.dwProcessId);
97 if (!can_debug) {
98 fmt::print(stderr,
99 "warning: DebugActiveProcess failed ({}), continuing without crash dumps",
100 GetLastError());
101 return false;
102 }
103
104 return true;
105}
106
107static const char* ExceptionName(DWORD exception) {
108 switch (exception) {
109 case EXCEPTION_ACCESS_VIOLATION:
110 return "EXCEPTION_ACCESS_VIOLATION";
111 case EXCEPTION_DATATYPE_MISALIGNMENT:
112 return "EXCEPTION_DATATYPE_MISALIGNMENT";
113 case EXCEPTION_BREAKPOINT:
114 return "EXCEPTION_BREAKPOINT";
115 case EXCEPTION_SINGLE_STEP:
116 return "EXCEPTION_SINGLE_STEP";
117 case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
118 return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
119 case EXCEPTION_FLT_DENORMAL_OPERAND:
120 return "EXCEPTION_FLT_DENORMAL_OPERAND";
121 case EXCEPTION_FLT_DIVIDE_BY_ZERO:
122 return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
123 case EXCEPTION_FLT_INEXACT_RESULT:
124 return "EXCEPTION_FLT_INEXACT_RESULT";
125 case EXCEPTION_FLT_INVALID_OPERATION:
126 return "EXCEPTION_FLT_INVALID_OPERATION";
127 case EXCEPTION_FLT_OVERFLOW:
128 return "EXCEPTION_FLT_OVERFLOW";
129 case EXCEPTION_FLT_STACK_CHECK:
130 return "EXCEPTION_FLT_STACK_CHECK";
131 case EXCEPTION_FLT_UNDERFLOW:
132 return "EXCEPTION_FLT_UNDERFLOW";
133 case EXCEPTION_INT_DIVIDE_BY_ZERO:
134 return "EXCEPTION_INT_DIVIDE_BY_ZERO";
135 case EXCEPTION_INT_OVERFLOW:
136 return "EXCEPTION_INT_OVERFLOW";
137 case EXCEPTION_PRIV_INSTRUCTION:
138 return "EXCEPTION_PRIV_INSTRUCTION";
139 case EXCEPTION_IN_PAGE_ERROR:
140 return "EXCEPTION_IN_PAGE_ERROR";
141 case EXCEPTION_ILLEGAL_INSTRUCTION:
142 return "EXCEPTION_ILLEGAL_INSTRUCTION";
143 case EXCEPTION_NONCONTINUABLE_EXCEPTION:
144 return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
145 case EXCEPTION_STACK_OVERFLOW:
146 return "EXCEPTION_STACK_OVERFLOW";
147 case EXCEPTION_INVALID_DISPOSITION:
148 return "EXCEPTION_INVALID_DISPOSITION";
149 case EXCEPTION_GUARD_PAGE:
150 return "EXCEPTION_GUARD_PAGE";
151 case EXCEPTION_INVALID_HANDLE:
152 return "EXCEPTION_INVALID_HANDLE";
153 default:
154 return "unknown exception type";
155 }
156}
157
158void DebugDebuggee(PROCESS_INFORMATION& pi) {
159 DEBUG_EVENT deb_ev = {};
160
161 while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) {
162 const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE);
163 if (!wait_success) {
164 fmt::print(stderr, "error: WaitForDebugEvent failed ({})", GetLastError());
165 return;
166 }
167
168 switch (deb_ev.dwDebugEventCode) {
169 case OUTPUT_DEBUG_STRING_EVENT:
170 case CREATE_PROCESS_DEBUG_EVENT:
171 case CREATE_THREAD_DEBUG_EVENT:
172 case EXIT_PROCESS_DEBUG_EVENT:
173 case EXIT_THREAD_DEBUG_EVENT:
174 case LOAD_DLL_DEBUG_EVENT:
175 case RIP_EVENT:
176 case UNLOAD_DLL_DEBUG_EVENT:
177 // Continue on all other debug events
178 ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE);
179 break;
180 case EXCEPTION_DEBUG_EVENT:
181 EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
182
183 // We want to generate a crash dump if we are seeing the same exception again.
184 if (!deb_ev.u.Exception.dwFirstChance) {
185 fmt::print(stderr, "Creating MiniDump on ExceptionCode: 0x{:08x} {}\n",
186 record.ExceptionCode, ExceptionName(record.ExceptionCode));
187 DumpFromDebugEvent(deb_ev, pi);
188 }
189
190 // Continue without handling the exception.
191 // Lets the debuggee use its own exception handler.
192 // - If one does not exist, we will see the exception once more where we make a minidump
193 // for. Then when it reaches here again, yuzu will probably crash.
194 // - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an
195 // infinite loop of exceptions.
196 ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
197 break;
198 }
199 }
200}
201
202} // 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 @@
1// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <windows.h>
7
8#include <dbghelp.h>
9
10namespace MiniDump {
11
12void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
13 EXCEPTION_POINTERS* pep);
14
15void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi);
16bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi);
17void DebugDebuggee(PROCESS_INFORMATION& pi);
18
19} // namespace MiniDump
diff --git a/vcpkg.json b/vcpkg.json
index 203fc9708..da4e9edb9 100644
--- a/vcpkg.json
+++ b/vcpkg.json
@@ -33,10 +33,6 @@
33 "description": "Compile tests", 33 "description": "Compile tests",
34 "dependencies": [ "catch2" ] 34 "dependencies": [ "catch2" ]
35 }, 35 },
36 "dbghelp": {
37 "description": "Compile Windows crash dump (Minidump) support",
38 "dependencies": [ "dbghelp" ]
39 },
40 "web-service": { 36 "web-service": {
41 "description": "Enable web services (telemetry, etc.)", 37 "description": "Enable web services (telemetry, etc.)",
42 "dependencies": [ 38 "dependencies": [