00001
00002
00003
00004
00005
00006
00007
00008
00009
00012 #include "../../stdafx.h"
00013 #include "../../crashlog.h"
00014 #include "win32.h"
00015 #include "../../core/alloc_func.hpp"
00016 #include "../../core/math_func.hpp"
00017 #include "../../string_func.h"
00018 #include "../../fileio_func.h"
00019 #include "../../strings_func.h"
00020 #include "../../gamelog.h"
00021
00022 #include <windows.h>
00023 #include <signal.h>
00024
00028 class CrashLogWindows : public CrashLog {
00030 EXCEPTION_POINTERS *ep;
00031
00032 char *LogOSVersion(char *buffer, const char *last) const;
00033 char *LogError(char *buffer, const char *last, const char *message) const;
00034 char *LogStacktrace(char *buffer, const char *last) const;
00035 char *LogRegisters(char *buffer, const char *last) const;
00036 char *LogModules(char *buffer, const char *last) const;
00037 public:
00038 #if defined(_MSC_VER)
00039 int WriteCrashDump(char *filename, const char *filename_last) const;
00040 #endif
00041
00043 char crashlog[65536];
00045 char crashlog_filename[MAX_PATH];
00047 char crashdump_filename[MAX_PATH];
00049 char screenshot_filename[MAX_PATH];
00050
00055 CrashLogWindows(EXCEPTION_POINTERS *ep = NULL) :
00056 ep(ep)
00057 {
00058 this->crashlog[0] = '\0';
00059 this->crashlog_filename[0] = '\0';
00060 this->crashdump_filename[0] = '\0';
00061 this->screenshot_filename[0] = '\0';
00062 }
00063
00067 static CrashLogWindows *current;
00068 };
00069
00070 CrashLogWindows *CrashLogWindows::current = NULL;
00071
00072 char *CrashLogWindows::LogOSVersion(char *buffer, const char *last) const
00073 {
00074 _OSVERSIONINFOA os;
00075 os.dwOSVersionInfoSize = sizeof(os);
00076 GetVersionExA(&os);
00077
00078 return buffer + seprintf(buffer, last,
00079 "Operating system:\n"
00080 " Name: Windows\n"
00081 " Release: %d.%d.%d (%s)\n"
00082 " MSVC: %s\n\n",
00083 (int)os.dwMajorVersion,
00084 (int)os.dwMinorVersion,
00085 (int)os.dwBuildNumber,
00086 os.szCSDVersion,
00087 #if defined(_MSC_VER)
00088 "Yes"
00089 #else
00090 "No"
00091 #endif
00092 );
00093
00094 }
00095
00096 char *CrashLogWindows::LogError(char *buffer, const char *last, const char *message) const
00097 {
00098 return buffer + seprintf(buffer, last,
00099 "Crash reason:\n"
00100 " Exception: %.8X\n"
00101 #ifdef _M_AMD64
00102 " Location: %.16IX\n"
00103 #else
00104 " Location: %.8X\n"
00105 #endif
00106 " Message: %s\n\n",
00107 (int)ep->ExceptionRecord->ExceptionCode,
00108 (size_t)ep->ExceptionRecord->ExceptionAddress,
00109 message == NULL ? "<none>" : message
00110 );
00111 }
00112
00113 struct DebugFileInfo {
00114 uint32 size;
00115 uint32 crc32;
00116 SYSTEMTIME file_time;
00117 };
00118
00119 static uint32 *_crc_table;
00120
00121 static void MakeCRCTable(uint32 *table)
00122 {
00123 uint32 crc, poly = 0xEDB88320L;
00124 int i;
00125 int j;
00126
00127 _crc_table = table;
00128
00129 for (i = 0; i != 256; i++) {
00130 crc = i;
00131 for (j = 8; j != 0; j--) {
00132 crc = (crc & 1 ? (crc >> 1) ^ poly : crc >> 1);
00133 }
00134 table[i] = crc;
00135 }
00136 }
00137
00138 static uint32 CalcCRC(byte *data, uint size, uint32 crc)
00139 {
00140 for (; size > 0; size--) {
00141 crc = ((crc >> 8) & 0x00FFFFFF) ^ _crc_table[(crc ^ *data++) & 0xFF];
00142 }
00143 return crc;
00144 }
00145
00146 static void GetFileInfo(DebugFileInfo *dfi, const TCHAR *filename)
00147 {
00148 HANDLE file;
00149 memset(dfi, 0, sizeof(*dfi));
00150
00151 file = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
00152 if (file != INVALID_HANDLE_VALUE) {
00153 byte buffer[1024];
00154 DWORD numread;
00155 uint32 filesize = 0;
00156 FILETIME write_time;
00157 uint32 crc = (uint32)-1;
00158
00159 for (;;) {
00160 if (ReadFile(file, buffer, sizeof(buffer), &numread, NULL) == 0 || numread == 0)
00161 break;
00162 filesize += numread;
00163 crc = CalcCRC(buffer, numread, crc);
00164 }
00165 dfi->size = filesize;
00166 dfi->crc32 = crc ^ (uint32)-1;
00167
00168 if (GetFileTime(file, NULL, NULL, &write_time)) {
00169 FileTimeToSystemTime(&write_time, &dfi->file_time);
00170 }
00171 CloseHandle(file);
00172 }
00173 }
00174
00175
00176 static char *PrintModuleInfo(char *output, const char *last, HMODULE mod)
00177 {
00178 TCHAR buffer[MAX_PATH];
00179 DebugFileInfo dfi;
00180
00181 GetModuleFileName(mod, buffer, MAX_PATH);
00182 GetFileInfo(&dfi, buffer);
00183 output += seprintf(output, last, " %-20s handle: %p size: %d crc: %.8X date: %d-%.2d-%.2d %.2d:%.2d:%.2d\n",
00184 WIDE_TO_MB(buffer),
00185 mod,
00186 dfi.size,
00187 dfi.crc32,
00188 dfi.file_time.wYear,
00189 dfi.file_time.wMonth,
00190 dfi.file_time.wDay,
00191 dfi.file_time.wHour,
00192 dfi.file_time.wMinute,
00193 dfi.file_time.wSecond
00194 );
00195 return output;
00196 }
00197
00198 char *CrashLogWindows::LogModules(char *output, const char *last) const
00199 {
00200 MakeCRCTable(AllocaM(uint32, 256));
00201 BOOL (WINAPI *EnumProcessModules)(HANDLE, HMODULE*, DWORD, LPDWORD);
00202
00203 output += seprintf(output, last, "Module information:\n");
00204
00205 if (LoadLibraryList((Function*)&EnumProcessModules, "psapi.dll\0EnumProcessModules\0\0")) {
00206 HMODULE modules[100];
00207 DWORD needed;
00208 BOOL res;
00209
00210 HANDLE proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
00211 if (proc != NULL) {
00212 res = EnumProcessModules(proc, modules, sizeof(modules), &needed);
00213 CloseHandle(proc);
00214 if (res) {
00215 size_t count = min(needed / sizeof(HMODULE), lengthof(modules));
00216
00217 for (size_t i = 0; i != count; i++) output = PrintModuleInfo(output, last, modules[i]);
00218 return output + seprintf(output, last, "\n");
00219 }
00220 }
00221 }
00222 output = PrintModuleInfo(output, last, NULL);
00223 return output + seprintf(output, last, "\n");
00224 }
00225
00226 char *CrashLogWindows::LogRegisters(char *buffer, const char *last) const
00227 {
00228 buffer += seprintf(buffer, last, "Registers:\n");
00229 #ifdef _M_AMD64
00230 buffer += seprintf(buffer, last,
00231 " RAX: %.16llX RBX: %.16llX RCX: %.16llX RDX: %.16llX\n"
00232 " RSI: %.16llX RDI: %.16llX RBP: %.16llX RSP: %.16llX\n"
00233 " R8: %.16llX R9: %.16llX R10: %.16llX R11: %.16llX\n"
00234 " R12: %.16llX R13: %.16llX R14: %.16llX R15: %.16llX\n"
00235 " RIP: %.16llX EFLAGS: %.8X\n",
00236 ep->ContextRecord->Rax,
00237 ep->ContextRecord->Rbx,
00238 ep->ContextRecord->Rcx,
00239 ep->ContextRecord->Rdx,
00240 ep->ContextRecord->Rsi,
00241 ep->ContextRecord->Rdi,
00242 ep->ContextRecord->Rbp,
00243 ep->ContextRecord->Rsp,
00244 ep->ContextRecord->R8,
00245 ep->ContextRecord->R9,
00246 ep->ContextRecord->R10,
00247 ep->ContextRecord->R11,
00248 ep->ContextRecord->R12,
00249 ep->ContextRecord->R13,
00250 ep->ContextRecord->R14,
00251 ep->ContextRecord->R15,
00252 ep->ContextRecord->Rip,
00253 ep->ContextRecord->EFlags
00254 );
00255 #else
00256 buffer += seprintf(buffer, last,
00257 " EAX: %.8X EBX: %.8X ECX: %.8X EDX: %.8X\n"
00258 " ESI: %.8X EDI: %.8X EBP: %.8X ESP: %.8X\n"
00259 " EIP: %.8X EFLAGS: %.8X\n",
00260 (int)ep->ContextRecord->Eax,
00261 (int)ep->ContextRecord->Ebx,
00262 (int)ep->ContextRecord->Ecx,
00263 (int)ep->ContextRecord->Edx,
00264 (int)ep->ContextRecord->Esi,
00265 (int)ep->ContextRecord->Edi,
00266 (int)ep->ContextRecord->Ebp,
00267 (int)ep->ContextRecord->Esp,
00268 (int)ep->ContextRecord->Eip,
00269 (int)ep->ContextRecord->EFlags
00270 );
00271 #endif
00272
00273 buffer += seprintf(buffer, last, "\n Bytes at instruction pointer:\n");
00274 #ifdef _M_AMD64
00275 byte *b = (byte*)ep->ContextRecord->Rip;
00276 #else
00277 byte *b = (byte*)ep->ContextRecord->Eip;
00278 #endif
00279 for (int i = 0; i != 24; i++) {
00280 if (IsBadReadPtr(b, 1)) {
00281 buffer += seprintf(buffer, last, " ??");
00282 } else {
00283 buffer += seprintf(buffer, last, " %.2X", *b);
00284 }
00285 b++;
00286 }
00287 return buffer + seprintf(buffer, last, "\n\n");
00288 }
00289
00290 char *CrashLogWindows::LogStacktrace(char *buffer, const char *last) const
00291 {
00292 buffer += seprintf(buffer, last, "Stack trace:\n");
00293 #ifdef _M_AMD64
00294 uint32 *b = (uint32*)ep->ContextRecord->Rsp;
00295 #else
00296 uint32 *b = (uint32*)ep->ContextRecord->Esp;
00297 #endif
00298 for (int j = 0; j != 24; j++) {
00299 for (int i = 0; i != 8; i++) {
00300 if (IsBadReadPtr(b, sizeof(uint32))) {
00301 buffer += seprintf(buffer, last, " ????????");
00302 } else {
00303 buffer += seprintf(buffer, last, " %.8X", *b);
00304 }
00305 b++;
00306 }
00307 buffer += seprintf(buffer, last, "\n");
00308 }
00309 return buffer + seprintf(buffer, last, "\n");
00310 }
00311
00312 #if defined(_MSC_VER)
00313 #include <dbghelp.h>
00314
00315 int CrashLogWindows::WriteCrashDump(char *filename, const char *filename_last) const
00316 {
00317 int ret = 0;
00318 HMODULE dbghelp = LoadLibrary(_T("dbghelp.dll"));
00319 if (dbghelp != NULL) {
00320 typedef BOOL (WINAPI *MiniDumpWriteDump_t)(HANDLE, DWORD, HANDLE,
00321 MINIDUMP_TYPE,
00322 CONST PMINIDUMP_EXCEPTION_INFORMATION,
00323 CONST PMINIDUMP_USER_STREAM_INFORMATION,
00324 CONST PMINIDUMP_CALLBACK_INFORMATION);
00325 MiniDumpWriteDump_t funcMiniDumpWriteDump = (MiniDumpWriteDump_t)GetProcAddress(dbghelp, "MiniDumpWriteDump");
00326 if (funcMiniDumpWriteDump != NULL) {
00327 seprintf(filename, filename_last, "%scrash.dmp", _personal_dir);
00328 HANDLE file = CreateFile(OTTD2FS(filename), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
00329 HANDLE proc = GetCurrentProcess();
00330 DWORD procid = GetCurrentProcessId();
00331 MINIDUMP_EXCEPTION_INFORMATION mdei;
00332 MINIDUMP_USER_STREAM userstream;
00333 MINIDUMP_USER_STREAM_INFORMATION musi;
00334
00335 userstream.Type = LastReservedStream + 1;
00336 userstream.Buffer = (void*)this->crashlog;
00337 userstream.BufferSize = (ULONG)strlen(this->crashlog) + 1;
00338
00339 musi.UserStreamCount = 1;
00340 musi.UserStreamArray = &userstream;
00341
00342 mdei.ThreadId = GetCurrentThreadId();
00343 mdei.ExceptionPointers = ep;
00344 mdei.ClientPointers = false;
00345
00346 funcMiniDumpWriteDump(proc, procid, file, MiniDumpWithDataSegs, &mdei, &musi, NULL);
00347 ret = 1;
00348 } else {
00349 ret = -1;
00350 }
00351 FreeLibrary(dbghelp);
00352 }
00353 return ret;
00354 }
00355 #endif
00356
00357 extern bool CloseConsoleLogIfActive();
00358 static void ShowCrashlogWindow();
00359
00364 void *_safe_esp = NULL;
00365
00366 static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
00367 {
00368 if (CrashLogWindows::current != NULL) {
00369 CrashLog::AfterCrashLogCleanup();
00370 ExitProcess(2);
00371 }
00372
00373 if (GamelogTestEmergency()) {
00374 static const TCHAR _emergency_crash[] =
00375 _T("A serious fault condition occured in the game. The game will shut down.\n")
00376 _T("As you loaded an emergency savegame no crash information will be generated.\n");
00377 MessageBox(NULL, _emergency_crash, _T("Fatal Application Failure"), MB_ICONERROR);
00378 ExitProcess(3);
00379 }
00380
00381 CrashLogWindows *log = new CrashLogWindows(ep);
00382 CrashLogWindows::current = log;
00383 log->FillCrashLog(log->crashlog, lastof(log->crashlog));
00384 log->WriteCrashLog(log->crashlog, log->crashlog_filename, lastof(log->crashlog_filename));
00385 log->WriteCrashDump(log->crashdump_filename, lastof(log->crashdump_filename));
00386 log->WriteScreenshot(log->screenshot_filename, lastof(log->screenshot_filename));
00387
00388
00389 CloseConsoleLogIfActive();
00390
00391 if (_safe_esp) {
00392 #ifdef _M_AMD64
00393 ep->ContextRecord->Rip = (DWORD64)ShowCrashlogWindow;
00394 ep->ContextRecord->Rsp = (DWORD64)_safe_esp;
00395 #else
00396 ep->ContextRecord->Eip = (DWORD)ShowCrashlogWindow;
00397 ep->ContextRecord->Esp = (DWORD)_safe_esp;
00398 #endif
00399 return EXCEPTION_CONTINUE_EXECUTION;
00400 }
00401
00402 CrashLog::AfterCrashLogCleanup();
00403 return EXCEPTION_EXECUTE_HANDLER;
00404 }
00405
00406 #ifdef _M_AMD64
00407 extern "C" void *_get_safe_esp();
00408 #endif
00409
00410 static void CDECL CustomAbort(int signal)
00411 {
00412 RaiseException(0xE1212012, 0, 0, NULL);
00413 }
00414
00415 void CrashLog::InitialiseCrashLog()
00416 {
00417 #if defined(_MSC_VER)
00418 #ifdef _M_AMD64
00419 _safe_esp = _get_safe_esp();
00420 #else
00421 _asm {
00422 mov _safe_esp, esp
00423 }
00424 #endif
00425 #else
00426 asm("movl %esp, __safe_esp");
00427 #endif
00428
00429
00430 signal(SIGABRT, CustomAbort);
00431 #if defined(_MSC_VER)
00432
00433 _set_abort_behavior(0, _WRITE_ABORT_MSG);
00434 #endif
00435 SetUnhandledExceptionFilter(ExceptionHandler);
00436 }
00437
00438
00439
00440 static bool _expanded;
00441
00442 static const TCHAR _crash_desc[] =
00443 _T("A serious fault condition occured in the game. The game will shut down.\n")
00444 _T("Please send the crash information and the crash.dmp file (if any) to the developers.\n")
00445 _T("This will greatly help debugging. The correct place to do this is http:
00446 _T("The information contained in the report is displayed below.\n")
00447 _T("Press \"Emergency save\" to attempt saving the game. Generated file(s):\n")
00448 _T("%s");
00449
00450 static const TCHAR _save_succeeded[] =
00451 _T("Emergency save succeeded.\nIts location is '%s'.\n")
00452 _T("Be aware that critical parts of the internal game state may have become ")
00453 _T("corrupted. The saved game is not guaranteed to work.");
00454
00455 static const TCHAR * const _expand_texts[] = {_T("S&how report >>"), _T("&Hide report <<") };
00456
00457 static void SetWndSize(HWND wnd, int mode)
00458 {
00459 RECT r, r2;
00460
00461 GetWindowRect(wnd, &r);
00462 SetDlgItemText(wnd, 15, _expand_texts[mode == 1]);
00463
00464 if (mode >= 0) {
00465 GetWindowRect(GetDlgItem(wnd, 11), &r2);
00466 int offs = r2.bottom - r2.top + 10;
00467 if (!mode) offs = -offs;
00468 SetWindowPos(wnd, HWND_TOPMOST, 0, 0,
00469 r.right - r.left, r.bottom - r.top + offs, SWP_NOMOVE | SWP_NOZORDER);
00470 } else {
00471 SetWindowPos(wnd, HWND_TOPMOST,
00472 (GetSystemMetrics(SM_CXSCREEN) - (r.right - r.left)) / 2,
00473 (GetSystemMetrics(SM_CYSCREEN) - (r.bottom - r.top)) / 2,
00474 0, 0, SWP_NOSIZE);
00475 }
00476 }
00477
00478 static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
00479 {
00480 switch (msg) {
00481 case WM_INITDIALOG: {
00482 #if defined(UNICODE)
00483
00484
00485 wchar_t crash_msgW[lengthof(CrashLogWindows::current->crashlog)];
00486 #endif
00487
00488 const char *unix_nl = CrashLogWindows::current->crashlog;
00489 char dos_nl[lengthof(CrashLogWindows::current->crashlog)];
00490 char *p = dos_nl;
00491 WChar c;
00492 while ((c = Utf8Consume(&unix_nl)) && p < lastof(dos_nl) - 4) {
00493 if (c == '\n') p += Utf8Encode(p, '\r');
00494 p += Utf8Encode(p, c);
00495 }
00496 *p = '\0';
00497
00498
00499 size_t len = _tcslen(_crash_desc) + 2;
00500 len += _tcslen(OTTD2FS(CrashLogWindows::current->crashlog_filename)) + 2;
00501 len += _tcslen(OTTD2FS(CrashLogWindows::current->crashdump_filename)) + 2;
00502 len += _tcslen(OTTD2FS(CrashLogWindows::current->screenshot_filename)) + 1;
00503
00504 TCHAR *text = AllocaM(TCHAR, len);
00505 _sntprintf(text, len, _crash_desc, OTTD2FS(CrashLogWindows::current->crashlog_filename));
00506 if (OTTD2FS(CrashLogWindows::current->crashdump_filename)[0] != _T('\0')) {
00507 _tcscat(text, _T("\n"));
00508 _tcscat(text, OTTD2FS(CrashLogWindows::current->crashdump_filename));
00509 }
00510 if (OTTD2FS(CrashLogWindows::current->screenshot_filename)[0] != _T('\0')) {
00511 _tcscat(text, _T("\n"));
00512 _tcscat(text, OTTD2FS(CrashLogWindows::current->screenshot_filename));
00513 }
00514
00515 SetDlgItemText(wnd, 10, text);
00516 SetDlgItemText(wnd, 11, MB_TO_WIDE_BUFFER(dos_nl, crash_msgW, lengthof(crash_msgW)));
00517 SendDlgItemMessage(wnd, 11, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), FALSE);
00518 SetWndSize(wnd, -1);
00519 } return TRUE;
00520 case WM_COMMAND:
00521 switch (wParam) {
00522 case 12:
00523 CrashLog::AfterCrashLogCleanup();
00524 ExitProcess(2);
00525 case 13:
00526 char filename[MAX_PATH];
00527 if (CrashLogWindows::current->WriteSavegame(filename, lastof(filename))) {
00528 size_t len = _tcslen(_save_succeeded) + _tcslen(OTTD2FS(filename)) + 1;
00529 TCHAR *text = AllocaM(TCHAR, len);
00530 _sntprintf(text, len, _save_succeeded, OTTD2FS(filename));
00531 MessageBox(wnd, text, _T("Save successful"), MB_ICONINFORMATION);
00532 } else {
00533 MessageBox(wnd, _T("Save failed"), _T("Save failed"), MB_ICONINFORMATION);
00534 }
00535 break;
00536 case 15:
00537 _expanded ^= 1;
00538 SetWndSize(wnd, _expanded);
00539 break;
00540 }
00541 return TRUE;
00542 case WM_CLOSE:
00543 CrashLog::AfterCrashLogCleanup();
00544 ExitProcess(2);
00545 }
00546
00547 return FALSE;
00548 }
00549
00550 static void ShowCrashlogWindow()
00551 {
00552 ShowCursor(TRUE);
00553 ShowWindow(GetActiveWindow(), FALSE);
00554 DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(100), NULL, CrashDialogFunc);
00555 }