summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Liam2023-10-04 13:11:05 -0400
committerGravatar Liam2023-10-08 11:35:53 -0400
commitd3997bad9b84579938d8cdb44b1d17cfab7bbcce (patch)
tree4a9128e8ba93d3963ce37b65efd2a940776cae3c
parentMerge pull request #11657 from liamwhite/new-codespell (diff)
downloadyuzu-d3997bad9b84579938d8cdb44b1d17cfab7bbcce.tar.gz
yuzu-d3997bad9b84579938d8cdb44b1d17cfab7bbcce.tar.xz
yuzu-d3997bad9b84579938d8cdb44b1d17cfab7bbcce.zip
qt: implement automatic crash dump support
Diffstat (limited to '')
-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 e5d83d4b9..4b0193565 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 2bef9d6ed..ed757eb0a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -53,7 +53,7 @@ option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android"
53 53
54CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF) 54CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF)
55 55
56CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF) 56CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF)
57 57
58option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}") 58option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}")
59 59
@@ -179,9 +179,6 @@ if (YUZU_USE_BUNDLED_VCPKG)
179 if (YUZU_TESTS) 179 if (YUZU_TESTS)
180 list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests") 180 list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests")
181 endif() 181 endif()
182 if (YUZU_CRASH_DUMPS)
183 list(APPEND VCPKG_MANIFEST_FEATURES "dbghelp")
184 endif()
185 if (ENABLE_WEB_SERVICE) 182 if (ENABLE_WEB_SERVICE)
186 list(APPEND VCPKG_MANIFEST_FEATURES "web-service") 183 list(APPEND VCPKG_MANIFEST_FEATURES "web-service")
187 endif() 184 endif()
@@ -587,6 +584,18 @@ if (NOT YUZU_USE_BUNDLED_FFMPEG)
587 find_package(FFmpeg 4.3 REQUIRED QUIET COMPONENTS ${FFmpeg_COMPONENTS}) 584 find_package(FFmpeg 4.3 REQUIRED QUIET COMPONENTS ${FFmpeg_COMPONENTS})
588endif() 585endif()
589 586
587if (WIN32 AND YUZU_CRASH_DUMPS)
588 set(BREAKPAD_VER "breakpad-c89f9dd")
589 download_bundled_external("breakpad/" ${BREAKPAD_VER} BREAKPAD_PREFIX)
590
591 set(BREAKPAD_CLIENT_INCLUDE_DIR "${BREAKPAD_PREFIX}/include")
592 set(BREAKPAD_CLIENT_LIBRARY "${BREAKPAD_PREFIX}/lib/libbreakpad_client.lib")
593
594 add_library(libbreakpad_client INTERFACE IMPORTED)
595 target_link_libraries(libbreakpad_client INTERFACE "${BREAKPAD_CLIENT_LIBRARY}")
596 target_include_directories(libbreakpad_client INTERFACE "${BREAKPAD_CLIENT_INCLUDE_DIR}")
597endif()
598
590# Prefer the -pthread flag on Linux. 599# Prefer the -pthread flag on Linux.
591set(THREADS_PREFER_PTHREAD_FLAG ON) 600set(THREADS_PREFER_PTHREAD_FLAG ON)
592find_package(Threads REQUIRED) 601find_package(Threads REQUIRED)
@@ -606,13 +615,6 @@ elseif (WIN32)
606 # PSAPI is the Process Status API 615 # PSAPI is the Process Status API
607 set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version) 616 set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
608 endif() 617 endif()
609
610 if (YUZU_CRASH_DUMPS)
611 find_library(DBGHELP_LIBRARY dbghelp)
612 if ("${DBGHELP_LIBRARY}" STREQUAL "DBGHELP_LIBRARY-NOTFOUND")
613 message(FATAL_ERROR "YUZU_CRASH_DUMPS enabled but dbghelp library not found")
614 endif()
615 endif()
616elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$") 618elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
617 set(PLATFORM_LIBRARIES rt) 619 set(PLATFORM_LIBRARIES rt)
618endif() 620endif()
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 9eebc7d65..c2009f34b 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -185,3 +185,105 @@ if (ANDROID)
185 add_subdirectory(libadrenotools) 185 add_subdirectory(libadrenotools)
186 endif() 186 endif()
187endif() 187endif()
188
189# Breakpad
190# https://github.com/microsoft/vcpkg/blob/master/ports/breakpad/CMakeLists.txt
191if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client)
192 set(BREAKPAD_WIN32_DEFINES
193 NOMINMAX
194 UNICODE
195 WIN32_LEAN_AND_MEAN
196 _CRT_SECURE_NO_WARNINGS
197 _CRT_SECURE_NO_DEPRECATE
198 _CRT_NONSTDC_NO_DEPRECATE
199 )
200
201 # libbreakpad
202 add_library(libbreakpad STATIC)
203 file(GLOB_RECURSE LIBBREAKPAD_SOURCES breakpad/src/processor/*.cc)
204 file(GLOB_RECURSE LIBDISASM_SOURCES breakpad/src/third_party/libdisasm/*.c)
205 list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "_unittest|_selftest|synth_minidump|/tests|/testdata|/solaris|microdump_stackwalk|minidump_dump|minidump_stackwalk")
206 if (WIN32)
207 list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/linux|/mac|/android")
208 target_compile_definitions(libbreakpad PRIVATE ${BREAKPAD_WIN32_DEFINES})
209 target_include_directories(libbreakpad PRIVATE "${CMAKE_GENERATOR_INSTANCE}/DIA SDK/include")
210 elseif (APPLE)
211 list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/linux|/windows|/android")
212 else()
213 list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/mac|/windows|/android")
214 endif()
215 target_sources(libbreakpad PRIVATE ${LIBBREAKPAD_SOURCES} ${LIBDISASM_SOURCES})
216 target_include_directories(libbreakpad
217 PUBLIC
218 ${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src
219 ${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src/third_party/libdisasm
220 )
221
222 # libbreakpad_client
223 add_library(libbreakpad_client STATIC)
224 file(GLOB LIBBREAKPAD_COMMON_SOURCES breakpad/src/common/*.cc breakpad/src/common/*.c breakpad/src/client/*.cc)
225
226 if (WIN32)
227 file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/windows/*.cc breakpad/src/common/windows/*.cc)
228 list(FILTER LIBBREAKPAD_COMMON_SOURCES EXCLUDE REGEX "language.cc|path_helper.cc|stabs_to_module.cc|stabs_reader.cc|minidump_file_writer.cc")
229 target_include_directories(libbreakpad_client PRIVATE "${CMAKE_GENERATOR_INSTANCE}/DIA SDK/include")
230 target_compile_definitions(libbreakpad_client PRIVATE ${BREAKPAD_WIN32_DEFINES})
231 elseif (APPLE)
232 target_compile_definitions(libbreakpad_client PRIVATE HAVE_MACH_O_NLIST_H)
233 file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/mac/*.cc breakpad/src/common/mac/*.cc)
234 list(APPEND LIBBREAKPAD_CLIENT_SOURCES breakpad/src/common/mac/MachIPC.mm)
235 else()
236 target_compile_definitions(libbreakpad_client PUBLIC -DHAVE_A_OUT_H)
237 file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/linux/*.cc breakpad/src/common/linux/*.cc)
238 endif()
239 list(APPEND LIBBREAKPAD_CLIENT_SOURCES ${LIBBREAKPAD_COMMON_SOURCES})
240 list(FILTER LIBBREAKPAD_CLIENT_SOURCES EXCLUDE REGEX "/sender|/tests|/unittests|/testcases|_unittest|_test")
241 target_sources(libbreakpad_client PRIVATE ${LIBBREAKPAD_CLIENT_SOURCES})
242 target_include_directories(libbreakpad_client PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src)
243
244 if (WIN32)
245 target_link_libraries(libbreakpad_client PRIVATE wininet.lib)
246 elseif (APPLE)
247 find_library(CoreFoundation_FRAMEWORK CoreFoundation)
248 target_link_libraries(libbreakpad_client PRIVATE ${CoreFoundation_FRAMEWORK})
249 else()
250 find_library(PTHREAD_LIBRARIES pthread)
251 target_compile_definitions(libbreakpad_client PRIVATE HAVE_GETCONTEXT=1)
252 if (PTHREAD_LIBRARIES)
253 target_link_libraries(libbreakpad_client PRIVATE ${PTHREAD_LIBRARIES})
254 endif()
255 endif()
256
257 # Host tools for symbol processing
258 if (LINUX)
259 find_package(ZLIB REQUIRED)
260
261 add_executable(minidump_stackwalk breakpad/src/processor/minidump_stackwalk.cc)
262 target_link_libraries(minidump_stackwalk PRIVATE libbreakpad libbreakpad_client)
263
264 add_executable(dump_syms
265 breakpad/src/common/dwarf_cfi_to_module.cc
266 breakpad/src/common/dwarf_cu_to_module.cc
267 breakpad/src/common/dwarf_line_to_module.cc
268 breakpad/src/common/dwarf_range_list_handler.cc
269 breakpad/src/common/language.cc
270 breakpad/src/common/module.cc
271 breakpad/src/common/path_helper.cc
272 breakpad/src/common/stabs_reader.cc
273 breakpad/src/common/stabs_to_module.cc
274 breakpad/src/common/dwarf/bytereader.cc
275 breakpad/src/common/dwarf/dwarf2diehandler.cc
276 breakpad/src/common/dwarf/dwarf2reader.cc
277 breakpad/src/common/dwarf/elf_reader.cc
278 breakpad/src/common/linux/crc32.cc
279 breakpad/src/common/linux/dump_symbols.cc
280 breakpad/src/common/linux/elf_symbols_to_module.cc
281 breakpad/src/common/linux/elfutils.cc
282 breakpad/src/common/linux/file_id.cc
283 breakpad/src/common/linux/linux_libc_support.cc
284 breakpad/src/common/linux/memory_mapped_file.cc
285 breakpad/src/common/linux/safe_readlink.cc
286 breakpad/src/tools/linux/dump_syms/dump_syms.cc)
287 target_link_libraries(dump_syms PRIVATE libbreakpad_client ZLIB::ZLIB)
288 endif()
289endif()
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 61bac9eba..1a3f6ab45 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 dce219fcf..fbac4d80c 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 ba28964d0..036e475aa 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 98ab0ec2e..6a3fe47c9 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -500,7 +500,6 @@ struct Values {
500 linkage, false, "use_auto_stub", Category::Debugging, Specialization::Default, false}; 500 linkage, false, "use_auto_stub", Category::Debugging, Specialization::Default, false};
501 Setting<bool> enable_all_controllers{linkage, false, "enable_all_controllers", 501 Setting<bool> enable_all_controllers{linkage, false, "enable_all_controllers",
502 Category::Debugging}; 502 Category::Debugging};
503 Setting<bool> create_crash_dumps{linkage, false, "create_crash_dumps", Category::Debugging};
504 Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging}; 503 Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
505 504
506 // Miscellaneous 505 // Miscellaneous
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 8f86a1553..d23c1e9d0 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -225,14 +225,14 @@ add_executable(yuzu
225 yuzu.rc 225 yuzu.rc
226) 226)
227 227
228if (WIN32 AND YUZU_CRASH_DUMPS) 228if (YUZU_CRASH_DUMPS)
229 target_sources(yuzu PRIVATE 229 target_sources(yuzu PRIVATE
230 mini_dump.cpp 230 breakpad.cpp
231 mini_dump.h 231 breakpad.h
232 ) 232 )
233 233
234 target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY}) 234 target_link_libraries(yuzu PRIVATE libbreakpad_client)
235 target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP) 235 target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS)
236endif() 236endif()
237 237
238if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 238if (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 16fa92e2c..eb69da4ba 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -155,8 +155,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
155#include "yuzu/util/clickable_label.h" 155#include "yuzu/util/clickable_label.h"
156#include "yuzu/vk_device_info.h" 156#include "yuzu/vk_device_info.h"
157 157
158#ifdef YUZU_DBGHELP 158#ifdef YUZU_CRASH_DUMPS
159#include "yuzu/mini_dump.h" 159#include "yuzu/breakpad.h"
160#endif 160#endif
161 161
162using namespace Common::Literals; 162using namespace Common::Literals;
@@ -5054,22 +5054,15 @@ int main(int argc, char* argv[]) {
5054 return 0; 5054 return 0;
5055 } 5055 }
5056 5056
5057#ifdef YUZU_DBGHELP
5058 PROCESS_INFORMATION pi;
5059 if (!is_child && Settings::values.create_crash_dumps.GetValue() &&
5060 MiniDump::SpawnDebuggee(argv[0], pi)) {
5061 // Delete the config object so that it doesn't save when the program exits
5062 config.reset(nullptr);
5063 MiniDump::DebugDebuggee(pi);
5064 return 0;
5065 }
5066#endif
5067
5068 if (StartupChecks(argv[0], &has_broken_vulkan, 5057 if (StartupChecks(argv[0], &has_broken_vulkan,
5069 Settings::values.perform_vulkan_check.GetValue())) { 5058 Settings::values.perform_vulkan_check.GetValue())) {
5070 return 0; 5059 return 0;
5071 } 5060 }
5072 5061
5062#ifdef YUZU_CRASH_DUMPS
5063 Breakpad::InstallCrashHandler();
5064#endif
5065
5073 Common::DetachedTasks detached_tasks; 5066 Common::DetachedTasks detached_tasks;
5074 MicroProfileOnThreadCreate("Frontend"); 5067 MicroProfileOnThreadCreate("Frontend");
5075 SCOPE_EXIT({ MicroProfileShutdown(); }); 5068 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": [