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