OpenTTD Source  1.11.0-beta2
crashlog_win.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of OpenTTD.
3  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6  */
7 
10 #include "../../stdafx.h"
11 #include "../../crashlog.h"
12 #include "win32.h"
13 #include "../../core/alloc_func.hpp"
14 #include "../../core/math_func.hpp"
15 #include "../../string_func.h"
16 #include "../../fileio_func.h"
17 #include "../../strings_func.h"
18 #include "../../gamelog.h"
19 #include "../../saveload/saveload.h"
20 #include "../../video/video_driver.hpp"
21 
22 #include <windows.h>
23 #include <mmsystem.h>
24 #include <signal.h>
25 
26 #include "../../safeguards.h"
27 
28 /* printf format specification for 32/64-bit addresses. */
29 #ifdef _M_AMD64
30 #define PRINTF_PTR "0x%016IX"
31 #else
32 #define PRINTF_PTR "0x%08X"
33 #endif
34 
38 class CrashLogWindows : public CrashLog {
40  EXCEPTION_POINTERS *ep;
41 
42  char *LogOSVersion(char *buffer, const char *last) const override;
43  char *LogError(char *buffer, const char *last, const char *message) const override;
44  char *LogStacktrace(char *buffer, const char *last) const override;
45  char *LogRegisters(char *buffer, const char *last) const override;
46  char *LogModules(char *buffer, const char *last) const override;
47 public:
48 #if defined(_MSC_VER)
49  int WriteCrashDump(char *filename, const char *filename_last) const override;
50  char *AppendDecodedStacktrace(char *buffer, const char *last) const;
51 #else
52  char *AppendDecodedStacktrace(char *buffer, const char *last) const { return buffer; }
53 #endif /* _MSC_VER */
54 
56  char crashlog[65536];
58  char crashlog_filename[MAX_PATH];
60  char crashdump_filename[MAX_PATH];
62  char screenshot_filename[MAX_PATH];
63 
68  CrashLogWindows(EXCEPTION_POINTERS *ep = nullptr) :
69  ep(ep)
70  {
71  this->crashlog[0] = '\0';
72  this->crashlog_filename[0] = '\0';
73  this->crashdump_filename[0] = '\0';
74  this->screenshot_filename[0] = '\0';
75  }
76 
81 };
82 
83 /* static */ CrashLogWindows *CrashLogWindows::current = nullptr;
84 
85 /* virtual */ char *CrashLogWindows::LogOSVersion(char *buffer, const char *last) const
86 {
87  _OSVERSIONINFOA os;
88  os.dwOSVersionInfoSize = sizeof(os);
89  GetVersionExA(&os);
90 
91  return buffer + seprintf(buffer, last,
92  "Operating system:\n"
93  " Name: Windows\n"
94  " Release: %d.%d.%d (%s)\n",
95  (int)os.dwMajorVersion,
96  (int)os.dwMinorVersion,
97  (int)os.dwBuildNumber,
98  os.szCSDVersion
99  );
100 
101 }
102 
103 /* virtual */ char *CrashLogWindows::LogError(char *buffer, const char *last, const char *message) const
104 {
105  return buffer + seprintf(buffer, last,
106  "Crash reason:\n"
107  " Exception: %.8X\n"
108 #ifdef _M_AMD64
109  " Location: %.16IX\n"
110 #else
111  " Location: %.8X\n"
112 #endif
113  " Message: %s\n\n",
114  (int)ep->ExceptionRecord->ExceptionCode,
115  (size_t)ep->ExceptionRecord->ExceptionAddress,
116  message == nullptr ? "<none>" : message
117  );
118 }
119 
121  uint32 size;
122  uint32 crc32;
123  SYSTEMTIME file_time;
124 };
125 
126 static uint32 *_crc_table;
127 
128 static void MakeCRCTable(uint32 *table)
129 {
130  uint32 crc, poly = 0xEDB88320L;
131  int i;
132  int j;
133 
134  _crc_table = table;
135 
136  for (i = 0; i != 256; i++) {
137  crc = i;
138  for (j = 8; j != 0; j--) {
139  crc = (crc & 1 ? (crc >> 1) ^ poly : crc >> 1);
140  }
141  table[i] = crc;
142  }
143 }
144 
145 static uint32 CalcCRC(byte *data, uint size, uint32 crc)
146 {
147  for (; size > 0; size--) {
148  crc = ((crc >> 8) & 0x00FFFFFF) ^ _crc_table[(crc ^ *data++) & 0xFF];
149  }
150  return crc;
151 }
152 
153 static void GetFileInfo(DebugFileInfo *dfi, const wchar_t *filename)
154 {
155  HANDLE file;
156  memset(dfi, 0, sizeof(*dfi));
157 
158  file = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, 0);
159  if (file != INVALID_HANDLE_VALUE) {
160  byte buffer[1024];
161  DWORD numread;
162  uint32 filesize = 0;
163  FILETIME write_time;
164  uint32 crc = (uint32)-1;
165 
166  for (;;) {
167  if (ReadFile(file, buffer, sizeof(buffer), &numread, nullptr) == 0 || numread == 0) {
168  break;
169  }
170  filesize += numread;
171  crc = CalcCRC(buffer, numread, crc);
172  }
173  dfi->size = filesize;
174  dfi->crc32 = crc ^ (uint32)-1;
175 
176  if (GetFileTime(file, nullptr, nullptr, &write_time)) {
177  FileTimeToSystemTime(&write_time, &dfi->file_time);
178  }
179  CloseHandle(file);
180  }
181 }
182 
183 
184 static char *PrintModuleInfo(char *output, const char *last, HMODULE mod)
185 {
186  wchar_t buffer[MAX_PATH];
187  DebugFileInfo dfi;
188 
189  GetModuleFileName(mod, buffer, MAX_PATH);
190  GetFileInfo(&dfi, buffer);
191  output += seprintf(output, last, " %-20s handle: %p size: %d crc: %.8X date: %d-%.2d-%.2d %.2d:%.2d:%.2d\n",
192  FS2OTTD(buffer),
193  mod,
194  dfi.size,
195  dfi.crc32,
196  dfi.file_time.wYear,
197  dfi.file_time.wMonth,
198  dfi.file_time.wDay,
199  dfi.file_time.wHour,
200  dfi.file_time.wMinute,
201  dfi.file_time.wSecond
202  );
203  return output;
204 }
205 
206 /* virtual */ char *CrashLogWindows::LogModules(char *output, const char *last) const
207 {
208  MakeCRCTable(AllocaM(uint32, 256));
209  BOOL (WINAPI *EnumProcessModules)(HANDLE, HMODULE*, DWORD, LPDWORD);
210 
211  output += seprintf(output, last, "Module information:\n");
212 
213  if (LoadLibraryList((Function*)&EnumProcessModules, "psapi.dll\0EnumProcessModules\0\0")) {
214  HMODULE modules[100];
215  DWORD needed;
216  BOOL res;
217 
218  HANDLE proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
219  if (proc != nullptr) {
220  res = EnumProcessModules(proc, modules, sizeof(modules), &needed);
221  CloseHandle(proc);
222  if (res) {
223  size_t count = std::min<DWORD>(needed / sizeof(HMODULE), lengthof(modules));
224 
225  for (size_t i = 0; i != count; i++) output = PrintModuleInfo(output, last, modules[i]);
226  return output + seprintf(output, last, "\n");
227  }
228  }
229  }
230  output = PrintModuleInfo(output, last, nullptr);
231  return output + seprintf(output, last, "\n");
232 }
233 
234 /* virtual */ char *CrashLogWindows::LogRegisters(char *buffer, const char *last) const
235 {
236  buffer += seprintf(buffer, last, "Registers:\n");
237 #ifdef _M_AMD64
238  buffer += seprintf(buffer, last,
239  " RAX: %.16I64X RBX: %.16I64X RCX: %.16I64X RDX: %.16I64X\n"
240  " RSI: %.16I64X RDI: %.16I64X RBP: %.16I64X RSP: %.16I64X\n"
241  " R8: %.16I64X R9: %.16I64X R10: %.16I64X R11: %.16I64X\n"
242  " R12: %.16I64X R13: %.16I64X R14: %.16I64X R15: %.16I64X\n"
243  " RIP: %.16I64X EFLAGS: %.8lX\n",
244  ep->ContextRecord->Rax,
245  ep->ContextRecord->Rbx,
246  ep->ContextRecord->Rcx,
247  ep->ContextRecord->Rdx,
248  ep->ContextRecord->Rsi,
249  ep->ContextRecord->Rdi,
250  ep->ContextRecord->Rbp,
251  ep->ContextRecord->Rsp,
252  ep->ContextRecord->R8,
253  ep->ContextRecord->R9,
254  ep->ContextRecord->R10,
255  ep->ContextRecord->R11,
256  ep->ContextRecord->R12,
257  ep->ContextRecord->R13,
258  ep->ContextRecord->R14,
259  ep->ContextRecord->R15,
260  ep->ContextRecord->Rip,
261  ep->ContextRecord->EFlags
262  );
263 #elif defined(_M_IX86)
264  buffer += seprintf(buffer, last,
265  " EAX: %.8X EBX: %.8X ECX: %.8X EDX: %.8X\n"
266  " ESI: %.8X EDI: %.8X EBP: %.8X ESP: %.8X\n"
267  " EIP: %.8X EFLAGS: %.8X\n",
268  (int)ep->ContextRecord->Eax,
269  (int)ep->ContextRecord->Ebx,
270  (int)ep->ContextRecord->Ecx,
271  (int)ep->ContextRecord->Edx,
272  (int)ep->ContextRecord->Esi,
273  (int)ep->ContextRecord->Edi,
274  (int)ep->ContextRecord->Ebp,
275  (int)ep->ContextRecord->Esp,
276  (int)ep->ContextRecord->Eip,
277  (int)ep->ContextRecord->EFlags
278  );
279 #elif defined(_M_ARM64)
280  buffer += seprintf(buffer, last,
281  " X0: %.16I64X X1: %.16I64X X2: %.16I64X X3: %.16I64X\n"
282  " X4: %.16I64X X5: %.16I64X X6: %.16I64X X7: %.16I64X\n"
283  " X8: %.16I64X X9: %.16I64X X10: %.16I64X X11: %.16I64X\n"
284  " X12: %.16I64X X13: %.16I64X X14: %.16I64X X15: %.16I64X\n"
285  " X16: %.16I64X X17: %.16I64X X18: %.16I64X X19: %.16I64X\n"
286  " X20: %.16I64X X21: %.16I64X X22: %.16I64X X23: %.16I64X\n"
287  " X24: %.16I64X X25: %.16I64X X26: %.16I64X X27: %.16I64X\n"
288  " X28: %.16I64X Fp: %.16I64X Lr: %.16I64X\n",
289  ep->ContextRecord->X0,
290  ep->ContextRecord->X1,
291  ep->ContextRecord->X2,
292  ep->ContextRecord->X3,
293  ep->ContextRecord->X4,
294  ep->ContextRecord->X5,
295  ep->ContextRecord->X6,
296  ep->ContextRecord->X7,
297  ep->ContextRecord->X8,
298  ep->ContextRecord->X9,
299  ep->ContextRecord->X10,
300  ep->ContextRecord->X11,
301  ep->ContextRecord->X12,
302  ep->ContextRecord->X13,
303  ep->ContextRecord->X14,
304  ep->ContextRecord->X15,
305  ep->ContextRecord->X16,
306  ep->ContextRecord->X17,
307  ep->ContextRecord->X18,
308  ep->ContextRecord->X19,
309  ep->ContextRecord->X20,
310  ep->ContextRecord->X21,
311  ep->ContextRecord->X22,
312  ep->ContextRecord->X23,
313  ep->ContextRecord->X24,
314  ep->ContextRecord->X25,
315  ep->ContextRecord->X26,
316  ep->ContextRecord->X27,
317  ep->ContextRecord->X28,
318  ep->ContextRecord->Fp,
319  ep->ContextRecord->Lr
320  );
321 #endif
322 
323  buffer += seprintf(buffer, last, "\n Bytes at instruction pointer:\n");
324 #ifdef _M_AMD64
325  byte *b = (byte*)ep->ContextRecord->Rip;
326 #elif defined(_M_IX86)
327  byte *b = (byte*)ep->ContextRecord->Eip;
328 #elif defined(_M_ARM64)
329  byte *b = (byte*)ep->ContextRecord->Pc;
330 #endif
331  for (int i = 0; i != 24; i++) {
332  if (IsBadReadPtr(b, 1)) {
333  buffer += seprintf(buffer, last, " ??"); // OCR: WAS: , 0);
334  } else {
335  buffer += seprintf(buffer, last, " %.2X", *b);
336  }
337  b++;
338  }
339  return buffer + seprintf(buffer, last, "\n\n");
340 }
341 
342 /* virtual */ char *CrashLogWindows::LogStacktrace(char *buffer, const char *last) const
343 {
344  buffer += seprintf(buffer, last, "Stack trace:\n");
345 #ifdef _M_AMD64
346  uint32 *b = (uint32*)ep->ContextRecord->Rsp;
347 #elif defined(_M_IX86)
348  uint32 *b = (uint32*)ep->ContextRecord->Esp;
349 #elif defined(_M_ARM64)
350  uint32 *b = (uint32*)ep->ContextRecord->Sp;
351 #endif
352  for (int j = 0; j != 24; j++) {
353  for (int i = 0; i != 8; i++) {
354  if (IsBadReadPtr(b, sizeof(uint32))) {
355  buffer += seprintf(buffer, last, " ????????"); // OCR: WAS - , 0);
356  } else {
357  buffer += seprintf(buffer, last, " %.8X", *b);
358  }
359  b++;
360  }
361  buffer += seprintf(buffer, last, "\n");
362  }
363  return buffer + seprintf(buffer, last, "\n");
364 }
365 
366 #if defined(_MSC_VER)
367 static const uint MAX_SYMBOL_LEN = 512;
368 static const uint MAX_FRAMES = 64;
369 
370 #pragma warning(disable:4091)
371 #include <dbghelp.h>
372 #pragma warning(default:4091)
373 
374 char *CrashLogWindows::AppendDecodedStacktrace(char *buffer, const char *last) const
375 {
376 #define M(x) x "\0"
377  static const char dbg_import[] =
378  M("dbghelp.dll")
379  M("SymInitialize")
380  M("SymSetOptions")
381  M("SymCleanup")
382  M("StackWalk64")
383  M("SymFunctionTableAccess64")
384  M("SymGetModuleBase64")
385  M("SymGetModuleInfo64")
386  M("SymGetSymFromAddr64")
387  M("SymGetLineFromAddr64")
388  M("")
389  ;
390 #undef M
391 
392  struct ProcPtrs {
393  BOOL (WINAPI * pSymInitialize)(HANDLE, PCSTR, BOOL);
394  BOOL (WINAPI * pSymSetOptions)(DWORD);
395  BOOL (WINAPI * pSymCleanup)(HANDLE);
396  BOOL (WINAPI * pStackWalk64)(DWORD, HANDLE, HANDLE, LPSTACKFRAME64, PVOID, PREAD_PROCESS_MEMORY_ROUTINE64, PFUNCTION_TABLE_ACCESS_ROUTINE64, PGET_MODULE_BASE_ROUTINE64, PTRANSLATE_ADDRESS_ROUTINE64);
397  PVOID (WINAPI * pSymFunctionTableAccess64)(HANDLE, DWORD64);
398  DWORD64 (WINAPI * pSymGetModuleBase64)(HANDLE, DWORD64);
399  BOOL (WINAPI * pSymGetModuleInfo64)(HANDLE, DWORD64, PIMAGEHLP_MODULE64);
400  BOOL (WINAPI * pSymGetSymFromAddr64)(HANDLE, DWORD64, PDWORD64, PIMAGEHLP_SYMBOL64);
401  BOOL (WINAPI * pSymGetLineFromAddr64)(HANDLE, DWORD64, PDWORD, PIMAGEHLP_LINE64);
402  } proc;
403 
404  buffer += seprintf(buffer, last, "\nDecoded stack trace:\n");
405 
406  /* Try to load the functions from the DLL, if that fails because of a too old dbghelp.dll, just skip it. */
407  if (LoadLibraryList((Function*)&proc, dbg_import)) {
408  /* Initialize symbol handler. */
409  HANDLE hCur = GetCurrentProcess();
410  proc.pSymInitialize(hCur, nullptr, TRUE);
411  /* Load symbols only when needed, fail silently on errors, demangle symbol names. */
412  proc.pSymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_UNDNAME);
413 
414  /* Initialize starting stack frame from context record. */
415  STACKFRAME64 frame;
416  memset(&frame, 0, sizeof(frame));
417 #ifdef _M_AMD64
418  frame.AddrPC.Offset = ep->ContextRecord->Rip;
419  frame.AddrFrame.Offset = ep->ContextRecord->Rbp;
420  frame.AddrStack.Offset = ep->ContextRecord->Rsp;
421 #elif defined(_M_IX86)
422  frame.AddrPC.Offset = ep->ContextRecord->Eip;
423  frame.AddrFrame.Offset = ep->ContextRecord->Ebp;
424  frame.AddrStack.Offset = ep->ContextRecord->Esp;
425 #elif defined(_M_ARM64)
426  frame.AddrPC.Offset = ep->ContextRecord->Pc;
427  frame.AddrFrame.Offset = ep->ContextRecord->Fp;
428  frame.AddrStack.Offset = ep->ContextRecord->Sp;
429 #endif
430  frame.AddrPC.Mode = AddrModeFlat;
431  frame.AddrFrame.Mode = AddrModeFlat;
432  frame.AddrStack.Mode = AddrModeFlat;
433 
434  /* Copy context record as StackWalk64 may modify it. */
435  CONTEXT ctx;
436  memcpy(&ctx, ep->ContextRecord, sizeof(ctx));
437 
438  /* Allocate space for symbol info. */
439  IMAGEHLP_SYMBOL64 *sym_info = (IMAGEHLP_SYMBOL64*)alloca(sizeof(IMAGEHLP_SYMBOL64) + MAX_SYMBOL_LEN - 1);
440  sym_info->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
441  sym_info->MaxNameLength = MAX_SYMBOL_LEN;
442 
443  /* Walk stack at most MAX_FRAMES deep in case the stack is corrupt. */
444  for (uint num = 0; num < MAX_FRAMES; num++) {
445  if (!proc.pStackWalk64(
446 #ifdef _M_AMD64
447  IMAGE_FILE_MACHINE_AMD64,
448 #else
449  IMAGE_FILE_MACHINE_I386,
450 #endif
451  hCur, GetCurrentThread(), &frame, &ctx, nullptr, proc.pSymFunctionTableAccess64, proc.pSymGetModuleBase64, nullptr)) break;
452 
453  if (frame.AddrPC.Offset == frame.AddrReturn.Offset) {
454  buffer += seprintf(buffer, last, " <infinite loop>\n");
455  break;
456  }
457 
458  /* Get module name. */
459  const char *mod_name = "???";
460 
461  IMAGEHLP_MODULE64 module;
462  module.SizeOfStruct = sizeof(module);
463  if (proc.pSymGetModuleInfo64(hCur, frame.AddrPC.Offset, &module)) {
464  mod_name = module.ModuleName;
465  }
466 
467  /* Print module and instruction pointer. */
468  buffer += seprintf(buffer, last, "[%02d] %-20s " PRINTF_PTR, num, mod_name, frame.AddrPC.Offset);
469 
470  /* Get symbol name and line info if possible. */
471  DWORD64 offset;
472  if (proc.pSymGetSymFromAddr64(hCur, frame.AddrPC.Offset, &offset, sym_info)) {
473  buffer += seprintf(buffer, last, " %s + %I64u", sym_info->Name, offset);
474 
475  DWORD line_offs;
476  IMAGEHLP_LINE64 line;
477  line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
478  if (proc.pSymGetLineFromAddr64(hCur, frame.AddrPC.Offset, &line_offs, &line)) {
479  buffer += seprintf(buffer, last, " (%s:%d)", line.FileName, line.LineNumber);
480  }
481  }
482  buffer += seprintf(buffer, last, "\n");
483  }
484 
485  proc.pSymCleanup(hCur);
486  }
487 
488  return buffer + seprintf(buffer, last, "\n*** End of additional info ***\n");
489 }
490 
491 /* virtual */ int CrashLogWindows::WriteCrashDump(char *filename, const char *filename_last) const
492 {
493  int ret = 0;
494  HMODULE dbghelp = LoadLibrary(L"dbghelp.dll");
495  if (dbghelp != nullptr) {
496  typedef BOOL (WINAPI *MiniDumpWriteDump_t)(HANDLE, DWORD, HANDLE,
497  MINIDUMP_TYPE,
498  CONST PMINIDUMP_EXCEPTION_INFORMATION,
499  CONST PMINIDUMP_USER_STREAM_INFORMATION,
500  CONST PMINIDUMP_CALLBACK_INFORMATION);
501  MiniDumpWriteDump_t funcMiniDumpWriteDump = (MiniDumpWriteDump_t)GetProcAddress(dbghelp, "MiniDumpWriteDump");
502  if (funcMiniDumpWriteDump != nullptr) {
503  seprintf(filename, filename_last, "%scrash.dmp", _personal_dir.c_str());
504  HANDLE file = CreateFile(OTTD2FS(filename), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, 0);
505  HANDLE proc = GetCurrentProcess();
506  DWORD procid = GetCurrentProcessId();
507  MINIDUMP_EXCEPTION_INFORMATION mdei;
508  MINIDUMP_USER_STREAM userstream;
509  MINIDUMP_USER_STREAM_INFORMATION musi;
510 
511  userstream.Type = LastReservedStream + 1;
512  userstream.Buffer = (void*)this->crashlog;
513  userstream.BufferSize = (ULONG)strlen(this->crashlog) + 1;
514 
515  musi.UserStreamCount = 1;
516  musi.UserStreamArray = &userstream;
517 
518  mdei.ThreadId = GetCurrentThreadId();
519  mdei.ExceptionPointers = ep;
520  mdei.ClientPointers = false;
521 
522  funcMiniDumpWriteDump(proc, procid, file, MiniDumpWithDataSegs, &mdei, &musi, nullptr);
523  ret = 1;
524  } else {
525  ret = -1;
526  }
527  FreeLibrary(dbghelp);
528  }
529  return ret;
530 }
531 #endif /* _MSC_VER */
532 
533 extern bool CloseConsoleLogIfActive();
534 static void ShowCrashlogWindow();
535 
540 void *_safe_esp = nullptr;
541 
542 static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
543 {
544  /* Restore system timer resolution. */
545  timeEndPeriod(1);
546 
547  /* Disable our event loop. */
548  SetWindowLongPtr(GetActiveWindow(), GWLP_WNDPROC, (LONG_PTR)&DefWindowProc);
549 
550  if (CrashLogWindows::current != nullptr) {
552  ExitProcess(2);
553  }
554 
555  if (GamelogTestEmergency()) {
556  static const wchar_t _emergency_crash[] =
557  L"A serious fault condition occurred in the game. The game will shut down.\n"
558  L"As you loaded an emergency savegame no crash information will be generated.\n";
559  MessageBox(nullptr, _emergency_crash, L"Fatal Application Failure", MB_ICONERROR);
560  ExitProcess(3);
561  }
562 
564  static const wchar_t _saveload_crash[] =
565  L"A serious fault condition occurred in the game. The game will shut down.\n"
566  L"As you loaded an savegame for which you do not have the required NewGRFs\n"
567  L"no crash information will be generated.\n";
568  MessageBox(nullptr, _saveload_crash, L"Fatal Application Failure", MB_ICONERROR);
569  ExitProcess(3);
570  }
571 
572  CrashLogWindows *log = new CrashLogWindows(ep);
574  char *buf = log->FillCrashLog(log->crashlog, lastof(log->crashlog));
576  log->AppendDecodedStacktrace(buf, lastof(log->crashlog));
579 
580  /* Close any possible log files */
581  CloseConsoleLogIfActive();
582 
583  if ((VideoDriver::GetInstance() == nullptr || VideoDriver::GetInstance()->HasGUI()) && _safe_esp != nullptr) {
584 #ifdef _M_AMD64
585  ep->ContextRecord->Rip = (DWORD64)ShowCrashlogWindow;
586  ep->ContextRecord->Rsp = (DWORD64)_safe_esp;
587 #elif defined(_M_IX86)
588  ep->ContextRecord->Eip = (DWORD)ShowCrashlogWindow;
589  ep->ContextRecord->Esp = (DWORD)_safe_esp;
590 #elif defined(_M_ARM64)
591  ep->ContextRecord->Pc = (DWORD64)ShowCrashlogWindow;
592  ep->ContextRecord->Sp = (DWORD64)_safe_esp;
593 #endif
594  return EXCEPTION_CONTINUE_EXECUTION;
595  }
596 
598  return EXCEPTION_EXECUTE_HANDLER;
599 }
600 
601 static void CDECL CustomAbort(int signal)
602 {
603  RaiseException(0xE1212012, 0, 0, nullptr);
604 }
605 
606 /* static */ void CrashLog::InitialiseCrashLog()
607 {
608 #if defined(_M_AMD64) || defined(_M_ARM64)
609  CONTEXT ctx;
610  RtlCaptureContext(&ctx);
611 
612  /* The stack pointer for AMD64 must always be 16-byte aligned inside a
613  * function. As we are simulating a function call with the safe ESP value,
614  * we need to subtract 8 for the imaginary return address otherwise stack
615  * alignment would be wrong in the called function. */
616 #if defined(_M_ARM64)
617  _safe_esp = (void *)(ctx.Sp - 8);
618 #else
619  _safe_esp = (void *)(ctx.Rsp - 8);
620 #endif
621 #else
622 #if defined(_MSC_VER)
623  _asm {
624  mov _safe_esp, esp
625  }
626 #else
627  asm("movl %esp, __safe_esp");
628 #endif
629 #endif
630 
631  /* SIGABRT is not an unhandled exception, so we need to intercept it. */
632  signal(SIGABRT, CustomAbort);
633 #if defined(_MSC_VER)
634  /* Don't show abort message as we will get the crashlog window anyway. */
635  _set_abort_behavior(0, _WRITE_ABORT_MSG);
636 #endif
637  SetUnhandledExceptionFilter(ExceptionHandler);
638 }
639 
640 /* The crash log GUI */
641 
642 static bool _expanded;
643 
644 static const wchar_t _crash_desc[] =
645  L"A serious fault condition occurred in the game. The game will shut down.\n"
646  L"Please send the crash information and the crash.dmp file (if any) to the developers.\n"
647  L"This will greatly help debugging. The correct place to do this is https://github.com/OpenTTD/OpenTTD/issues. "
648  L"The information contained in the report is displayed below.\n"
649  L"Press \"Emergency save\" to attempt saving the game. Generated file(s):\n"
650  L"%s";
651 
652 static const wchar_t _save_succeeded[] =
653  L"Emergency save succeeded.\nIts location is '%s'.\n"
654  L"Be aware that critical parts of the internal game state may have become "
655  L"corrupted. The saved game is not guaranteed to work.";
656 
657 static const wchar_t * const _expand_texts[] = {L"S&how report >>", L"&Hide report <<" };
658 
659 static void SetWndSize(HWND wnd, int mode)
660 {
661  RECT r, r2;
662 
663  GetWindowRect(wnd, &r);
664  SetDlgItemText(wnd, 15, _expand_texts[mode == 1]);
665 
666  if (mode >= 0) {
667  GetWindowRect(GetDlgItem(wnd, 11), &r2);
668  int offs = r2.bottom - r2.top + 10;
669  if (mode == 0) offs = -offs;
670  SetWindowPos(wnd, HWND_TOPMOST, 0, 0,
671  r.right - r.left, r.bottom - r.top + offs, SWP_NOMOVE | SWP_NOZORDER);
672  } else {
673  SetWindowPos(wnd, HWND_TOPMOST,
674  (GetSystemMetrics(SM_CXSCREEN) - (r.right - r.left)) / 2,
675  (GetSystemMetrics(SM_CYSCREEN) - (r.bottom - r.top)) / 2,
676  0, 0, SWP_NOSIZE);
677  }
678 }
679 
680 static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
681 {
682  switch (msg) {
683  case WM_INITDIALOG: {
684  /* We need to put the crash-log in a separate buffer because the default
685  * buffer in OTTD2FS is not large enough (512 chars) */
686  wchar_t crash_msgW[lengthof(CrashLogWindows::current->crashlog)];
687  /* Convert unix -> dos newlines because the edit box only supports that properly :( */
688  const char *unix_nl = CrashLogWindows::current->crashlog;
689  char dos_nl[lengthof(CrashLogWindows::current->crashlog)];
690  char *p = dos_nl;
691  WChar c;
692  while ((c = Utf8Consume(&unix_nl)) && p < lastof(dos_nl) - 4) { // 4 is max number of bytes per character
693  if (c == '\n') p += Utf8Encode(p, '\r');
694  p += Utf8Encode(p, c);
695  }
696  *p = '\0';
697 
698  /* Add path to crash.log and crash.dmp (if any) to the crash window text */
699  size_t len = wcslen(_crash_desc) + 2;
700  len += wcslen(OTTD2FS(CrashLogWindows::current->crashlog_filename)) + 2;
701  len += wcslen(OTTD2FS(CrashLogWindows::current->crashdump_filename)) + 2;
702  len += wcslen(OTTD2FS(CrashLogWindows::current->screenshot_filename)) + 1;
703 
704  wchar_t *text = AllocaM(wchar_t, len);
705  _snwprintf(text, len, _crash_desc, OTTD2FS(CrashLogWindows::current->crashlog_filename));
706  if (OTTD2FS(CrashLogWindows::current->crashdump_filename)[0] != L'\0') {
707  wcscat(text, L"\n");
708  wcscat(text, OTTD2FS(CrashLogWindows::current->crashdump_filename));
709  }
710  if (OTTD2FS(CrashLogWindows::current->screenshot_filename)[0] != L'\0') {
711  wcscat(text, L"\n");
712  wcscat(text, OTTD2FS(CrashLogWindows::current->screenshot_filename));
713  }
714 
715  SetDlgItemText(wnd, 10, text);
716  SetDlgItemText(wnd, 11, convert_to_fs(dos_nl, crash_msgW, lengthof(crash_msgW)));
717  SendDlgItemMessage(wnd, 11, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), FALSE);
718  SetWndSize(wnd, -1);
719  } return TRUE;
720  case WM_COMMAND:
721  switch (wParam) {
722  case 12: // Close
724  ExitProcess(2);
725  case 13: // Emergency save
726  char filename[MAX_PATH];
727  if (CrashLogWindows::current->WriteSavegame(filename, lastof(filename))) {
728  size_t len = wcslen(_save_succeeded) + wcslen(OTTD2FS(filename)) + 1;
729  wchar_t *text = AllocaM(wchar_t, len);
730  _snwprintf(text, len, _save_succeeded, OTTD2FS(filename));
731  MessageBox(wnd, text, L"Save successful", MB_ICONINFORMATION);
732  } else {
733  MessageBox(wnd, L"Save failed", L"Save failed", MB_ICONINFORMATION);
734  }
735  break;
736  case 15: // Expand window to show crash-message
737  _expanded ^= 1;
738  SetWndSize(wnd, _expanded);
739  break;
740  }
741  return TRUE;
742  case WM_CLOSE:
744  ExitProcess(2);
745  }
746 
747  return FALSE;
748 }
749 
750 static void ShowCrashlogWindow()
751 {
752  ShowCursor(TRUE);
753  ShowWindow(GetActiveWindow(), FALSE);
754  DialogBox(GetModuleHandle(nullptr), MAKEINTRESOURCE(100), nullptr, CrashDialogFunc);
755 }
WChar
char32_t WChar
Type for wide characters, i.e.
Definition: string_type.h:35
FS2OTTD
const char * FS2OTTD(const wchar_t *name)
Convert to OpenTTD's encoding from wide characters.
Definition: win32.cpp:565
_personal_dir
std::string _personal_dir
custom directory for personal settings, saves, newgrf, etc.
Definition: fileio.cpp:1119
CrashLog::AfterCrashLogCleanup
static void AfterCrashLogCleanup()
Try to close the sound/video stuff so it doesn't keep lingering around incorrect video states or so,...
Definition: crashlog.cpp:507
win32.h
CrashLog::WriteScreenshot
bool WriteScreenshot(char *filename, const char *filename_last) const
Write the (crash) screenshot to a file.
Definition: crashlog.cpp:423
CrashLogWindows
Windows implementation for the crash logger.
Definition: crashlog_win.cpp:38
CrashLogWindows::CrashLogWindows
CrashLogWindows(EXCEPTION_POINTERS *ep=nullptr)
A crash log is always generated when it's generated.
Definition: crashlog_win.cpp:68
_safe_esp
void * _safe_esp
Stack pointer for use when 'starting' the crash handler.
Definition: crashlog_win.cpp:540
LoadLibraryList
bool LoadLibraryList(Function proc[], const char *dll)
Helper function needed by dynamically loading libraries.
Definition: win32.cpp:56
DebugFileInfo
Definition: crashlog_win.cpp:120
Utf8Encode
size_t Utf8Encode(T buf, WChar c)
Encode a unicode character and place it in the buffer.
Definition: string.cpp:523
CrashLogWindows::crashlog_filename
char crashlog_filename[MAX_PATH]
Buffer for the filename of the crash log.
Definition: crashlog_win.cpp:58
CrashLogWindows::LogOSVersion
char * LogOSVersion(char *buffer, const char *last) const override
Writes OS' version to the buffer.
Definition: crashlog_win.cpp:85
CrashLogWindows::LogStacktrace
char * LogStacktrace(char *buffer, const char *last) const override
Writes the stack trace to the buffer, if there is information about it available.
Definition: crashlog_win.cpp:342
CrashLogWindows::ep
EXCEPTION_POINTERS * ep
Information about the encountered exception.
Definition: crashlog_win.cpp:40
CrashLogWindows::current
static CrashLogWindows * current
Points to the current crash log.
Definition: crashlog_win.cpp:80
GamelogTestEmergency
bool GamelogTestEmergency()
Finds out if current game is a loaded emergency savegame.
Definition: gamelog.cpp:420
convert_to_fs
wchar_t * convert_to_fs(const char *name, wchar_t *system_buf, size_t buflen, bool console_cp)
Convert from OpenTTD's encoding to that of the environment in UNICODE.
Definition: win32.cpp:617
SaveloadCrashWithMissingNewGRFs
bool SaveloadCrashWithMissingNewGRFs()
Did loading the savegame cause a crash? If so, were NewGRFs missing?
Definition: afterload.cpp:364
CrashLog::message
static const char * message
Pointer to the error message.
Definition: crashlog.h:19
CrashLog
Helper class for creating crash logs.
Definition: crashlog.h:16
CrashLog::WriteCrashDump
virtual int WriteCrashDump(char *filename, const char *filename_last) const
Write the (crash) dump to a file.
Definition: crashlog.cpp:383
CrashLogWindows::crashdump_filename
char crashdump_filename[MAX_PATH]
Buffer for the filename of the crash dump.
Definition: crashlog_win.cpp:60
VideoDriver::GetInstance
static VideoDriver * GetInstance()
Get the currently active instance of the video driver.
Definition: video_driver.hpp:168
CrashLogWindows::crashlog
char crashlog[65536]
Buffer for the generated crash log.
Definition: crashlog_win.cpp:56
CrashLog::InitialiseCrashLog
static void InitialiseCrashLog()
Initialiser for crash logs; do the appropriate things so crashes are handled by our crash handler ins...
Definition: crashlog_osx.cpp:254
seprintf
int CDECL seprintf(char *str, const char *last, const char *format,...)
Safer implementation of snprintf; same as snprintf except:
Definition: string.cpp:442
CrashLogWindows::LogModules
char * LogModules(char *buffer, const char *last) const override
Writes the dynamically linked libraries/modules to the buffer, if there is information about it avail...
Definition: crashlog_win.cpp:206
CrashLogWindows::screenshot_filename
char screenshot_filename[MAX_PATH]
Buffer for the filename of the crash screenshot.
Definition: crashlog_win.cpp:62
lengthof
#define lengthof(x)
Return the length of an fixed size array.
Definition: stdafx.h:367
CrashLog::FillCrashLog
char * FillCrashLog(char *buffer, const char *last) const
Fill the crash log buffer with all data of a crash log.
Definition: crashlog.cpp:334
CrashLog::WriteCrashLog
bool WriteCrashLog(const char *buffer, char *filename, const char *filename_last) const
Write the crash log to a file.
Definition: crashlog.cpp:369
CrashLogWindows::LogError
char * LogError(char *buffer, const char *last, const char *message) const override
Writes actually encountered error to the buffer.
Definition: crashlog_win.cpp:103
lastof
#define lastof(x)
Get the last element of an fixed size array.
Definition: stdafx.h:383
OTTD2FS
const wchar_t * OTTD2FS(const char *name, bool console_cp)
Convert from OpenTTD's encoding to wide characters.
Definition: win32.cpp:580
CrashLogWindows::LogRegisters
char * LogRegisters(char *buffer, const char *last) const override
Writes information about the data in the registers, if there is information about it available.
Definition: crashlog_win.cpp:234
AllocaM
#define AllocaM(T, num_elements)
alloca() has to be called in the parent function, so define AllocaM() as a macro
Definition: alloc_func.hpp:132