Merge pull request #11689 from liamwhite/breakpad
qt: implement automatic crash dump support
This commit is contained in:
		@@ -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
									
								
							 Submodule externals/breakpad added at c89f9dddc7
									
								
							
							
								
								
									
										2
									
								
								externals/dynarmic
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								externals/dynarmic
									
									
									
									
										vendored
									
									
								
							 Submodule externals/dynarmic updated: 7da378033a...0df09e2f6b
									
								
							@@ -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": [
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user