mirror of
				https://git.tardis.systems/mirrors/yuzu
				synced 2025-10-30 18:24:15 +01:00 
			
		
		
		
	Merge pull request #11689 from liamwhite/breakpad
qt: implement automatic crash dump support
This commit is contained in:
		
						commit
						eec3d356b6
					
				| @ -19,6 +19,7 @@ cmake .. \ | ||||
|       -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ | ||||
|       -DENABLE_QT_TRANSLATION=ON \ | ||||
|       -DUSE_DISCORD_PRESENCE=ON \ | ||||
|       -DYUZU_CRASH_DUMPS=ON \ | ||||
|       -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \ | ||||
|       -DYUZU_USE_BUNDLED_FFMPEG=ON \ | ||||
|       -GNinja | ||||
|  | ||||
| @ -23,6 +23,7 @@ cmake .. \ | ||||
|       -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \ | ||||
|       -DYUZU_USE_BUNDLED_FFMPEG=ON \ | ||||
|       -DYUZU_ENABLE_LTO=ON \ | ||||
|       -DYUZU_CRASH_DUMPS=ON \ | ||||
|       -GNinja | ||||
| 
 | ||||
| ninja | ||||
|  | ||||
| @ -17,7 +17,6 @@ cmake .. \ | ||||
|     -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ | ||||
|     -DENABLE_QT_TRANSLATION=ON \ | ||||
|     -DUSE_CCACHE=ON \ | ||||
|     -DYUZU_CRASH_DUMPS=ON \ | ||||
|     -DYUZU_USE_BUNDLED_SDL2=OFF \ | ||||
|     -DYUZU_USE_EXTERNAL_SDL2=OFF \ | ||||
|     -DYUZU_TESTS=OFF \ | ||||
|  | ||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @ -58,3 +58,6 @@ | ||||
| [submodule "VulkanMemoryAllocator"] | ||||
| 	path = externals/VulkanMemoryAllocator | ||||
| 	url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git | ||||
| [submodule "breakpad"] | ||||
| 	path = externals/breakpad | ||||
| 	url = https://github.com/yuzu-emu/breakpad.git | ||||
|  | ||||
| @ -52,7 +52,7 @@ option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" | ||||
| 
 | ||||
| CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF) | ||||
| 
 | ||||
| CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF) | ||||
| CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF) | ||||
| 
 | ||||
| option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}") | ||||
| 
 | ||||
| @ -139,9 +139,6 @@ if (YUZU_USE_BUNDLED_VCPKG) | ||||
|     if (YUZU_TESTS) | ||||
|         list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests") | ||||
|     endif() | ||||
|     if (YUZU_CRASH_DUMPS) | ||||
|         list(APPEND VCPKG_MANIFEST_FEATURES "dbghelp") | ||||
|     endif() | ||||
|     if (ENABLE_WEB_SERVICE) | ||||
|         list(APPEND VCPKG_MANIFEST_FEATURES "web-service") | ||||
|     endif() | ||||
| @ -551,6 +548,18 @@ if (NOT YUZU_USE_BUNDLED_FFMPEG) | ||||
|     find_package(FFmpeg 4.3 REQUIRED QUIET COMPONENTS ${FFmpeg_COMPONENTS}) | ||||
| endif() | ||||
| 
 | ||||
| if (WIN32 AND YUZU_CRASH_DUMPS) | ||||
|     set(BREAKPAD_VER "breakpad-c89f9dd") | ||||
|     download_bundled_external("breakpad/" ${BREAKPAD_VER} BREAKPAD_PREFIX) | ||||
| 
 | ||||
|     set(BREAKPAD_CLIENT_INCLUDE_DIR "${BREAKPAD_PREFIX}/include") | ||||
|     set(BREAKPAD_CLIENT_LIBRARY "${BREAKPAD_PREFIX}/lib/libbreakpad_client.lib") | ||||
| 
 | ||||
|     add_library(libbreakpad_client INTERFACE IMPORTED) | ||||
|     target_link_libraries(libbreakpad_client INTERFACE "${BREAKPAD_CLIENT_LIBRARY}") | ||||
|     target_include_directories(libbreakpad_client INTERFACE "${BREAKPAD_CLIENT_INCLUDE_DIR}") | ||||
| endif() | ||||
| 
 | ||||
| # Prefer the -pthread flag on Linux. | ||||
| set(THREADS_PREFER_PTHREAD_FLAG ON) | ||||
| find_package(Threads REQUIRED) | ||||
| @ -570,13 +579,6 @@ elseif (WIN32) | ||||
|         # PSAPI is the Process Status API | ||||
|         set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version) | ||||
|     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)$") | ||||
|     set(PLATFORM_LIBRARIES rt) | ||||
| endif() | ||||
|  | ||||
							
								
								
									
										102
									
								
								externals/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										102
									
								
								externals/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							| @ -189,3 +189,105 @@ if (ANDROID) | ||||
|        add_subdirectory(libadrenotools) | ||||
|    endif() | ||||
| endif() | ||||
| 
 | ||||
| # Breakpad | ||||
| # https://github.com/microsoft/vcpkg/blob/master/ports/breakpad/CMakeLists.txt | ||||
| if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client) | ||||
|     set(BREAKPAD_WIN32_DEFINES | ||||
|         NOMINMAX | ||||
|         UNICODE | ||||
|         WIN32_LEAN_AND_MEAN | ||||
|         _CRT_SECURE_NO_WARNINGS | ||||
|         _CRT_SECURE_NO_DEPRECATE | ||||
|         _CRT_NONSTDC_NO_DEPRECATE | ||||
|     ) | ||||
| 
 | ||||
|     # libbreakpad | ||||
|     add_library(libbreakpad STATIC) | ||||
|     file(GLOB_RECURSE LIBBREAKPAD_SOURCES breakpad/src/processor/*.cc) | ||||
|     file(GLOB_RECURSE LIBDISASM_SOURCES breakpad/src/third_party/libdisasm/*.c) | ||||
|     list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "_unittest|_selftest|synth_minidump|/tests|/testdata|/solaris|microdump_stackwalk|minidump_dump|minidump_stackwalk") | ||||
|     if (WIN32) | ||||
|         list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/linux|/mac|/android") | ||||
|         target_compile_definitions(libbreakpad PRIVATE ${BREAKPAD_WIN32_DEFINES}) | ||||
|         target_include_directories(libbreakpad PRIVATE "${CMAKE_GENERATOR_INSTANCE}/DIA SDK/include") | ||||
|     elseif (APPLE) | ||||
|         list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/linux|/windows|/android") | ||||
|     else() | ||||
|         list(FILTER LIBBREAKPAD_SOURCES EXCLUDE REGEX "/mac|/windows|/android") | ||||
|     endif() | ||||
|     target_sources(libbreakpad PRIVATE ${LIBBREAKPAD_SOURCES} ${LIBDISASM_SOURCES}) | ||||
|     target_include_directories(libbreakpad | ||||
|         PUBLIC | ||||
|             ${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src | ||||
|             ${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src/third_party/libdisasm | ||||
|     ) | ||||
| 
 | ||||
|     # libbreakpad_client | ||||
|     add_library(libbreakpad_client STATIC) | ||||
|     file(GLOB LIBBREAKPAD_COMMON_SOURCES breakpad/src/common/*.cc breakpad/src/common/*.c breakpad/src/client/*.cc) | ||||
| 
 | ||||
|     if (WIN32) | ||||
|         file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/windows/*.cc breakpad/src/common/windows/*.cc) | ||||
|         list(FILTER LIBBREAKPAD_COMMON_SOURCES EXCLUDE REGEX "language.cc|path_helper.cc|stabs_to_module.cc|stabs_reader.cc|minidump_file_writer.cc") | ||||
|         target_include_directories(libbreakpad_client PRIVATE "${CMAKE_GENERATOR_INSTANCE}/DIA SDK/include") | ||||
|         target_compile_definitions(libbreakpad_client PRIVATE ${BREAKPAD_WIN32_DEFINES}) | ||||
|     elseif (APPLE) | ||||
|         target_compile_definitions(libbreakpad_client PRIVATE HAVE_MACH_O_NLIST_H) | ||||
|         file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/mac/*.cc breakpad/src/common/mac/*.cc) | ||||
|         list(APPEND LIBBREAKPAD_CLIENT_SOURCES breakpad/src/common/mac/MachIPC.mm) | ||||
|     else() | ||||
|         target_compile_definitions(libbreakpad_client PUBLIC -DHAVE_A_OUT_H) | ||||
|         file(GLOB_RECURSE LIBBREAKPAD_CLIENT_SOURCES breakpad/src/client/linux/*.cc breakpad/src/common/linux/*.cc) | ||||
|     endif() | ||||
|     list(APPEND LIBBREAKPAD_CLIENT_SOURCES ${LIBBREAKPAD_COMMON_SOURCES}) | ||||
|     list(FILTER LIBBREAKPAD_CLIENT_SOURCES EXCLUDE REGEX "/sender|/tests|/unittests|/testcases|_unittest|_test") | ||||
|     target_sources(libbreakpad_client PRIVATE ${LIBBREAKPAD_CLIENT_SOURCES}) | ||||
|     target_include_directories(libbreakpad_client PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/breakpad/src) | ||||
| 
 | ||||
|     if (WIN32) | ||||
|         target_link_libraries(libbreakpad_client PRIVATE wininet.lib) | ||||
|     elseif (APPLE) | ||||
|         find_library(CoreFoundation_FRAMEWORK CoreFoundation) | ||||
|         target_link_libraries(libbreakpad_client PRIVATE ${CoreFoundation_FRAMEWORK}) | ||||
|     else() | ||||
|         find_library(PTHREAD_LIBRARIES pthread) | ||||
|         target_compile_definitions(libbreakpad_client PRIVATE HAVE_GETCONTEXT=1) | ||||
|         if (PTHREAD_LIBRARIES) | ||||
|             target_link_libraries(libbreakpad_client PRIVATE ${PTHREAD_LIBRARIES}) | ||||
|         endif() | ||||
|     endif() | ||||
| 
 | ||||
|     # Host tools for symbol processing | ||||
|     if (LINUX) | ||||
|         find_package(ZLIB REQUIRED) | ||||
| 
 | ||||
|         add_executable(minidump_stackwalk breakpad/src/processor/minidump_stackwalk.cc) | ||||
|         target_link_libraries(minidump_stackwalk PRIVATE libbreakpad libbreakpad_client) | ||||
| 
 | ||||
|         add_executable(dump_syms | ||||
|             breakpad/src/common/dwarf_cfi_to_module.cc | ||||
|             breakpad/src/common/dwarf_cu_to_module.cc | ||||
|             breakpad/src/common/dwarf_line_to_module.cc | ||||
|             breakpad/src/common/dwarf_range_list_handler.cc | ||||
|             breakpad/src/common/language.cc | ||||
|             breakpad/src/common/module.cc | ||||
|             breakpad/src/common/path_helper.cc | ||||
|             breakpad/src/common/stabs_reader.cc | ||||
|             breakpad/src/common/stabs_to_module.cc | ||||
|             breakpad/src/common/dwarf/bytereader.cc | ||||
|             breakpad/src/common/dwarf/dwarf2diehandler.cc | ||||
|             breakpad/src/common/dwarf/dwarf2reader.cc | ||||
|             breakpad/src/common/dwarf/elf_reader.cc | ||||
|             breakpad/src/common/linux/crc32.cc | ||||
|             breakpad/src/common/linux/dump_symbols.cc | ||||
|             breakpad/src/common/linux/elf_symbols_to_module.cc | ||||
|             breakpad/src/common/linux/elfutils.cc | ||||
|             breakpad/src/common/linux/file_id.cc | ||||
|             breakpad/src/common/linux/linux_libc_support.cc | ||||
|             breakpad/src/common/linux/memory_mapped_file.cc | ||||
|             breakpad/src/common/linux/safe_readlink.cc | ||||
|             breakpad/src/tools/linux/dump_syms/dump_syms.cc) | ||||
|         target_link_libraries(dump_syms PRIVATE libbreakpad_client ZLIB::ZLIB) | ||||
|     endif() | ||||
| endif() | ||||
|  | ||||
							
								
								
									
										1
									
								
								externals/breakpad
									
									
									
									
										vendored
									
									
										Submodule
									
								
							
							
								
								
								
								
								
								
							
						
						
									
										1
									
								
								externals/breakpad
									
									
									
									
										vendored
									
									
										Submodule
									
								
							| @ -0,0 +1 @@ | ||||
| Subproject commit c89f9dddc793f19910ef06c13e4fd240da4e7a59 | ||||
							
								
								
									
										2
									
								
								externals/dynarmic
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
								
							
						
						
									
										2
									
								
								externals/dynarmic
									
									
									
									
										vendored
									
									
								
							| @ -1 +1 @@ | ||||
| Subproject commit 7da378033a7764f955516f75194856d87bbcd7a5 | ||||
| Subproject commit 0df09e2f6b61c2d7ad2f2053d4f020a5c33e0378 | ||||
| @ -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" | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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.
 | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
| @ -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") | ||||
|  | ||||
							
								
								
									
										77
									
								
								src/yuzu/breakpad.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/yuzu/breakpad.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -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
 | ||||
							
								
								
									
										10
									
								
								src/yuzu/breakpad.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/yuzu/breakpad.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
 | ||||
| // SPDX-License-Identifier: GPL-2.0-or-later
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| namespace Breakpad { | ||||
| 
 | ||||
| void InstallCrashHandler(); | ||||
| 
 | ||||
| } | ||||
| @ -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(); | ||||
|  | ||||
| @ -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"> | ||||
|  | ||||
| @ -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(); }); | ||||
|  | ||||
| @ -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
 | ||||
| @ -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
 | ||||
| @ -33,10 +33,6 @@ | ||||
|             "description": "Compile tests", | ||||
|             "dependencies": [ "catch2" ] | ||||
|         }, | ||||
|         "dbghelp": { | ||||
|             "description": "Compile Windows crash dump (Minidump) support", | ||||
|             "dependencies": [ "dbghelp" ] | ||||
|         }, | ||||
|         "web-service": { | ||||
|             "description": "Enable web services (telemetry, etc.)", | ||||
|             "dependencies": [ | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user