diff options
Diffstat (limited to 'contrib/SDL-3.2.8/test/win32/sdlprocdump.c')
| -rw-r--r-- | contrib/SDL-3.2.8/test/win32/sdlprocdump.c | 683 |
1 files changed, 683 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/test/win32/sdlprocdump.c b/contrib/SDL-3.2.8/test/win32/sdlprocdump.c new file mode 100644 index 0000000..13259af --- /dev/null +++ b/contrib/SDL-3.2.8/test/win32/sdlprocdump.c | |||
| @@ -0,0 +1,683 @@ | |||
| 1 | #ifndef WIN32_LEAN_AND_MEAN | ||
| 2 | #define WIN32_LEAN_AND_MEAN | ||
| 3 | #endif | ||
| 4 | |||
| 5 | #include <windows.h> | ||
| 6 | #include <dbghelp.h> | ||
| 7 | |||
| 8 | #include <inttypes.h> | ||
| 9 | #include <stdarg.h> | ||
| 10 | #include <stdio.h> | ||
| 11 | #include <stdlib.h> | ||
| 12 | #include <string.h> | ||
| 13 | |||
| 14 | #define DUMP_FOLDER "minidumps" | ||
| 15 | #define APPNAME "SDLPROCDUMP" | ||
| 16 | |||
| 17 | #define PRODCUMP_MIN(A,B) (((A) < (B)) ? (A) : (B)) | ||
| 18 | |||
| 19 | #if defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) ||defined( __i386) || defined(_M_IX86) | ||
| 20 | #define SDLPROCDUMP_CPU_X86 1 | ||
| 21 | #elif defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) | ||
| 22 | #define SDLPROCDUMP_CPU_X64 1 | ||
| 23 | #elif defined(__aarch64__) || defined(_M_ARM64) | ||
| 24 | #define SDLPROCDUMP_CPU_ARM64 1 | ||
| 25 | #elif defined(__arm__) || defined(_M_ARM) | ||
| 26 | #define SDLPROCDUMP_CPU_ARM32 1 | ||
| 27 | #endif | ||
| 28 | |||
| 29 | #if defined(SDLPROCDUMP_CPU_X86) || defined(SDLPROCDUMP_CPU_X64) || defined(SDLPROCDUMP_CPU_ARM32) || defined(SDLPROCDUMP_CPU_ARM64) | ||
| 30 | #define SDLPROCDUMP_PRINTSTACK | ||
| 31 | #else | ||
| 32 | #pragma message("Unsupported architecture: don't know how to StackWalk") | ||
| 33 | #endif | ||
| 34 | |||
| 35 | #ifndef EXCEPTION_SOFTWARE_ORIGINATE | ||
| 36 | #define EXCEPTION_SOFTWARE_ORIGINATE 0x80 | ||
| 37 | #endif | ||
| 38 | |||
| 39 | static void printf_message(const char *format, ...) { | ||
| 40 | va_list ap; | ||
| 41 | fprintf(stderr, "[" APPNAME "] "); | ||
| 42 | va_start(ap, format); | ||
| 43 | vfprintf(stderr, format, ap); | ||
| 44 | va_end(ap); | ||
| 45 | fprintf(stderr, "\n"); | ||
| 46 | } | ||
| 47 | |||
| 48 | static void printf_windows_message(const char *format, ...) { | ||
| 49 | va_list ap; | ||
| 50 | char win_msg[512]; | ||
| 51 | size_t win_msg_len; | ||
| 52 | |||
| 53 | FormatMessageA( | ||
| 54 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, | ||
| 55 | NULL, | ||
| 56 | GetLastError(), | ||
| 57 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | ||
| 58 | win_msg, sizeof(win_msg)/sizeof(*win_msg), | ||
| 59 | NULL); | ||
| 60 | win_msg_len = strlen(win_msg); | ||
| 61 | while (win_msg[win_msg_len-1] == '\r' || win_msg[win_msg_len-1] == '\n' || win_msg[win_msg_len-1] == ' ') { | ||
| 62 | win_msg[win_msg_len-1] = '\0'; | ||
| 63 | win_msg_len--; | ||
| 64 | } | ||
| 65 | fprintf(stderr, "[" APPNAME "] "); | ||
| 66 | va_start(ap, format); | ||
| 67 | vfprintf(stderr, format, ap); | ||
| 68 | va_end(ap); | ||
| 69 | fprintf(stderr, " (%s)\n", win_msg); | ||
| 70 | } | ||
| 71 | |||
| 72 | struct { | ||
| 73 | HMODULE module; | ||
| 74 | BOOL (WINAPI *pSymInitialize)(HANDLE hProcess, PCSTR UserSearchPath, BOOL fInvadeProcess); | ||
| 75 | BOOL (WINAPI *pSymCleanup)(HANDLE hProcess); | ||
| 76 | BOOL (WINAPI *pMiniDumpWriteDump)( | ||
| 77 | HANDLE hProcess, | ||
| 78 | DWORD ProcessId, | ||
| 79 | HANDLE hFile, | ||
| 80 | MINIDUMP_TYPE DumpType, | ||
| 81 | PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, | ||
| 82 | PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, | ||
| 83 | PMINIDUMP_CALLBACK_INFORMATION CallbackParam); | ||
| 84 | BOOL (WINAPI *pSymFromAddr)(HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, PSYMBOL_INFO Symbol); | ||
| 85 | BOOL (WINAPI *pSymGetLineFromAddr64)(HANDLE hProcess, DWORD64 qwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINE64 Line); | ||
| 86 | BOOL (WINAPI *pStackWalk64)(DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME64 StackFrame, | ||
| 87 | PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, | ||
| 88 | PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, | ||
| 89 | PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress); | ||
| 90 | PVOID (WINAPI *pSymFunctionTableAccess64)(HANDLE hProcess, DWORD64 AddrBase); | ||
| 91 | DWORD64 (WINAPI *pSymGetModuleBase64)(HANDLE hProcess, DWORD64 qwAddr); | ||
| 92 | BOOL (WINAPI *pSymGetModuleInfo64)(HANDLE hProcess, DWORD64 qwAddr, PIMAGEHLP_MODULE64 ModuleInfo); | ||
| 93 | BOOL (WINAPI *pSymRefreshModuleList)(HANDLE hProcess); | ||
| 94 | } dyn_dbghelp; | ||
| 95 | |||
| 96 | static void load_dbghelp(void) { | ||
| 97 | if (dyn_dbghelp.module) { | ||
| 98 | return; | ||
| 99 | } | ||
| 100 | dyn_dbghelp.module = LoadLibraryA("dbghelp.dll"); | ||
| 101 | if (!dyn_dbghelp.module) { | ||
| 102 | printf_message("Failed to load dbghelp.dll"); | ||
| 103 | goto failed; | ||
| 104 | } | ||
| 105 | dyn_dbghelp.pSymInitialize = (void *)GetProcAddress(dyn_dbghelp.module, "SymInitialize"); | ||
| 106 | dyn_dbghelp.pSymCleanup = (void *)GetProcAddress(dyn_dbghelp.module, "SymCleanup"); | ||
| 107 | dyn_dbghelp.pMiniDumpWriteDump = (void *)GetProcAddress(dyn_dbghelp.module, "MiniDumpWriteDump"); | ||
| 108 | dyn_dbghelp.pSymFromAddr = (void *)GetProcAddress(dyn_dbghelp.module, "SymFromAddr"); | ||
| 109 | dyn_dbghelp.pStackWalk64 = (void *)GetProcAddress(dyn_dbghelp.module, "StackWalk64"); | ||
| 110 | dyn_dbghelp.pSymGetLineFromAddr64 = (void *)GetProcAddress(dyn_dbghelp.module, "SymGetLineFromAddr64"); | ||
| 111 | dyn_dbghelp.pSymFunctionTableAccess64 = (void *)GetProcAddress(dyn_dbghelp.module, "SymFunctionTableAccess64"); | ||
| 112 | dyn_dbghelp.pSymGetModuleBase64 = (void *)GetProcAddress(dyn_dbghelp.module, "SymGetModuleBase64"); | ||
| 113 | dyn_dbghelp.pSymGetModuleInfo64 = (void *)GetProcAddress(dyn_dbghelp.module, "SymGetModuleInfo64"); | ||
| 114 | dyn_dbghelp.pSymRefreshModuleList = (void *)GetProcAddress(dyn_dbghelp.module, "SymRefreshModuleList"); | ||
| 115 | return; | ||
| 116 | failed: | ||
| 117 | if (dyn_dbghelp.module) { | ||
| 118 | FreeLibrary(dyn_dbghelp.module); | ||
| 119 | dyn_dbghelp.module = NULL; | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | static void unload_dbghelp(void) { | ||
| 124 | if (!dyn_dbghelp.module) { | ||
| 125 | return; | ||
| 126 | } | ||
| 127 | FreeLibrary(dyn_dbghelp.module); | ||
| 128 | memset(&dyn_dbghelp, 0, sizeof(dyn_dbghelp)); | ||
| 129 | } | ||
| 130 | |||
| 131 | #define FOREACH_EXCEPTION_CODES(X) \ | ||
| 132 | X(EXCEPTION_ACCESS_VIOLATION) \ | ||
| 133 | X(EXCEPTION_DATATYPE_MISALIGNMENT) \ | ||
| 134 | X(EXCEPTION_BREAKPOINT) \ | ||
| 135 | X(EXCEPTION_SINGLE_STEP) \ | ||
| 136 | X(EXCEPTION_ARRAY_BOUNDS_EXCEEDED) \ | ||
| 137 | X(EXCEPTION_FLT_DENORMAL_OPERAND) \ | ||
| 138 | X(EXCEPTION_FLT_DIVIDE_BY_ZERO) \ | ||
| 139 | X(EXCEPTION_FLT_INEXACT_RESULT) \ | ||
| 140 | X(EXCEPTION_FLT_INVALID_OPERATION) \ | ||
| 141 | X(EXCEPTION_FLT_OVERFLOW) \ | ||
| 142 | X(EXCEPTION_FLT_STACK_CHECK) \ | ||
| 143 | X(EXCEPTION_FLT_UNDERFLOW) \ | ||
| 144 | X(EXCEPTION_INT_DIVIDE_BY_ZERO) \ | ||
| 145 | X(EXCEPTION_INT_OVERFLOW) \ | ||
| 146 | X(EXCEPTION_PRIV_INSTRUCTION) \ | ||
| 147 | X(EXCEPTION_IN_PAGE_ERROR) \ | ||
| 148 | X(EXCEPTION_ILLEGAL_INSTRUCTION) \ | ||
| 149 | X(EXCEPTION_NONCONTINUABLE_EXCEPTION) \ | ||
| 150 | X(EXCEPTION_STACK_OVERFLOW) \ | ||
| 151 | X(EXCEPTION_INVALID_DISPOSITION) \ | ||
| 152 | X(EXCEPTION_GUARD_PAGE) \ | ||
| 153 | X(EXCEPTION_INVALID_HANDLE) \ | ||
| 154 | X(STATUS_HEAP_CORRUPTION) | ||
| 155 | |||
| 156 | #define FOREACH_EXCEPTION_FLAGS(X) \ | ||
| 157 | X(EXCEPTION_NONCONTINUABLE) \ | ||
| 158 | X(EXCEPTION_UNWINDING) \ | ||
| 159 | X(EXCEPTION_EXIT_UNWIND) \ | ||
| 160 | X(EXCEPTION_STACK_INVALID) \ | ||
| 161 | X(EXCEPTION_NESTED_CALL) \ | ||
| 162 | X(EXCEPTION_TARGET_UNWIND) \ | ||
| 163 | X(EXCEPTION_COLLIDED_UNWIND) \ | ||
| 164 | X(EXCEPTION_SOFTWARE_ORIGINATE) | ||
| 165 | |||
| 166 | static const char *exceptionCode_to_string(DWORD dwCode) { | ||
| 167 | #define SWITCH_CODE_STR(V) case V: return #V; | ||
| 168 | switch (dwCode) { | ||
| 169 | case 0xe06d7363: return "MS Visual C++ Exception"; | ||
| 170 | FOREACH_EXCEPTION_CODES(SWITCH_CODE_STR) | ||
| 171 | default: { | ||
| 172 | return "unknown"; | ||
| 173 | } | ||
| 174 | } | ||
| 175 | #undef SWITCH_CODE_STR | ||
| 176 | } | ||
| 177 | |||
| 178 | static const char *exceptionFlags_to_string(DWORD dwFlags, char *buffer, size_t buffer_length) { | ||
| 179 | buffer[0] = '\0'; | ||
| 180 | |||
| 181 | #define APPEND_OR_STR(CODE) \ | ||
| 182 | if (dwFlags & (CODE)) { \ | ||
| 183 | if (buffer[0]) { \ | ||
| 184 | strcat_s(buffer, buffer_length, "|"); \ | ||
| 185 | } \ | ||
| 186 | strcat_s(buffer, buffer_length, #CODE); \ | ||
| 187 | } | ||
| 188 | |||
| 189 | FOREACH_EXCEPTION_FLAGS(APPEND_OR_STR) | ||
| 190 | #undef APPEND_OR_STR | ||
| 191 | return buffer; | ||
| 192 | } | ||
| 193 | |||
| 194 | static BOOL IsCXXException(DWORD dwCode) { | ||
| 195 | /* https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273 */ | ||
| 196 | return dwCode == 0xe06d7363; /* FOURCC(0xe0, 'm', 's', 'c') */ | ||
| 197 | } | ||
| 198 | |||
| 199 | static BOOL IsFatalExceptionCode(DWORD dwCode) { | ||
| 200 | switch (dwCode) { | ||
| 201 | case EXCEPTION_ACCESS_VIOLATION: | ||
| 202 | case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: | ||
| 203 | case EXCEPTION_IN_PAGE_ERROR: | ||
| 204 | case EXCEPTION_ILLEGAL_INSTRUCTION: | ||
| 205 | case EXCEPTION_INT_DIVIDE_BY_ZERO: | ||
| 206 | case EXCEPTION_STACK_OVERFLOW: | ||
| 207 | case STATUS_HEAP_CORRUPTION: | ||
| 208 | case STATUS_STACK_BUFFER_OVERRUN: | ||
| 209 | case EXCEPTION_GUARD_PAGE: | ||
| 210 | case EXCEPTION_INVALID_HANDLE: | ||
| 211 | return TRUE; | ||
| 212 | default: | ||
| 213 | return FALSE; | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | static const char *get_simple_basename(const char *path) { | ||
| 218 | const char *pos = strrchr(path, '\\'); | ||
| 219 | if (pos) { | ||
| 220 | return pos + 1; | ||
| 221 | } | ||
| 222 | pos = strrchr(path, '/'); | ||
| 223 | if (pos) { | ||
| 224 | return pos + 1; | ||
| 225 | } | ||
| 226 | return path; | ||
| 227 | } | ||
| 228 | |||
| 229 | static void write_minidump(const char *child_file_path, const LPPROCESS_INFORMATION process_information, DWORD dwThreadId, PEXCEPTION_RECORD exception_record, PCONTEXT context) { | ||
| 230 | BOOL success; | ||
| 231 | char dump_file_path[MAX_PATH]; | ||
| 232 | char child_file_name[64]; | ||
| 233 | EXCEPTION_POINTERS exception_pointers; | ||
| 234 | HANDLE hFile = INVALID_HANDLE_VALUE; | ||
| 235 | MINIDUMP_EXCEPTION_INFORMATION minidump_exception_information; | ||
| 236 | SYSTEMTIME system_time; | ||
| 237 | |||
| 238 | if (!dyn_dbghelp.pMiniDumpWriteDump) { | ||
| 239 | printf_message("Cannot find pMiniDumpWriteDump in dbghelp.dll: no minidump"); | ||
| 240 | return; | ||
| 241 | } | ||
| 242 | |||
| 243 | success = CreateDirectoryA(DUMP_FOLDER, NULL); | ||
| 244 | if (!success && GetLastError() != ERROR_ALREADY_EXISTS) { | ||
| 245 | printf_windows_message("Failed to create minidump directory"); | ||
| 246 | goto post_dump; | ||
| 247 | } | ||
| 248 | _splitpath_s(child_file_path, NULL, 0, NULL, 0, child_file_name, sizeof(child_file_name), NULL, 0); | ||
| 249 | GetLocalTime(&system_time); | ||
| 250 | |||
| 251 | snprintf(dump_file_path, sizeof(dump_file_path), "minidumps/%s_%04d-%02d-%02d_%02d-%02d-%02d.dmp", | ||
| 252 | child_file_name, | ||
| 253 | system_time.wYear, system_time.wMonth, system_time.wDay, | ||
| 254 | system_time.wHour, system_time.wMinute, system_time.wSecond); | ||
| 255 | printf_message(""); | ||
| 256 | printf_message("Writing minidump to \"%s\"", dump_file_path); | ||
| 257 | hFile = CreateFileA( | ||
| 258 | dump_file_path, | ||
| 259 | GENERIC_WRITE, | ||
| 260 | FILE_SHARE_WRITE, | ||
| 261 | NULL, | ||
| 262 | CREATE_ALWAYS, | ||
| 263 | FILE_ATTRIBUTE_NORMAL, | ||
| 264 | NULL); | ||
| 265 | if (hFile == INVALID_HANDLE_VALUE) { | ||
| 266 | printf_windows_message("Failed to open file for minidump"); | ||
| 267 | goto post_dump; | ||
| 268 | } | ||
| 269 | memset(&exception_pointers, 0, sizeof(exception_pointers)); | ||
| 270 | exception_pointers.ContextRecord = context; | ||
| 271 | exception_pointers.ExceptionRecord = exception_record; | ||
| 272 | minidump_exception_information.ClientPointers = FALSE; | ||
| 273 | minidump_exception_information.ExceptionPointers = &exception_pointers; | ||
| 274 | minidump_exception_information.ThreadId = dwThreadId; | ||
| 275 | success = dyn_dbghelp.pMiniDumpWriteDump( | ||
| 276 | process_information->hProcess, /* HANDLE hProcess */ | ||
| 277 | process_information->dwProcessId, /* DWORD ProcessId */ | ||
| 278 | hFile, /* HANDLE hFile */ | ||
| 279 | MiniDumpWithFullMemory, /* MINIDUMP_TYPE DumpType */ | ||
| 280 | &minidump_exception_information, /* PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam */ | ||
| 281 | NULL, /* PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam */ | ||
| 282 | NULL); /* PMINIDUMP_CALLBACK_INFORMATION CallbackParam */ | ||
| 283 | if (!success) { | ||
| 284 | printf_windows_message("Failed to write minidump"); | ||
| 285 | } | ||
| 286 | post_dump: | ||
| 287 | if (hFile != INVALID_HANDLE_VALUE) { | ||
| 288 | CloseHandle(hFile); | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | static void print_stacktrace(const LPPROCESS_INFORMATION process_information, LPVOID address, PCONTEXT context) { | ||
| 293 | STACKFRAME64 stack_frame; | ||
| 294 | DWORD machine_type; | ||
| 295 | |||
| 296 | if (!context) { | ||
| 297 | printf_message("Cannot create a stacktrace without a context"); | ||
| 298 | return; | ||
| 299 | } | ||
| 300 | if (!dyn_dbghelp.pStackWalk64) { | ||
| 301 | printf_message("Cannot find StackWalk64 in dbghelp.dll: no stacktrace"); | ||
| 302 | return; | ||
| 303 | } | ||
| 304 | if (!dyn_dbghelp.pSymFunctionTableAccess64) { | ||
| 305 | printf_message("Cannot find SymFunctionTableAccess64 in dbghelp.dll: no stacktrace"); | ||
| 306 | return; | ||
| 307 | } | ||
| 308 | if (!dyn_dbghelp.pSymGetModuleBase64) { | ||
| 309 | printf_message("Cannot find SymGetModuleBase64 in dbghelp.dll: no stacktrace"); | ||
| 310 | return; | ||
| 311 | } | ||
| 312 | if (!dyn_dbghelp.pSymFromAddr) { | ||
| 313 | printf_message("Cannot find pSymFromAddr in dbghelp.dll: no stacktrace"); | ||
| 314 | return; | ||
| 315 | } | ||
| 316 | if (!dyn_dbghelp.pSymGetLineFromAddr64) { | ||
| 317 | printf_message("Cannot find SymGetLineFromAddr64 in dbghelp.dll: no stacktrace"); | ||
| 318 | return; | ||
| 319 | } | ||
| 320 | if (!dyn_dbghelp.pSymGetModuleInfo64) { | ||
| 321 | printf_message("Cannot find SymGetModuleInfo64 in dbghelp.dll: no stacktrace"); | ||
| 322 | return; | ||
| 323 | } | ||
| 324 | |||
| 325 | if (!dyn_dbghelp.pSymRefreshModuleList || !dyn_dbghelp.pSymRefreshModuleList(process_information->hProcess)) { | ||
| 326 | printf_windows_message("SymRefreshModuleList failed: maybe no stacktrace"); | ||
| 327 | } | ||
| 328 | |||
| 329 | memset(&stack_frame, 0, sizeof(stack_frame)); | ||
| 330 | |||
| 331 | stack_frame.AddrPC.Mode = AddrModeFlat; | ||
| 332 | stack_frame.AddrFrame.Mode = AddrModeFlat; | ||
| 333 | stack_frame.AddrStack.Mode = AddrModeFlat; | ||
| 334 | |||
| 335 | #if defined(SDLPROCDUMP_CPU_X86) | ||
| 336 | machine_type = IMAGE_FILE_MACHINE_I386; | ||
| 337 | stack_frame.AddrFrame.Offset = context->Ebp; | ||
| 338 | stack_frame.AddrStack.Offset = context->Esp; | ||
| 339 | stack_frame.AddrPC.Offset = context->Eip; | ||
| 340 | #elif defined(SDLPROCDUMP_CPU_X64) | ||
| 341 | machine_type = IMAGE_FILE_MACHINE_AMD64; | ||
| 342 | stack_frame.AddrFrame.Offset = context->Rbp; | ||
| 343 | stack_frame.AddrStack.Offset = context->Rsp; | ||
| 344 | stack_frame.AddrPC.Offset = context->Rip; | ||
| 345 | #elif defined(SDLPROCDUMP_CPU_ARM32) | ||
| 346 | machine_type = IMAGE_FILE_MACHINE_ARM; | ||
| 347 | stack_frame.AddrFrame.Offset = context->Lr; | ||
| 348 | stack_frame.AddrStack.Offset = context->Sp; | ||
| 349 | stack_frame.AddrPC.Offset = context->Pc; | ||
| 350 | #elif defined(SDLPROCDUMP_CPU_ARM64) | ||
| 351 | machine_type = IMAGE_FILE_MACHINE_ARM64; | ||
| 352 | stack_frame.AddrFrame.Offset = context->Fp; | ||
| 353 | stack_frame.AddrStack.Offset = context->Sp; | ||
| 354 | stack_frame.AddrPC.Offset = context->Pc; | ||
| 355 | #endif | ||
| 356 | while (dyn_dbghelp.pStackWalk64(machine_type, /* DWORD MachineType */ | ||
| 357 | process_information->hProcess, /* HANDLE hProcess */ | ||
| 358 | process_information->hThread, /* HANDLE hThread */ | ||
| 359 | &stack_frame, /* LPSTACKFRAME64 StackFrame */ | ||
| 360 | context, /* PVOID ContextRecord */ | ||
| 361 | NULL, /* PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine */ | ||
| 362 | dyn_dbghelp.pSymFunctionTableAccess64, /* PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine */ | ||
| 363 | dyn_dbghelp.pSymGetModuleBase64, /* PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine */ | ||
| 364 | NULL)) { /* PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress */ | ||
| 365 | IMAGEHLP_MODULE64 module_info; | ||
| 366 | union { | ||
| 367 | char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(CHAR)]; | ||
| 368 | SYMBOL_INFO symbol_info; | ||
| 369 | } symbol; | ||
| 370 | DWORD64 dwDisplacement; | ||
| 371 | DWORD lineColumn = 0; | ||
| 372 | IMAGEHLP_LINE64 line; | ||
| 373 | const char *image_file_name; | ||
| 374 | const char *symbol_name; | ||
| 375 | const char *file_name; | ||
| 376 | char line_number[16]; | ||
| 377 | |||
| 378 | if (stack_frame.AddrPC.Offset == stack_frame.AddrReturn.Offset) { | ||
| 379 | printf_message("PC == Return Address => Possible endless callstack"); | ||
| 380 | break; | ||
| 381 | } | ||
| 382 | |||
| 383 | memset(&module_info, 0, sizeof(module_info)); | ||
| 384 | module_info.SizeOfStruct = sizeof(module_info); | ||
| 385 | if (!dyn_dbghelp.pSymGetModuleInfo64(process_information->hProcess, stack_frame.AddrPC.Offset, &module_info)) { | ||
| 386 | image_file_name = "?"; | ||
| 387 | } else { | ||
| 388 | image_file_name = get_simple_basename(module_info.ImageName); | ||
| 389 | } | ||
| 390 | |||
| 391 | memset(&symbol, 0, sizeof(symbol)); | ||
| 392 | symbol.symbol_info.SizeOfStruct = sizeof(symbol.symbol_info); | ||
| 393 | symbol.symbol_info.MaxNameLen = MAX_SYM_NAME; | ||
| 394 | if (!dyn_dbghelp.pSymFromAddr(process_information->hProcess, (DWORD64)(uintptr_t)stack_frame.AddrPC.Offset, &dwDisplacement, &symbol.symbol_info)) { | ||
| 395 | symbol_name = "???"; | ||
| 396 | dwDisplacement = 0; | ||
| 397 | } else { | ||
| 398 | symbol_name = symbol.symbol_info.Name; | ||
| 399 | } | ||
| 400 | |||
| 401 | line.SizeOfStruct = sizeof(line); | ||
| 402 | if (!dyn_dbghelp.pSymGetLineFromAddr64(process_information->hProcess, (DWORD64)(uintptr_t)stack_frame.AddrPC.Offset, &lineColumn, &line)) { | ||
| 403 | file_name = ""; | ||
| 404 | line_number[0] = '\0'; | ||
| 405 | } else { | ||
| 406 | file_name = line.FileName; | ||
| 407 | snprintf(line_number, sizeof(line_number), "Line %u", (unsigned int)line.LineNumber); | ||
| 408 | } | ||
| 409 | printf_message("%s!%s+0x%x %s %s", image_file_name, symbol_name, dwDisplacement, file_name, line_number); | ||
| 410 | } | ||
| 411 | } | ||
| 412 | |||
| 413 | static PCONTEXT FillInThreadContext(LPPROCESS_INFORMATION process_information, PCONTEXT context_buffer) { | ||
| 414 | HANDLE thread_handle = NULL; | ||
| 415 | |||
| 416 | thread_handle = OpenThread(THREAD_ALL_ACCESS, FALSE, process_information->dwThreadId); | ||
| 417 | if (!thread_handle) { | ||
| 418 | printf_windows_message("OpenThread failed: no stacktrace"); | ||
| 419 | return NULL; | ||
| 420 | } | ||
| 421 | |||
| 422 | memset(context_buffer, 0, sizeof(*context_buffer)); | ||
| 423 | context_buffer->ContextFlags = CONTEXT_ALL; | ||
| 424 | if (!GetThreadContext(thread_handle, context_buffer)) { | ||
| 425 | printf_windows_message("GetThreadContext failed: no stacktrace"); | ||
| 426 | CloseHandle(thread_handle); | ||
| 427 | return NULL; | ||
| 428 | } | ||
| 429 | CloseHandle(thread_handle); | ||
| 430 | return context_buffer; | ||
| 431 | } | ||
| 432 | |||
| 433 | static void GetMSCExceptionName(HANDLE hProcess, ULONG_PTR *parameters, DWORD count_parameters, char *buffer, size_t buffer_size) { | ||
| 434 | |||
| 435 | #define FIXUP_DWORD_POINTER(ADDR) ((sizeof(void *) == 8) ? (parameters[3] + (ADDR)) : (ADDR)) | ||
| 436 | #define CHECKED_ReadProcessMemory(PROCESS, ADDRESS, BUFFER, COUNT, WHAT) \ | ||
| 437 | do { \ | ||
| 438 | SIZE_T actual_count; \ | ||
| 439 | BOOL res = ReadProcessMemory((PROCESS), (ADDRESS), (BUFFER), (COUNT), &actual_count); \ | ||
| 440 | if (!res) { \ | ||
| 441 | printf_windows_message(WHAT ": ReadProcessMemory failed"); \ | ||
| 442 | strncpy_s(buffer, buffer_size, "<error>", buffer_size); \ | ||
| 443 | return; \ | ||
| 444 | } \ | ||
| 445 | if ((COUNT) != (actual_count)) { \ | ||
| 446 | printf_message(WHAT ": ReadProcessMemory did not read enough data actual=%lu expected=%lu", \ | ||
| 447 | (unsigned long) (actual_count), (unsigned long) (COUNT)); \ | ||
| 448 | strncpy_s(buffer, buffer_size, "<error>", buffer_size); \ | ||
| 449 | return; \ | ||
| 450 | } \ | ||
| 451 | } while (0) | ||
| 452 | |||
| 453 | DWORD depth0; | ||
| 454 | char *ptr_depth0; | ||
| 455 | DWORD depth1; | ||
| 456 | char *ptr_depth1; | ||
| 457 | DWORD depth2; | ||
| 458 | char *ptr_depth2; | ||
| 459 | |||
| 460 | CHECKED_ReadProcessMemory(hProcess, (void *)(parameters[2] + 3 * sizeof(DWORD)), &depth0, sizeof(depth0), "depth 0"); | ||
| 461 | ptr_depth0 = (char *)FIXUP_DWORD_POINTER(depth0); | ||
| 462 | CHECKED_ReadProcessMemory(hProcess, ptr_depth0 + 1 * sizeof(DWORD), &depth1, sizeof(depth1), "depth 1"); | ||
| 463 | ptr_depth1 = (char *)FIXUP_DWORD_POINTER(depth1); | ||
| 464 | CHECKED_ReadProcessMemory(hProcess, ptr_depth1 + 1 * sizeof(DWORD), &depth2, sizeof(depth2), "depth 2"); | ||
| 465 | ptr_depth2 = (char *)FIXUP_DWORD_POINTER(depth2); | ||
| 466 | CHECKED_ReadProcessMemory(hProcess, ptr_depth2 + 2 * sizeof(void*), buffer, buffer_size, "data"); | ||
| 467 | buffer[buffer_size - 1] = '\0'; | ||
| 468 | |||
| 469 | #undef FIXUP_DWORD_POINTER | ||
| 470 | #undef CHECKED_ReadProcessMemory | ||
| 471 | } | ||
| 472 | |||
| 473 | static void log_usage(const char *argv0) { | ||
| 474 | fprintf(stderr, "Usage: %s [--help] [--debug-stream] [--] PROGRAM [ARG1 [ARG2 [ARG3 ... ]]]\n", argv0); | ||
| 475 | } | ||
| 476 | |||
| 477 | int main(int argc, char *argv[]) { | ||
| 478 | int i; | ||
| 479 | int cmd_start; | ||
| 480 | size_t command_line_len = 0; | ||
| 481 | char *command_line; | ||
| 482 | STARTUPINFOA startup_info; | ||
| 483 | PROCESS_INFORMATION process_information; | ||
| 484 | BOOL success; | ||
| 485 | BOOL debugger_present; | ||
| 486 | DWORD exit_code; | ||
| 487 | DWORD creation_flags; | ||
| 488 | BOOL log_debug_stream = FALSE; | ||
| 489 | |||
| 490 | cmd_start = -1; | ||
| 491 | for (i = 1; i < argc; i++) { | ||
| 492 | if (strcmp(argv[i], "--") == 0) { | ||
| 493 | cmd_start = i + 1; | ||
| 494 | break; | ||
| 495 | } else if (strcmp(argv[i], "--debug-stream") == 0) { | ||
| 496 | log_debug_stream = TRUE; | ||
| 497 | continue; | ||
| 498 | } else if (strcmp(argv[i], "--help") == 0) { | ||
| 499 | log_usage(argv[0]); | ||
| 500 | return 0; | ||
| 501 | } else { | ||
| 502 | cmd_start = i; | ||
| 503 | break; | ||
| 504 | } | ||
| 505 | } | ||
| 506 | if (cmd_start < 0 || cmd_start >= argc) { | ||
| 507 | log_usage(argv[0]); | ||
| 508 | return 1; | ||
| 509 | } | ||
| 510 | |||
| 511 | for (i = cmd_start; i < argc; i++) { | ||
| 512 | command_line_len += strlen(argv[i]) + 1; | ||
| 513 | } | ||
| 514 | command_line = malloc(command_line_len + 1); | ||
| 515 | if (!command_line) { | ||
| 516 | printf_message("Failed to allocate memory for command line"); | ||
| 517 | return 1; | ||
| 518 | } | ||
| 519 | command_line[0] = '\0'; | ||
| 520 | for (i = cmd_start; i < argc; i++) { | ||
| 521 | strcat_s(command_line, command_line_len, argv[i]); | ||
| 522 | if (i != argc - 1) { | ||
| 523 | strcat_s(command_line, command_line_len, " "); | ||
| 524 | } | ||
| 525 | } | ||
| 526 | |||
| 527 | memset(&startup_info, 0, sizeof(startup_info)); | ||
| 528 | startup_info.cb = sizeof(startup_info); | ||
| 529 | |||
| 530 | debugger_present = IsDebuggerPresent(); | ||
| 531 | creation_flags = NORMAL_PRIORITY_CLASS; | ||
| 532 | if (!debugger_present) { | ||
| 533 | creation_flags |= DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS; | ||
| 534 | } | ||
| 535 | success = CreateProcessA( | ||
| 536 | argv[cmd_start], /* LPCSTR lpApplicationName, */ | ||
| 537 | command_line, /* LPSTR lpCommandLine, */ | ||
| 538 | NULL, /* LPSECURITY_ATTRIBUTES lpProcessAttributes, */ | ||
| 539 | NULL, /* LPSECURITY_ATTRIBUTES lpThreadAttributes, */ | ||
| 540 | TRUE, /* BOOL bInheritHandles, */ | ||
| 541 | creation_flags, /* DWORD dwCreationFlags, */ | ||
| 542 | NULL, /* LPVOID lpEnvironment, */ | ||
| 543 | NULL, /* LPCSTR lpCurrentDirectory, */ | ||
| 544 | &startup_info, /* LPSTARTUPINFOA lpStartupInfo, */ | ||
| 545 | &process_information); /* LPPROCESS_INFORMATION lpProcessInformation */ | ||
| 546 | |||
| 547 | if (!success) { | ||
| 548 | printf_windows_message("Failed to start application \"%s\"", argv[cmd_start]); | ||
| 549 | return 1; | ||
| 550 | } | ||
| 551 | |||
| 552 | if (debugger_present) { | ||
| 553 | WaitForSingleObject(process_information.hProcess, INFINITE); | ||
| 554 | } else { | ||
| 555 | int process_alive = 1; | ||
| 556 | DEBUG_EVENT event; | ||
| 557 | while (process_alive) { | ||
| 558 | DWORD continue_status = DBG_CONTINUE; | ||
| 559 | success = WaitForDebugEvent(&event, INFINITE); | ||
| 560 | if (!success) { | ||
| 561 | printf_windows_message("Failed to get a debug event"); | ||
| 562 | return 1; | ||
| 563 | } | ||
| 564 | switch (event.dwDebugEventCode) { | ||
| 565 | case OUTPUT_DEBUG_STRING_EVENT: | ||
| 566 | { | ||
| 567 | if (log_debug_stream) { | ||
| 568 | SIZE_T bytes_read = 0; | ||
| 569 | union { | ||
| 570 | char char_buffer[512]; | ||
| 571 | WCHAR wchar_buffer[256]; | ||
| 572 | } buffer; | ||
| 573 | if (ReadProcessMemory(process_information.hProcess, event.u.DebugString.lpDebugStringData, buffer.char_buffer, PRODCUMP_MIN(sizeof(buffer), event.u.DebugString.nDebugStringLength), &bytes_read) && bytes_read) { | ||
| 574 | if (event.u.DebugString.fUnicode) { | ||
| 575 | size_t len = bytes_read / 2; | ||
| 576 | buffer.wchar_buffer[255] = '\0'; | ||
| 577 | while (len > 0 && (buffer.wchar_buffer[len - 1] == '\0' || buffer.wchar_buffer[len - 1] == '\n' || buffer.wchar_buffer[len - 1] == '\r')) { | ||
| 578 | buffer.wchar_buffer[len - 1] = '\0'; | ||
| 579 | len -= 1; | ||
| 580 | } | ||
| 581 | if (len > 0) { | ||
| 582 | printf("[" APPNAME "] (debug) %S\n", buffer.wchar_buffer); | ||
| 583 | } | ||
| 584 | } else { | ||
| 585 | size_t len = bytes_read; | ||
| 586 | buffer.char_buffer[511] = '\0'; | ||
| 587 | while (len > 0 && (buffer.char_buffer[len - 1] == '\0' || buffer.char_buffer[len - 1] == '\n' || buffer.char_buffer[len - 1] == '\r')) { | ||
| 588 | buffer.char_buffer[len - 1] = '\0'; | ||
| 589 | len -= 1; | ||
| 590 | } | ||
| 591 | if (len > 0) { | ||
| 592 | printf("[" APPNAME "] (debug) %s\n", buffer.char_buffer); | ||
| 593 | } | ||
| 594 | } | ||
| 595 | } | ||
| 596 | } | ||
| 597 | break; | ||
| 598 | } | ||
| 599 | case EXCEPTION_DEBUG_EVENT: | ||
| 600 | { | ||
| 601 | const BOOL cxx_exception = IsCXXException(event.u.Exception.ExceptionRecord.ExceptionCode); | ||
| 602 | const BOOL is_fatal = !cxx_exception && (IsFatalExceptionCode(event.u.Exception.ExceptionRecord.ExceptionCode) || (event.u.Exception.ExceptionRecord.ExceptionFlags & EXCEPTION_NONCONTINUABLE)); | ||
| 603 | if (cxx_exception || is_fatal) { | ||
| 604 | char flag_buffer[256]; | ||
| 605 | printf_message("EXCEPTION_DEBUG_EVENT"); | ||
| 606 | printf_message(" ExceptionCode: 0x%08lx (%s)", | ||
| 607 | event.u.Exception.ExceptionRecord.ExceptionCode, | ||
| 608 | exceptionCode_to_string(event.u.Exception.ExceptionRecord.ExceptionCode)); | ||
| 609 | printf_message(" ExceptionFlags: 0x%08lx (%s)", | ||
| 610 | event.u.Exception.ExceptionRecord.ExceptionFlags, | ||
| 611 | exceptionFlags_to_string(event.u.Exception.ExceptionRecord.ExceptionFlags, flag_buffer, sizeof(flag_buffer))); | ||
| 612 | |||
| 613 | printf_message(" FirstChance: %ld", event.u.Exception.dwFirstChance); | ||
| 614 | printf_message(" ExceptionAddress: 0x%08lx", | ||
| 615 | event.u.Exception.ExceptionRecord.ExceptionAddress); | ||
| 616 | } | ||
| 617 | if (cxx_exception) { | ||
| 618 | char exception_name[256]; | ||
| 619 | GetMSCExceptionName(process_information.hProcess, event.u.Exception.ExceptionRecord.ExceptionInformation, event.u.Exception.ExceptionRecord.NumberParameters, | ||
| 620 | exception_name, sizeof(exception_name)); | ||
| 621 | printf_message(" Exception name: %s", exception_name); | ||
| 622 | } else if (is_fatal) { | ||
| 623 | CONTEXT context_buffer; | ||
| 624 | PCONTEXT context; | ||
| 625 | |||
| 626 | printf_message(" (Non-continuable exception debug event)"); | ||
| 627 | context = FillInThreadContext(&process_information, &context_buffer); | ||
| 628 | write_minidump(argv[cmd_start], &process_information, event.dwThreadId, &event.u.Exception.ExceptionRecord, context); | ||
| 629 | printf_message(""); | ||
| 630 | #ifdef SDLPROCDUMP_PRINTSTACK | ||
| 631 | print_stacktrace(&process_information, event.u.Exception.ExceptionRecord.ExceptionAddress, context); | ||
| 632 | #else | ||
| 633 | printf_message("No support for printing stacktrack for current architecture"); | ||
| 634 | #endif | ||
| 635 | DebugActiveProcessStop(event.dwProcessId); | ||
| 636 | process_alive = FALSE; | ||
| 637 | } | ||
| 638 | continue_status = DBG_EXCEPTION_NOT_HANDLED; | ||
| 639 | break; | ||
| 640 | } | ||
| 641 | case CREATE_PROCESS_DEBUG_EVENT: | ||
| 642 | load_dbghelp(); | ||
| 643 | if (!dyn_dbghelp.pSymInitialize) { | ||
| 644 | printf_message("Cannot find pSymInitialize in dbghelp.dll: no stacktrace"); | ||
| 645 | break; | ||
| 646 | } | ||
| 647 | /* Don't invade process on CI: downloading symbols will cause test timeouts */ | ||
| 648 | if (!dyn_dbghelp.pSymInitialize(process_information.hProcess, NULL, FALSE)) { | ||
| 649 | printf_windows_message("SymInitialize failed: no stacktrace"); | ||
| 650 | break; | ||
| 651 | } | ||
| 652 | break; | ||
| 653 | case EXIT_PROCESS_DEBUG_EVENT: | ||
| 654 | if (event.dwProcessId == process_information.dwProcessId) { | ||
| 655 | process_alive = 0; | ||
| 656 | DebugActiveProcessStop(event.dwProcessId); | ||
| 657 | } | ||
| 658 | break; | ||
| 659 | } | ||
| 660 | success = ContinueDebugEvent(event.dwProcessId, event.dwThreadId, continue_status); | ||
| 661 | if (!process_alive) { | ||
| 662 | DebugActiveProcessStop(event.dwProcessId); | ||
| 663 | } | ||
| 664 | } | ||
| 665 | } | ||
| 666 | if (dyn_dbghelp.pSymCleanup) { | ||
| 667 | dyn_dbghelp.pSymCleanup(process_information.hProcess); | ||
| 668 | } | ||
| 669 | unload_dbghelp(); | ||
| 670 | |||
| 671 | exit_code = 1; | ||
| 672 | success = GetExitCodeProcess(process_information.hProcess, &exit_code); | ||
| 673 | |||
| 674 | if (!success) { | ||
| 675 | printf_message("Failed to get process exit code"); | ||
| 676 | return 1; | ||
| 677 | } | ||
| 678 | |||
| 679 | CloseHandle(process_information.hThread); | ||
| 680 | CloseHandle(process_information.hProcess); | ||
| 681 | |||
| 682 | return exit_code; | ||
| 683 | } | ||
