diff options
| -rw-r--r-- | src/yuzu/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 5 | ||||
| -rw-r--r-- | src/yuzu/mini_dump.cpp | 122 | ||||
| -rw-r--r-- | src/yuzu/mini_dump.h | 5 |
4 files changed, 71 insertions, 63 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index df0f64b83..29d506c47 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt | |||
| @@ -214,7 +214,7 @@ if (WIN32 AND YUZU_CRASH_DUMPS) | |||
| 214 | mini_dump.h | 214 | mini_dump.h |
| 215 | ) | 215 | ) |
| 216 | 216 | ||
| 217 | target_link_libraries(yuzu PUBLIC ${DBGHELP_LIBRARY}) | 217 | target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY}) |
| 218 | target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP) | 218 | target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP) |
| 219 | endif() | 219 | endif() |
| 220 | 220 | ||
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ff59c64c3..bda9986e1 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -4096,10 +4096,11 @@ int main(int argc, char* argv[]) { | |||
| 4096 | 4096 | ||
| 4097 | #ifdef YUZU_DBGHELP | 4097 | #ifdef YUZU_DBGHELP |
| 4098 | PROCESS_INFORMATION pi; | 4098 | PROCESS_INFORMATION pi; |
| 4099 | if (!is_child && Settings::values.create_crash_dumps.GetValue() && SpawnDebuggee(argv[0], pi)) { | 4099 | if (!is_child && Settings::values.create_crash_dumps.GetValue() && |
| 4100 | MiniDump::SpawnDebuggee(argv[0], pi)) { | ||
| 4100 | // Delete the config object so that it doesn't save when the program exits | 4101 | // Delete the config object so that it doesn't save when the program exits |
| 4101 | config.reset(nullptr); | 4102 | config.reset(nullptr); |
| 4102 | DebugDebuggee(pi); | 4103 | MiniDump::DebugDebuggee(pi); |
| 4103 | return 0; | 4104 | return 0; |
| 4104 | } | 4105 | } |
| 4105 | #endif | 4106 | #endif |
diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp index b25067c10..a34dc6a9c 100644 --- a/src/yuzu/mini_dump.cpp +++ b/src/yuzu/mini_dump.cpp | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #include <cstring> | 5 | #include <cstring> |
| 6 | #include <ctime> | 6 | #include <ctime> |
| 7 | #include <filesystem> | 7 | #include <filesystem> |
| 8 | #include <fmt/format.h> | ||
| 8 | #include <windows.h> | 9 | #include <windows.h> |
| 9 | #include "yuzu/mini_dump.h" | 10 | #include "yuzu/mini_dump.h" |
| 10 | #include "yuzu/startup_checks.h" | 11 | #include "yuzu/startup_checks.h" |
| @@ -12,6 +13,8 @@ | |||
| 12 | // dbghelp.h must be included after windows.h | 13 | // dbghelp.h must be included after windows.h |
| 13 | #include <dbghelp.h> | 14 | #include <dbghelp.h> |
| 14 | 15 | ||
| 16 | namespace MiniDump { | ||
| 17 | |||
| 15 | void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, | 18 | void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, |
| 16 | EXCEPTION_POINTERS* pep) { | 19 | EXCEPTION_POINTERS* pep) { |
| 17 | char file_name[255]; | 20 | char file_name[255]; |
| @@ -23,7 +26,7 @@ void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_ | |||
| 23 | CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); | 26 | CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); |
| 24 | 27 | ||
| 25 | if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) { | 28 | if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) { |
| 26 | std::fprintf(stderr, "CreateFileA failed. Error: %d\n", GetLastError()); | 29 | fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError()); |
| 27 | return; | 30 | return; |
| 28 | } | 31 | } |
| 29 | 32 | ||
| @@ -34,9 +37,9 @@ void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_ | |||
| 34 | dump_type, (pep != 0) ? info : 0, 0, 0); | 37 | dump_type, (pep != 0) ? info : 0, 0, 0); |
| 35 | 38 | ||
| 36 | if (write_dump_status) { | 39 | if (write_dump_status) { |
| 37 | std::fprintf(stderr, "MiniDump created: %s\n", file_name); | 40 | fmt::print(stderr, "MiniDump created: {}", file_name); |
| 38 | } else { | 41 | } else { |
| 39 | std::fprintf(stderr, "MiniDumpWriteDump failed. Error: %d\n", GetLastError()); | 42 | fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError()); |
| 40 | } | 43 | } |
| 41 | 44 | ||
| 42 | // Close the file | 45 | // Close the file |
| @@ -48,15 +51,15 @@ void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) { | |||
| 48 | 51 | ||
| 49 | HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId); | 52 | HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId); |
| 50 | if (thread_handle == nullptr) { | 53 | if (thread_handle == nullptr) { |
| 51 | std::fprintf(stderr, "OpenThread failed (%d)\n", GetLastError()); | 54 | fmt::print(stderr, "OpenThread failed ({})", GetLastError()); |
| 55 | return; | ||
| 52 | } | 56 | } |
| 53 | 57 | ||
| 54 | // Get child process context | 58 | // Get child process context |
| 55 | CONTEXT context; | 59 | CONTEXT context = {}; |
| 56 | std::memset(&context, 0, sizeof(context)); | ||
| 57 | context.ContextFlags = CONTEXT_ALL; | 60 | context.ContextFlags = CONTEXT_ALL; |
| 58 | if (!GetThreadContext(thread_handle, &context)) { | 61 | if (!GetThreadContext(thread_handle, &context)) { |
| 59 | std::fprintf(stderr, "GetThreadContext failed (%d)\n", GetLastError()); | 62 | fmt::print(stderr, "GetThreadContext failed ({})", GetLastError()); |
| 60 | return; | 63 | return; |
| 61 | } | 64 | } |
| 62 | 65 | ||
| @@ -73,7 +76,7 @@ void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) { | |||
| 73 | CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep); | 76 | CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep); |
| 74 | 77 | ||
| 75 | if (CloseHandle(thread_handle) == 0) { | 78 | if (CloseHandle(thread_handle) == 0) { |
| 76 | std::fprintf(stderr, "error: CloseHandle(thread_handle) failed (%d)\n", GetLastError()); | 79 | fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError()); |
| 77 | } | 80 | } |
| 78 | } | 81 | } |
| 79 | 82 | ||
| @@ -86,67 +89,22 @@ bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) { | |||
| 86 | } | 89 | } |
| 87 | 90 | ||
| 88 | if (!SpawnChild(arg0, &pi, 0)) { | 91 | if (!SpawnChild(arg0, &pi, 0)) { |
| 89 | std::fprintf(stderr, "warning: continuing without crash dumps\n"); | 92 | fmt::print(stderr, "warning: continuing without crash dumps"); |
| 90 | return false; | 93 | return false; |
| 91 | } | 94 | } |
| 92 | 95 | ||
| 93 | const bool can_debug = DebugActiveProcess(pi.dwProcessId); | 96 | const bool can_debug = DebugActiveProcess(pi.dwProcessId); |
| 94 | if (!can_debug) { | 97 | if (!can_debug) { |
| 95 | std::fprintf(stderr, | 98 | fmt::print(stderr, |
| 96 | "warning: DebugActiveProcess failed (%d), continuing without crash dumps\n", | 99 | "warning: DebugActiveProcess failed ({}), continuing without crash dumps", |
| 97 | GetLastError()); | 100 | GetLastError()); |
| 98 | return false; | 101 | return false; |
| 99 | } | 102 | } |
| 100 | 103 | ||
| 101 | return true; | 104 | return true; |
| 102 | } | 105 | } |
| 103 | 106 | ||
| 104 | void DebugDebuggee(PROCESS_INFORMATION& pi) { | 107 | static const char* ExceptionName(DWORD exception) { |
| 105 | DEBUG_EVENT deb_ev; | ||
| 106 | std::memset(&deb_ev, 0, sizeof(deb_ev)); | ||
| 107 | |||
| 108 | while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) { | ||
| 109 | const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE); | ||
| 110 | if (!wait_success) { | ||
| 111 | std::fprintf(stderr, "error: WaitForDebugEvent failed (%d)\n", GetLastError()); | ||
| 112 | return; | ||
| 113 | } | ||
| 114 | |||
| 115 | switch (deb_ev.dwDebugEventCode) { | ||
| 116 | case OUTPUT_DEBUG_STRING_EVENT: | ||
| 117 | case CREATE_PROCESS_DEBUG_EVENT: | ||
| 118 | case CREATE_THREAD_DEBUG_EVENT: | ||
| 119 | case EXIT_PROCESS_DEBUG_EVENT: | ||
| 120 | case EXIT_THREAD_DEBUG_EVENT: | ||
| 121 | case LOAD_DLL_DEBUG_EVENT: | ||
| 122 | case RIP_EVENT: | ||
| 123 | case UNLOAD_DLL_DEBUG_EVENT: | ||
| 124 | // Continue on all other debug events | ||
| 125 | ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE); | ||
| 126 | break; | ||
| 127 | case EXCEPTION_DEBUG_EVENT: | ||
| 128 | EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; | ||
| 129 | |||
| 130 | // We want to generate a crash dump if we are seeing the same exception again. | ||
| 131 | if (!deb_ev.u.Exception.dwFirstChance) { | ||
| 132 | std::fprintf(stderr, "Creating MiniDump on ExceptionCode: 0x%08x %s\n", | ||
| 133 | record.ExceptionCode, ExceptionName(record.ExceptionCode)); | ||
| 134 | DumpFromDebugEvent(deb_ev, pi); | ||
| 135 | } | ||
| 136 | |||
| 137 | // Continue without handling the exception. | ||
| 138 | // Lets the debuggee use its own exception handler. | ||
| 139 | // - If one does not exist, we will see the exception once more where we make a minidump | ||
| 140 | // for. Then when it reaches here again, yuzu will probably crash. | ||
| 141 | // - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an | ||
| 142 | // infinite loop of exceptions. | ||
| 143 | ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); | ||
| 144 | break; | ||
| 145 | } | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | const char* ExceptionName(DWORD exception) { | ||
| 150 | switch (exception) { | 108 | switch (exception) { |
| 151 | case EXCEPTION_ACCESS_VIOLATION: | 109 | case EXCEPTION_ACCESS_VIOLATION: |
| 152 | return "EXCEPTION_ACCESS_VIOLATION"; | 110 | return "EXCEPTION_ACCESS_VIOLATION"; |
| @@ -193,6 +151,52 @@ const char* ExceptionName(DWORD exception) { | |||
| 193 | case EXCEPTION_INVALID_HANDLE: | 151 | case EXCEPTION_INVALID_HANDLE: |
| 194 | return "EXCEPTION_INVALID_HANDLE"; | 152 | return "EXCEPTION_INVALID_HANDLE"; |
| 195 | default: | 153 | default: |
| 196 | return nullptr; | 154 | return "unknown exception type"; |
| 197 | } | 155 | } |
| 198 | } | 156 | } |
| 157 | |||
| 158 | void 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 index 2052e5248..d6b6cca84 100644 --- a/src/yuzu/mini_dump.h +++ b/src/yuzu/mini_dump.h | |||
| @@ -7,10 +7,13 @@ | |||
| 7 | 7 | ||
| 8 | #include <dbghelp.h> | 8 | #include <dbghelp.h> |
| 9 | 9 | ||
| 10 | namespace MiniDump { | ||
| 11 | |||
| 10 | void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, | 12 | void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, |
| 11 | EXCEPTION_POINTERS* pep); | 13 | EXCEPTION_POINTERS* pep); |
| 12 | 14 | ||
| 13 | void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi); | 15 | void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi); |
| 14 | bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi); | 16 | bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi); |
| 15 | void DebugDebuggee(PROCESS_INFORMATION& pi); | 17 | void DebugDebuggee(PROCESS_INFORMATION& pi); |
| 16 | const char* ExceptionName(DWORD exception); | 18 | |
| 19 | } // namespace MiniDump | ||