1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 1998-2000, Matthes Bender
5 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18 // Crash handler, Win32 version
19
20 #include "C4Include.h"
21
22 #ifdef HAVE_DBGHELP
23
24 // Dump generation on crash
25 #include "C4Version.h"
26 #include "platform/C4windowswrapper.h"
27 #include <dbghelp.h>
28 #include <tlhelp32.h>
29 #include <cinttypes>
30 #if defined(__CRT_WIDE) || (defined(_MSC_VER) && _MSC_VER >= 1900)
31 #define USE_WIDE_ASSERT
32 #endif
33
34 static bool FirstCrash = true;
35
36 namespace {
37 #define OC_MACHINE_UNKNOWN 0x0
38 #define OC_MACHINE_X86 0x1
39 #define OC_MACHINE_X64 0x2
40 #if defined(_M_X64) || defined(__amd64)
41 # define OC_MACHINE OC_MACHINE_X64
42 #elif defined(_M_IX86) || defined(__i386__)
43 # define OC_MACHINE OC_MACHINE_X86
44 #else
45 # define OC_MACHINE OC_MACHINE_UNKNOWN
46 #endif
47
48 const size_t DumpBufferSize = 2048;
49 char DumpBuffer[DumpBufferSize];
50 char SymbolBuffer[DumpBufferSize];
51 // Dump crash info in a human readable format. Uses a static buffer to avoid heap allocations
52 // from an exception handler. For the same reason, this also doesn't use Log/LogF etc.
SafeTextDump(LPEXCEPTION_POINTERS exc,int fd,const wchar_t * dump_filename)53 void SafeTextDump(LPEXCEPTION_POINTERS exc, int fd, const wchar_t *dump_filename)
54 {
55 #if defined(_MSC_VER)
56 # define LOG_SNPRINTF _snprintf
57 #else
58 # define LOG_SNPRINTF snprintf
59 #endif
60 #define LOG_STATIC_TEXT(text) write(fd, text, sizeof(text) - 1)
61 #define LOG_DYNAMIC_TEXT(...) write(fd, DumpBuffer, LOG_SNPRINTF(DumpBuffer, DumpBufferSize-1, __VA_ARGS__))
62
63 // Figure out which kind of format string will output a pointer in hex
64 #if defined(PRIdPTR)
65 # define POINTER_FORMAT_SUFFIX PRIdPTR
66 #elif defined(_MSC_VER)
67 # define POINTER_FORMAT_SUFFIX "Ix"
68 #elif defined(__GNUC__)
69 # define POINTER_FORMAT_SUFFIX "zx"
70 #else
71 # define POINTER_FORMAT_SUFFIX "p"
72 #endif
73 #if OC_MACHINE == OC_MACHINE_X64
74 # define POINTER_FORMAT "0x%016" POINTER_FORMAT_SUFFIX
75 #elif OC_MACHINE == OC_MACHINE_X86
76 # define POINTER_FORMAT "0x%08" POINTER_FORMAT_SUFFIX
77 #else
78 # define POINTER_FORMAT "0x%" POINTER_FORMAT_SUFFIX
79 #endif
80
81 #ifndef STATUS_ASSERTION_FAILURE
82 # define STATUS_ASSERTION_FAILURE ((DWORD)0xC0000420L)
83 #endif
84
85 LOG_STATIC_TEXT("**********************************************************************\n");
86 LOG_STATIC_TEXT("* UNHANDLED EXCEPTION\n");
87 if (OC_BUILD_ID[0] != '\0')
88 LOG_STATIC_TEXT("* Build Identifier: " OC_BUILD_ID "\n");
89 if (exc->ExceptionRecord->ExceptionCode != STATUS_ASSERTION_FAILURE && dump_filename && dump_filename[0] != L'\0')
90 {
91 int cch = WideCharToMultiByte(CP_UTF8, 0, dump_filename, -1, SymbolBuffer, sizeof(SymbolBuffer), nullptr, nullptr);
92 if (cch > 0)
93 {
94 LOG_STATIC_TEXT("* A crash dump may have been written to ");
95 write(fd, SymbolBuffer, cch - 1);
96 LOG_STATIC_TEXT("\n");
97 LOG_STATIC_TEXT("* If this file exists, please send it to a developer for investigation.\n");
98 }
99 }
100 LOG_STATIC_TEXT("**********************************************************************\n");
101 // Log exception type
102 switch (exc->ExceptionRecord->ExceptionCode)
103 {
104 #define LOG_EXCEPTION(code, text) case code: LOG_STATIC_TEXT(#code ": " text "\n"); break
105 LOG_EXCEPTION(EXCEPTION_ACCESS_VIOLATION, "The thread tried to read from or write to a virtual address for which it does not have the appropriate access.");
106 LOG_EXCEPTION(EXCEPTION_ILLEGAL_INSTRUCTION, "The thread tried to execute an invalid instruction.");
107 LOG_EXCEPTION(EXCEPTION_IN_PAGE_ERROR, "The thread tried to access a page that was not present, and the system was unable to load the page.");
108 LOG_EXCEPTION(EXCEPTION_NONCONTINUABLE_EXCEPTION, "The thread tried to continue execution after a noncontinuable exception occurred.");
109 LOG_EXCEPTION(EXCEPTION_PRIV_INSTRUCTION, "The thread tried to execute an instruction whose operation is not allowed in the current machine mode.");
110 LOG_EXCEPTION(EXCEPTION_STACK_OVERFLOW, "The thread used up its stack.");
111 LOG_EXCEPTION(EXCEPTION_GUARD_PAGE, "The thread accessed memory allocated with the PAGE_GUARD modifier.");
112 LOG_EXCEPTION(STATUS_ASSERTION_FAILURE, "The thread specified a pre- or postcondition that did not hold.");
113 #undef LOG_EXCEPTION
114 default:
115 LOG_DYNAMIC_TEXT("%#08x: The thread raised an unknown exception.\n", static_cast<unsigned int>(exc->ExceptionRecord->ExceptionCode));
116 break;
117 }
118 if (exc->ExceptionRecord->ExceptionFlags == EXCEPTION_NONCONTINUABLE)
119 LOG_STATIC_TEXT("This is a non-continuable exception.\n");
120 else
121 LOG_STATIC_TEXT("This is a continuable exception.\n");
122
123 // For some exceptions, there is a defined meaning to the ExceptionInformation field
124 switch (exc->ExceptionRecord->ExceptionCode)
125 {
126 case EXCEPTION_ACCESS_VIOLATION:
127 case EXCEPTION_IN_PAGE_ERROR:
128 if (exc->ExceptionRecord->NumberParameters < 2)
129 {
130 LOG_STATIC_TEXT("Additional information for the exception was not provided.\n");
131 break;
132 }
133 LOG_STATIC_TEXT("Additional information for the exception: The thread ");
134 switch (exc->ExceptionRecord->ExceptionInformation[0])
135 {
136 #ifndef EXCEPTION_READ_FAULT
137 # define EXCEPTION_READ_FAULT 0
138 # define EXCEPTION_WRITE_FAULT 1
139 # define EXCEPTION_EXECUTE_FAULT 8
140 #endif
141 case EXCEPTION_READ_FAULT: LOG_STATIC_TEXT("tried to read from memory"); break;
142 case EXCEPTION_WRITE_FAULT: LOG_STATIC_TEXT("tried to write to memory"); break;
143 case EXCEPTION_EXECUTE_FAULT: LOG_STATIC_TEXT("caused an user-mode DEP violation"); break;
144 default: LOG_DYNAMIC_TEXT("tried to access (%#x) memory", static_cast<unsigned int>(exc->ExceptionRecord->ExceptionInformation[0])); break;
145 }
146 LOG_DYNAMIC_TEXT(" at address " POINTER_FORMAT ".\n", static_cast<size_t>(exc->ExceptionRecord->ExceptionInformation[1]));
147 if (exc->ExceptionRecord->ExceptionCode == EXCEPTION_IN_PAGE_ERROR)
148 {
149 if (exc->ExceptionRecord->NumberParameters >= 3)
150 LOG_DYNAMIC_TEXT("The NTSTATUS code that resulted in this exception was " POINTER_FORMAT ".\n", static_cast<size_t>(exc->ExceptionRecord->ExceptionInformation[2]));
151 else
152 LOG_STATIC_TEXT("The NTSTATUS code that resulted in this exception was not provided.\n");
153 }
154 break;
155
156 case STATUS_ASSERTION_FAILURE:
157 if (exc->ExceptionRecord->NumberParameters < 3)
158 {
159 LOG_STATIC_TEXT("Additional information for the exception was not provided.\n");
160 break;
161 }
162 #ifdef USE_WIDE_ASSERT
163 # define ASSERTION_INFO_FORMAT "%ls"
164 # define ASSERTION_INFO_TYPE wchar_t *
165 #else
166 # define ASSERTION_INFO_FORMAT "%s"
167 # define ASSERTION_INFO_TYPE char *
168 #endif
169 LOG_DYNAMIC_TEXT("Additional information for the exception:\n Assertion that failed: " ASSERTION_INFO_FORMAT "\n File: " ASSERTION_INFO_FORMAT "\n Line: %d\n",
170 reinterpret_cast<ASSERTION_INFO_TYPE>(exc->ExceptionRecord->ExceptionInformation[0]),
171 reinterpret_cast<ASSERTION_INFO_TYPE>(exc->ExceptionRecord->ExceptionInformation[1]),
172 (int) exc->ExceptionRecord->ExceptionInformation[2]);
173 break;
174 }
175
176 // Dump registers
177 #if OC_MACHINE == OC_MACHINE_X64
178 LOG_STATIC_TEXT("\nProcessor registers (x86_64):\n");
179 LOG_DYNAMIC_TEXT("RAX: " POINTER_FORMAT ", RBX: " POINTER_FORMAT ", RCX: " POINTER_FORMAT ", RDX: " POINTER_FORMAT "\n",
180 static_cast<size_t>(exc->ContextRecord->Rax), static_cast<size_t>(exc->ContextRecord->Rbx),
181 static_cast<size_t>(exc->ContextRecord->Rcx), static_cast<size_t>(exc->ContextRecord->Rdx));
182 LOG_DYNAMIC_TEXT("RBP: " POINTER_FORMAT ", RSI: " POINTER_FORMAT ", RDI: " POINTER_FORMAT ", R8: " POINTER_FORMAT "\n",
183 static_cast<size_t>(exc->ContextRecord->Rbp), static_cast<size_t>(exc->ContextRecord->Rsi),
184 static_cast<size_t>(exc->ContextRecord->Rdi), static_cast<size_t>(exc->ContextRecord->R8));
185 LOG_DYNAMIC_TEXT(" R9: " POINTER_FORMAT ", R10: " POINTER_FORMAT ", R11: " POINTER_FORMAT ", R12: " POINTER_FORMAT "\n",
186 static_cast<size_t>(exc->ContextRecord->R9), static_cast<size_t>(exc->ContextRecord->R10),
187 static_cast<size_t>(exc->ContextRecord->R11), static_cast<size_t>(exc->ContextRecord->R12));
188 LOG_DYNAMIC_TEXT("R13: " POINTER_FORMAT ", R14: " POINTER_FORMAT ", R15: " POINTER_FORMAT "\n",
189 static_cast<size_t>(exc->ContextRecord->R13), static_cast<size_t>(exc->ContextRecord->R14),
190 static_cast<size_t>(exc->ContextRecord->R15));
191 LOG_DYNAMIC_TEXT("RSP: " POINTER_FORMAT ", RIP: " POINTER_FORMAT "\n",
192 static_cast<size_t>(exc->ContextRecord->Rsp), static_cast<size_t>(exc->ContextRecord->Rip));
193 #elif OC_MACHINE == OC_MACHINE_X86
194 LOG_STATIC_TEXT("\nProcessor registers (x86):\n");
195 LOG_DYNAMIC_TEXT("EAX: " POINTER_FORMAT ", EBX: " POINTER_FORMAT ", ECX: " POINTER_FORMAT ", EDX: " POINTER_FORMAT "\n",
196 static_cast<size_t>(exc->ContextRecord->Eax), static_cast<size_t>(exc->ContextRecord->Ebx),
197 static_cast<size_t>(exc->ContextRecord->Ecx), static_cast<size_t>(exc->ContextRecord->Edx));
198 LOG_DYNAMIC_TEXT("ESI: " POINTER_FORMAT ", EDI: " POINTER_FORMAT "\n",
199 static_cast<size_t>(exc->ContextRecord->Esi), static_cast<size_t>(exc->ContextRecord->Edi));
200 LOG_DYNAMIC_TEXT("EBP: " POINTER_FORMAT ", ESP: " POINTER_FORMAT ", EIP: " POINTER_FORMAT "\n",
201 static_cast<size_t>(exc->ContextRecord->Ebp), static_cast<size_t>(exc->ContextRecord->Esp),
202 static_cast<size_t>(exc->ContextRecord->Eip));
203 #endif
204 #if OC_MACHINE == OC_MACHINE_X64 || OC_MACHINE == OC_MACHINE_X86
205 LOG_DYNAMIC_TEXT("EFLAGS: 0x%08x (%c%c%c%c%c%c%c)\n", static_cast<unsigned int>(exc->ContextRecord->EFlags),
206 exc->ContextRecord->EFlags & 0x800 ? 'O' : '.', // overflow
207 exc->ContextRecord->EFlags & 0x400 ? 'D' : '.', // direction
208 exc->ContextRecord->EFlags & 0x80 ? 'S' : '.', // sign
209 exc->ContextRecord->EFlags & 0x40 ? 'Z' : '.', // zero
210 exc->ContextRecord->EFlags & 0x10 ? 'A' : '.', // auxiliary carry
211 exc->ContextRecord->EFlags & 0x4 ? 'P' : '.', // parity
212 exc->ContextRecord->EFlags & 0x1 ? 'C' : '.'); // carry
213 #endif
214
215 // Dump stack
216 LOG_STATIC_TEXT("\nStack contents:\n");
217 MEMORY_BASIC_INFORMATION stack_info;
218 intptr_t stack_pointer =
219 #if OC_MACHINE == OC_MACHINE_X64
220 exc->ContextRecord->Rsp
221 #elif OC_MACHINE == OC_MACHINE_X86
222 exc->ContextRecord->Esp
223 #endif
224 ;
225 if (VirtualQuery(reinterpret_cast<LPCVOID>(stack_pointer), &stack_info, sizeof(stack_info)))
226 {
227 intptr_t stack_base = reinterpret_cast<intptr_t>(stack_info.BaseAddress);
228 intptr_t dump_min = std::max<intptr_t>(stack_base, (stack_pointer - 256) & ~0xF);
229 intptr_t dump_max = std::min<intptr_t>(stack_base + stack_info.RegionSize, (stack_pointer + 256) | 0xF);
230
231 for (intptr_t dump_row_base = dump_min & ~0xF; dump_row_base < dump_max; dump_row_base += 0x10)
232 {
233 LOG_DYNAMIC_TEXT(POINTER_FORMAT ": ", dump_row_base);
234 // Hex dump
235 for (intptr_t dump_row_cursor = dump_row_base; dump_row_cursor < dump_row_base + 16; ++dump_row_cursor)
236 {
237 if (dump_row_cursor < dump_min || dump_row_cursor > dump_max)
238 LOG_STATIC_TEXT(" ");
239 else
240 LOG_DYNAMIC_TEXT("%02x ", (unsigned int)*reinterpret_cast<unsigned char*>(dump_row_cursor)); // Safe, since it's inside the VM of our process
241 }
242 LOG_STATIC_TEXT(" ");
243 // Text dump
244 for (intptr_t dump_row_cursor = dump_row_base; dump_row_cursor < dump_row_base + 16; ++dump_row_cursor)
245 {
246 if (dump_row_cursor < dump_min || dump_row_cursor > dump_max)
247 LOG_STATIC_TEXT(" ");
248 else
249 {
250 unsigned char c = *reinterpret_cast<unsigned char*>(dump_row_cursor); // Safe, since it's inside the VM of our process
251 if (c < 0x20 || (c > 0x7e && c < 0xa1))
252 LOG_STATIC_TEXT(".");
253 else
254 LOG_DYNAMIC_TEXT("%c", static_cast<char>(c));
255 }
256 }
257 LOG_STATIC_TEXT("\n");
258 }
259 }
260 else
261 {
262 LOG_STATIC_TEXT("[Failed to access stack memory]\n");
263 }
264
265 // Initialize DbgHelp.dll symbol functions
266 SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES);
267 HANDLE process = GetCurrentProcess();
268 if (SymInitialize(process, nullptr, true))
269 {
270 LOG_STATIC_TEXT("\nStack trace:\n");
271 auto frame = STACKFRAME64();
272 DWORD image_type;
273 CONTEXT context = *exc->ContextRecord;
274 // Setup frame info
275 frame.AddrPC.Mode = AddrModeFlat;
276 frame.AddrStack.Mode = AddrModeFlat;
277 frame.AddrFrame.Mode = AddrModeFlat;
278 #if OC_MACHINE == OC_MACHINE_X64
279 image_type = IMAGE_FILE_MACHINE_AMD64;
280 frame.AddrPC.Offset = context.Rip;
281 frame.AddrStack.Offset = context.Rsp;
282 // Some compilers use rdi for their frame pointer instead. Let's hope they're in the minority.
283 frame.AddrFrame.Offset = context.Rbp;
284 #elif OC_MACHINE == OC_MACHINE_X86
285 image_type = IMAGE_FILE_MACHINE_I386;
286 frame.AddrPC.Offset = context.Eip;
287 frame.AddrStack.Offset = context.Esp;
288 frame.AddrFrame.Offset = context.Ebp;
289 #endif
290 // Dump stack trace
291 SYMBOL_INFO *symbol = reinterpret_cast<SYMBOL_INFO*>(SymbolBuffer);
292 static_assert(DumpBufferSize >= sizeof(*symbol), "SYMBOL_INFO too large to fit into buffer");
293 IMAGEHLP_MODULE64 *module = reinterpret_cast<IMAGEHLP_MODULE64*>(SymbolBuffer);
294 static_assert(DumpBufferSize >= sizeof(*module), "IMAGEHLP_MODULE64 too large to fit into buffer");
295 IMAGEHLP_LINE64 *line = reinterpret_cast<IMAGEHLP_LINE64*>(SymbolBuffer);
296 static_assert(DumpBufferSize >= sizeof(*line), "IMAGEHLP_LINE64 too large to fit into buffer");
297 int frame_number = 0;
298 while (StackWalk64(image_type, process, GetCurrentThread(), &frame, &context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr))
299 {
300 LOG_DYNAMIC_TEXT("#%3d ", frame_number);
301 module->SizeOfStruct = sizeof(*module);
302 DWORD64 image_base = 0;
303 if (SymGetModuleInfo64(process, frame.AddrPC.Offset, module))
304 {
305 LOG_DYNAMIC_TEXT("%s", module->ModuleName);
306 image_base = module->BaseOfImage;
307 }
308 DWORD64 disp64;
309 symbol->MaxNameLen = DumpBufferSize - sizeof(*symbol);
310 symbol->SizeOfStruct = sizeof(*symbol);
311 if (SymFromAddr(process, frame.AddrPC.Offset, &disp64, symbol))
312 {
313 LOG_DYNAMIC_TEXT("!%s+%#lx", symbol->Name, static_cast<long>(disp64));
314 }
315 else if (image_base > 0)
316 {
317 LOG_DYNAMIC_TEXT("+%#lx", static_cast<long>(frame.AddrPC.Offset - image_base));
318 }
319 else
320 {
321 LOG_DYNAMIC_TEXT("%#lx", static_cast<long>(frame.AddrPC.Offset));
322 }
323 DWORD disp;
324 line->SizeOfStruct = sizeof(*line);
325 if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &disp, line))
326 {
327 LOG_DYNAMIC_TEXT(" [%s @ %u]", line->FileName, static_cast<unsigned int>(line->LineNumber));
328 }
329 LOG_STATIC_TEXT("\n");
330 ++frame_number;
331 }
332 SymCleanup(process);
333 }
334 else
335 {
336 LOG_STATIC_TEXT("[Stack trace not available: failed to initialize Debugging Help Library]\n");
337 }
338
339 // Dump loaded modules
340 HANDLE snapshot;
341 while((snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0)) == INVALID_HANDLE_VALUE)
342 if (GetLastError() != ERROR_BAD_LENGTH) break;
343 if (snapshot != INVALID_HANDLE_VALUE)
344 {
345 LOG_STATIC_TEXT("\nLoaded modules:\n");
346 MODULEENTRY32 *module = reinterpret_cast<MODULEENTRY32*>(SymbolBuffer);
347 static_assert(DumpBufferSize >= sizeof(*module), "MODULEENTRY32 too large to fit into buffer");
348 module->dwSize = sizeof(*module);
349 for (BOOL success = Module32First(snapshot, module); success; success = Module32Next(snapshot, module))
350 {
351 LOG_DYNAMIC_TEXT("%32ls loaded at " POINTER_FORMAT " - " POINTER_FORMAT " (%ls)\n", module->szModule,
352 reinterpret_cast<size_t>(module->modBaseAddr), reinterpret_cast<size_t>(module->modBaseAddr + module->modBaseSize),
353 module->szExePath);
354 }
355 CloseHandle(snapshot);
356 }
357 #undef POINTER_FORMAT_SUFFIX
358 #undef POINTER_FORMAT
359 #undef LOG_SNPRINTF
360 #undef LOG_DYNAMIC_TEXT
361 #undef LOG_STATIC_TEXT
362 }
363 }
GenerateDump(EXCEPTION_POINTERS * pExceptionPointers)364 LONG WINAPI GenerateDump(EXCEPTION_POINTERS* pExceptionPointers)
365 {
366 enum
367 {
368 MDST_BuildId = LastReservedStream + 1
369 };
370
371 if (!FirstCrash) return EXCEPTION_EXECUTE_HANDLER;
372 FirstCrash = false;
373
374 // Open dump file
375 // Work on the assumption that the config isn't corrupted
376 wchar_t *filename = reinterpret_cast<wchar_t*>(DumpBuffer);
377 const size_t filename_buffer_size = DumpBufferSize / sizeof(wchar_t);
378 if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, ::Config.General.UserDataPath, strnlen(::Config.General.UserDataPath, sizeof(::Config.General.UserDataPath)), filename, filename_buffer_size))
379 {
380 // Conversion failed; the likely reason for this is a corrupted config.
381 assert (GetLastError() == ERROR_NO_UNICODE_TRANSLATION);
382 // Fall back to the temporary files directory to write dump.
383 DWORD temp_size = GetTempPath(filename_buffer_size, filename);
384 if (temp_size == 0 || temp_size > filename_buffer_size)
385 {
386 // Getting the temp path failed as well; dump to current working directory as a last resort.
387 temp_size = GetCurrentDirectory(filename_buffer_size, filename);
388 if (temp_size == 0 || temp_size > filename_buffer_size)
389 {
390 // We don't really have any directory where we can store the dump, so just
391 // write the text log (we already have a FD for that)
392 filename[0] = L'\0';
393 }
394 }
395 }
396 HANDLE file = INVALID_HANDLE_VALUE;
397
398 if (filename[0] != L'\0')
399 {
400 // There is some path where we want to store our data
401 const wchar_t tmpl[] = TEXT(C4ENGINENICK) L"-crash-YYYY-MM-DD-HH-MM-SS.dmp";
402 size_t path_len = wcslen(filename);
403 if (path_len + sizeof(tmpl) / sizeof(*tmpl) > filename_buffer_size)
404 {
405 // Somehow the length of the required path is too long to fit in
406 // our buffer. Don't dump anything then.
407 filename[0] = L'\0';
408 }
409 else
410 {
411 // Make sure the path ends in a backslash.
412 if (filename[path_len - 1] != L'\\')
413 {
414 filename[path_len] = L'\\';
415 filename[++path_len] = L'\0';
416 }
417 SYSTEMTIME st;
418 GetSystemTime(&st);
419 wsprintf(&filename[path_len], L"%s-crash-%04d-%02d-%02d-%02d-%02d-%02d.dmp",
420 TEXT(C4ENGINENICK), st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
421 }
422 }
423
424 if (filename[0] != L'\0')
425 {
426 file = CreateFile(filename, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
427 // If we can't create a *new* file to dump into, don't dump at all.
428 if (file == INVALID_HANDLE_VALUE)
429 filename[0] = L'\0';
430 }
431
432 // Write dump (human readable format)
433 if (GetLogFD() != -1)
434 SafeTextDump(pExceptionPointers, GetLogFD(), filename);
435
436 if (file != INVALID_HANDLE_VALUE)
437 {
438 auto user_stream_info = MINIDUMP_USER_STREAM_INFORMATION();
439 auto user_stream = MINIDUMP_USER_STREAM();
440 char build_id[] = OC_BUILD_ID;
441 if (OC_BUILD_ID[0] != '\0')
442 {
443 user_stream.Type = MDST_BuildId;
444 user_stream.Buffer = build_id;
445 user_stream.BufferSize = sizeof(build_id) - 1; // don't need the terminating NUL
446 user_stream_info.UserStreamCount = 1;
447 user_stream_info.UserStreamArray = &user_stream;
448 }
449
450 MINIDUMP_EXCEPTION_INFORMATION ExpParam;
451 ExpParam.ThreadId = GetCurrentThreadId();
452 ExpParam.ExceptionPointers = pExceptionPointers;
453 ExpParam.ClientPointers = true;
454 MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
455 file, MiniDumpNormal, &ExpParam, &user_stream_info, nullptr);
456 CloseHandle(file);
457 }
458
459 // Pass exception
460 return EXCEPTION_EXECUTE_HANDLER;
461 }
462
463 #ifndef NDEBUG
464 namespace {
465 // Assertion logging hook. This will replace the prologue of the standard assertion
466 // handler with a trampoline to assertion_handler(), which logs the assertion, then
467 // replaces the trampoline with the original prologue, and calls the handler.
468 // If the standard handler returns control to assertion_handler(), it will then
469 // restore the hook.
470 #ifdef USE_WIDE_ASSERT
471 typedef void (__cdecl *ASSERT_FUNC)(const wchar_t *, const wchar_t *, unsigned);
472 const ASSERT_FUNC assert_func =
473 &_wassert;
474 #else
475 typedef void (__cdecl *ASSERT_FUNC)(const char *, const char *, int);
476 const ASSERT_FUNC assert_func =
477 &_assert;
478 #endif
479 unsigned char trampoline[] = {
480 #if OC_MACHINE == OC_MACHINE_X64
481 // MOV rax, 0xCCCCCCCCCCCCCCCC
482 0x48 /* REX.W */, 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
483 // JMP rax
484 0xFF, 0xE0
485 #elif OC_MACHINE == OC_MACHINE_X86
486 // NOP ; to align jump target
487 0x90,
488 // MOV eax, 0xCCCCCCCC
489 0xB8, 0xCC, 0xCC, 0xCC, 0xCC,
490 // JMP eax
491 0xFF, 0xE0
492 #endif
493 };
494 unsigned char trampoline_backup[sizeof(trampoline)];
HookAssert(ASSERT_FUNC hook)495 void HookAssert(ASSERT_FUNC hook)
496 {
497 // Write hook function address to trampoline
498 memcpy(trampoline + 2, (void*)&hook, sizeof(void*));
499 // Make target location writable
500 DWORD old_protect = 0;
501 if (!VirtualProtect((LPVOID)assert_func, sizeof(trampoline), PAGE_EXECUTE_READWRITE, &old_protect))
502 return;
503 // Take backup of old target function and replace it with trampoline
504 memcpy(trampoline_backup, (void*)assert_func, sizeof(trampoline_backup));
505 memcpy((void*)assert_func, trampoline, sizeof(trampoline));
506 // Restore memory protection
507 VirtualProtect((LPVOID)assert_func, sizeof(trampoline), old_protect, &old_protect);
508 // Flush processor caches. Not strictly necessary on x86 and x64.
509 FlushInstructionCache(GetCurrentProcess(), (LPCVOID)assert_func, sizeof(trampoline));
510 }
UnhookAssert()511 void UnhookAssert()
512 {
513 DWORD old_protect = 0;
514 if (!VirtualProtect((LPVOID)assert_func, sizeof(trampoline_backup), PAGE_EXECUTE_READWRITE, &old_protect))
515 // Couldn't make assert function writable. Abort program (it's what assert() is supposed to do anyway).
516 abort();
517 // Replace function with backup
518 memcpy((void*)assert_func, trampoline_backup, sizeof(trampoline_backup));
519 VirtualProtect((LPVOID)assert_func, sizeof(trampoline_backup), old_protect, &old_protect);
520 FlushInstructionCache(GetCurrentProcess(), (LPCVOID)assert_func, sizeof(trampoline_backup));
521 }
522
523 struct dump_thread_t {
524 HANDLE thread;
525 #ifdef USE_WIDE_ASSERT
526 const wchar_t
527 #else
528 const char
529 #endif
530 *expression, *file;
531 size_t line;
532 };
533 // Helper function to get a valid thread context for the main thread
dump_thread(LPVOID t)534 static DWORD WINAPI dump_thread(LPVOID t)
535 {
536 dump_thread_t *data = static_cast<dump_thread_t*>(t);
537
538 // Stop calling thread so we can take a snapshot
539 if (SuspendThread(data->thread) == -1)
540 return FALSE;
541
542 // Get thread info
543 auto ctx = CONTEXT();
544 #ifndef CONTEXT_ALL
545 #define CONTEXT_ALL (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | \
546 CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS)
547 #endif
548 ctx.ContextFlags = CONTEXT_ALL;
549 BOOL result = GetThreadContext(data->thread, &ctx);
550
551 // Setup a fake exception to log
552 auto erec = EXCEPTION_RECORD();
553 erec.ExceptionCode = STATUS_ASSERTION_FAILURE;
554 erec.ExceptionFlags = 0L;
555 erec.ExceptionInformation[0] = (ULONG_PTR)data->expression;
556 erec.ExceptionInformation[1] = (ULONG_PTR)data->file;
557 erec.ExceptionInformation[2] = (ULONG_PTR)data->line;
558 erec.NumberParameters = 3;
559
560 erec.ExceptionAddress = (LPVOID)
561 #if OC_MACHINE == OC_MACHINE_X64
562 ctx.Rip
563 #elif OC_MACHINE == OC_MACHINE_X86
564 ctx.Eip
565 #else
566 0
567 #endif
568 ;
569 EXCEPTION_POINTERS eptr;
570 eptr.ContextRecord = &ctx;
571 eptr.ExceptionRecord = &erec;
572
573 // Log
574 if (GetLogFD() != -1)
575 SafeTextDump(&eptr, GetLogFD(), nullptr);
576
577 // Continue caller
578 if (ResumeThread(data->thread) == -1)
579 abort();
580 return result;
581 }
582
583 // Replacement assertion handler
584 #ifdef USE_WIDE_ASSERT
assertion_handler(const wchar_t * expression,const wchar_t * file,unsigned line)585 void __cdecl assertion_handler(const wchar_t *expression, const wchar_t *file, unsigned line)
586 #else
587 void __cdecl assertion_handler(const char *expression, const char *file, int line)
588 #endif
589 {
590 // Dump thread status on a different thread because we can't get a valid thread context otherwise
591 HANDLE this_thread;
592 DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &this_thread, 0, FALSE, DUPLICATE_SAME_ACCESS);
593 dump_thread_t dump_thread_data = {
594 this_thread,
595 expression, file, line
596 };
597 HANDLE ctx_thread = CreateThread(nullptr, 0L, &dump_thread, &dump_thread_data, 0L, nullptr);
598 WaitForSingleObject(ctx_thread, INFINITE);
599 CloseHandle(this_thread);
600 CloseHandle(ctx_thread);
601 // Unhook _wassert/_assert
602 UnhookAssert();
603 // Call old _wassert/_assert
604 assert_func(expression, file, line);
605 // If we get here: rehook
606 HookAssert(&assertion_handler);
607 }
608 }
609 #endif
610
InstallCrashHandler()611 void InstallCrashHandler()
612 {
613 // Disable process-wide callback filter for exceptions on Windows Vista.
614 // Newer versions of Windows already get this disabled by the application
615 // manifest. Without turning this off, we won't be able to handle crashes
616 // inside window procedures on 64-bit Windows, regardless of whether we
617 // are 32 or 64 bit ourselves.
618 typedef BOOL (WINAPI *SetProcessUserModeExceptionPolicyProc)(DWORD);
619 typedef BOOL (WINAPI *GetProcessUserModeExceptionPolicyProc)(LPDWORD);
620 HMODULE kernel32 = LoadLibrary(TEXT("kernel32"));
621 const SetProcessUserModeExceptionPolicyProc SetProcessUserModeExceptionPolicy =
622 (SetProcessUserModeExceptionPolicyProc)GetProcAddress(kernel32, "SetProcessUserModeExceptionPolicy");
623 const GetProcessUserModeExceptionPolicyProc GetProcessUserModeExceptionPolicy =
624 (GetProcessUserModeExceptionPolicyProc)GetProcAddress(kernel32, "GetProcessUserModeExceptionPolicy");
625 #ifndef PROCESS_CALLBACK_FILTER_ENABLED
626 # define PROCESS_CALLBACK_FILTER_ENABLED 0x1
627 #endif
628 if (SetProcessUserModeExceptionPolicy && GetProcessUserModeExceptionPolicy)
629 {
630 DWORD flags;
631 if (GetProcessUserModeExceptionPolicy(&flags))
632 {
633 SetProcessUserModeExceptionPolicy(flags & ~PROCESS_CALLBACK_FILTER_ENABLED);
634 }
635 }
636 FreeLibrary(kernel32);
637
638 SetUnhandledExceptionFilter(GenerateDump);
639
640 #ifndef NDEBUG
641 // Hook _wassert/_assert, unless we're running under a debugger
642 if (!IsDebuggerPresent())
643 HookAssert(&assertion_handler);
644 #endif
645 }
646
647 #else
648
InstallCrashHandler()649 void InstallCrashHandler()
650 {
651 // no-op
652 }
653
654 #endif // HAVE_DBGHELP
655