mini_dump.cpp 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. // SPDX-FileCopyrightText: 2022 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <cstdio>
  4. #include <cstring>
  5. #include <ctime>
  6. #include <filesystem>
  7. #include <fmt/format.h>
  8. #include <windows.h>
  9. #include "yuzu/mini_dump.h"
  10. #include "yuzu/startup_checks.h"
  11. // dbghelp.h must be included after windows.h
  12. #include <dbghelp.h>
  13. namespace MiniDump {
  14. void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
  15. EXCEPTION_POINTERS* pep) {
  16. char file_name[255];
  17. const std::time_t the_time = std::time(nullptr);
  18. std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time));
  19. // Open the file
  20. HANDLE file_handle = CreateFileA(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
  21. CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
  22. if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) {
  23. fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError());
  24. return;
  25. }
  26. // Create the minidump
  27. const MINIDUMP_TYPE dump_type = MiniDumpNormal;
  28. const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle,
  29. dump_type, (pep != 0) ? info : 0, 0, 0);
  30. if (write_dump_status) {
  31. fmt::print(stderr, "MiniDump created: {}", file_name);
  32. } else {
  33. fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError());
  34. }
  35. // Close the file
  36. CloseHandle(file_handle);
  37. }
  38. void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) {
  39. EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
  40. HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId);
  41. if (thread_handle == nullptr) {
  42. fmt::print(stderr, "OpenThread failed ({})", GetLastError());
  43. return;
  44. }
  45. // Get child process context
  46. CONTEXT context = {};
  47. context.ContextFlags = CONTEXT_ALL;
  48. if (!GetThreadContext(thread_handle, &context)) {
  49. fmt::print(stderr, "GetThreadContext failed ({})", GetLastError());
  50. return;
  51. }
  52. // Create exception pointers for minidump
  53. EXCEPTION_POINTERS ep;
  54. ep.ExceptionRecord = &record;
  55. ep.ContextRecord = &context;
  56. MINIDUMP_EXCEPTION_INFORMATION info;
  57. info.ThreadId = deb_ev.dwThreadId;
  58. info.ExceptionPointers = &ep;
  59. info.ClientPointers = false;
  60. CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep);
  61. if (CloseHandle(thread_handle) == 0) {
  62. fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError());
  63. }
  64. }
  65. bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) {
  66. std::memset(&pi, 0, sizeof(pi));
  67. // Don't debug if we are already being debugged
  68. if (IsDebuggerPresent()) {
  69. return false;
  70. }
  71. if (!SpawnChild(arg0, &pi, 0)) {
  72. fmt::print(stderr, "warning: continuing without crash dumps");
  73. return false;
  74. }
  75. const bool can_debug = DebugActiveProcess(pi.dwProcessId);
  76. if (!can_debug) {
  77. fmt::print(stderr,
  78. "warning: DebugActiveProcess failed ({}), continuing without crash dumps",
  79. GetLastError());
  80. return false;
  81. }
  82. return true;
  83. }
  84. static const char* ExceptionName(DWORD exception) {
  85. switch (exception) {
  86. case EXCEPTION_ACCESS_VIOLATION:
  87. return "EXCEPTION_ACCESS_VIOLATION";
  88. case EXCEPTION_DATATYPE_MISALIGNMENT:
  89. return "EXCEPTION_DATATYPE_MISALIGNMENT";
  90. case EXCEPTION_BREAKPOINT:
  91. return "EXCEPTION_BREAKPOINT";
  92. case EXCEPTION_SINGLE_STEP:
  93. return "EXCEPTION_SINGLE_STEP";
  94. case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
  95. return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
  96. case EXCEPTION_FLT_DENORMAL_OPERAND:
  97. return "EXCEPTION_FLT_DENORMAL_OPERAND";
  98. case EXCEPTION_FLT_DIVIDE_BY_ZERO:
  99. return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
  100. case EXCEPTION_FLT_INEXACT_RESULT:
  101. return "EXCEPTION_FLT_INEXACT_RESULT";
  102. case EXCEPTION_FLT_INVALID_OPERATION:
  103. return "EXCEPTION_FLT_INVALID_OPERATION";
  104. case EXCEPTION_FLT_OVERFLOW:
  105. return "EXCEPTION_FLT_OVERFLOW";
  106. case EXCEPTION_FLT_STACK_CHECK:
  107. return "EXCEPTION_FLT_STACK_CHECK";
  108. case EXCEPTION_FLT_UNDERFLOW:
  109. return "EXCEPTION_FLT_UNDERFLOW";
  110. case EXCEPTION_INT_DIVIDE_BY_ZERO:
  111. return "EXCEPTION_INT_DIVIDE_BY_ZERO";
  112. case EXCEPTION_INT_OVERFLOW:
  113. return "EXCEPTION_INT_OVERFLOW";
  114. case EXCEPTION_PRIV_INSTRUCTION:
  115. return "EXCEPTION_PRIV_INSTRUCTION";
  116. case EXCEPTION_IN_PAGE_ERROR:
  117. return "EXCEPTION_IN_PAGE_ERROR";
  118. case EXCEPTION_ILLEGAL_INSTRUCTION:
  119. return "EXCEPTION_ILLEGAL_INSTRUCTION";
  120. case EXCEPTION_NONCONTINUABLE_EXCEPTION:
  121. return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
  122. case EXCEPTION_STACK_OVERFLOW:
  123. return "EXCEPTION_STACK_OVERFLOW";
  124. case EXCEPTION_INVALID_DISPOSITION:
  125. return "EXCEPTION_INVALID_DISPOSITION";
  126. case EXCEPTION_GUARD_PAGE:
  127. return "EXCEPTION_GUARD_PAGE";
  128. case EXCEPTION_INVALID_HANDLE:
  129. return "EXCEPTION_INVALID_HANDLE";
  130. default:
  131. return "unknown exception type";
  132. }
  133. }
  134. void DebugDebuggee(PROCESS_INFORMATION& pi) {
  135. DEBUG_EVENT deb_ev = {};
  136. while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) {
  137. const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE);
  138. if (!wait_success) {
  139. fmt::print(stderr, "error: WaitForDebugEvent failed ({})", GetLastError());
  140. return;
  141. }
  142. switch (deb_ev.dwDebugEventCode) {
  143. case OUTPUT_DEBUG_STRING_EVENT:
  144. case CREATE_PROCESS_DEBUG_EVENT:
  145. case CREATE_THREAD_DEBUG_EVENT:
  146. case EXIT_PROCESS_DEBUG_EVENT:
  147. case EXIT_THREAD_DEBUG_EVENT:
  148. case LOAD_DLL_DEBUG_EVENT:
  149. case RIP_EVENT:
  150. case UNLOAD_DLL_DEBUG_EVENT:
  151. // Continue on all other debug events
  152. ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE);
  153. break;
  154. case EXCEPTION_DEBUG_EVENT:
  155. EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
  156. // We want to generate a crash dump if we are seeing the same exception again.
  157. if (!deb_ev.u.Exception.dwFirstChance) {
  158. fmt::print(stderr, "Creating MiniDump on ExceptionCode: 0x{:08x} {}\n",
  159. record.ExceptionCode, ExceptionName(record.ExceptionCode));
  160. DumpFromDebugEvent(deb_ev, pi);
  161. }
  162. // Continue without handling the exception.
  163. // Lets the debuggee use its own exception handler.
  164. // - If one does not exist, we will see the exception once more where we make a minidump
  165. // for. Then when it reaches here again, yuzu will probably crash.
  166. // - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an
  167. // infinite loop of exceptions.
  168. ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
  169. break;
  170. }
  171. }
  172. }
  173. } // namespace MiniDump