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 "Registers:\n"
00232 " RAX: %.16llX RBX: %.16llX RCX: %.16llX RDX: %.16llX\n"
00233 " RSI: %.16llX RDI: %.16llX RBP: %.16llX RSP: %.16llX\n"
00234 " R8: %.16llX R9: %.16llX R10: %.16llX R11: %.16llX\n"
00235 " R12: %.16llX R13: %.16llX R14: %.16llX R15: %.16llX\n"
00236 " RIP: %.16llX EFLAGS: %.8X\n",
00237 ep->ContextRecord->Rax,
00238 ep->ContextRecord->Rbx,
00239 ep->ContextRecord->Rcx,
00240 ep->ContextRecord->Rdx,
00241 ep->ContextRecord->Rsi,
00242 ep->ContextRecord->Rdi,
00243 ep->ContextRecord->Rbp,
00244 ep->ContextRecord->Rsp,
00245 ep->ContextRecord->R8,
00246 ep->ContextRecord->R9,
00247 ep->ContextRecord->R10,
00248 ep->ContextRecord->R11,
00249 ep->ContextRecord->R12,
00250 ep->ContextRecord->R13,
00251 ep->ContextRecord->R14,
00252 ep->ContextRecord->R15,
00253 ep->ContextRecord->Rip,
00254 ep->ContextRecord->EFlags
00255 );
00256 #else
00257 buffer += seprintf(buffer, last,
00258 " EAX: %.8X EBX: %.8X ECX: %.8X EDX: %.8X\n"
00259 " ESI: %.8X EDI: %.8X EBP: %.8X ESP: %.8X\n"
00260 " EIP: %.8X EFLAGS: %.8X\n",
00261 (int)ep->ContextRecord->Eax,
00262 (int)ep->ContextRecord->Ebx,
00263 (int)ep->ContextRecord->Ecx,
00264 (int)ep->ContextRecord->Edx,
00265 (int)ep->ContextRecord->Esi,
00266 (int)ep->ContextRecord->Edi,
00267 (int)ep->ContextRecord->Ebp,
00268 (int)ep->ContextRecord->Esp,
00269 (int)ep->ContextRecord->Eip,
00270 (int)ep->ContextRecord->EFlags
00271 );
00272 #endif
00273
00274 buffer += seprintf(buffer, last, "\n Bytes at instruction pointer:\n");
00275 #ifdef _M_AMD64
00276 byte *b = (byte*)ep->ContextRecord->Rip;
00277 #else
00278 byte *b = (byte*)ep->ContextRecord->Eip;
00279 #endif
00280 for (int i = 0; i != 24; i++) {
00281 if (IsBadReadPtr(b, 1)) {
00282 buffer += seprintf(buffer, last, " ??");
00283 } else {
00284 buffer += seprintf(buffer, last, " %.2X", *b);
00285 }
00286 b++;
00287 }
00288 return buffer + seprintf(buffer, last, "\n\n");
00289 }
00290
00291 char *CrashLogWindows::LogStacktrace(char *buffer, const char *last) const
00292 {
00293 buffer += seprintf(buffer, last, "Stack trace:\n");
00294 #ifdef _M_AMD64
00295 uint32 *b = (uint32*)ep->ContextRecord->Rsp;
00296 #else
00297 uint32 *b = (uint32*)ep->ContextRecord->Esp;
00298 #endif
00299 for (int j = 0; j != 24; j++) {
00300 for (int i = 0; i != 8; i++) {
00301 if (IsBadReadPtr(b, sizeof(uint32))) {
00302 buffer += seprintf(buffer, last, " ????????");
00303 } else {
00304 buffer += seprintf(buffer, last, " %.8X", *b);
00305 }
00306 b++;
00307 }
00308 buffer += seprintf(buffer, last, "\n");
00309 }
00310 return buffer + seprintf(buffer, last, "\n");
00311 }
00312
00313 #if defined(_MSC_VER)
00314 #include <dbghelp.h>
00315
00316 int CrashLogWindows::WriteCrashDump(char *filename, const char *filename_last) const
00317 {
00318 int ret = 0;
00319 HMODULE dbghelp = LoadLibrary(_T("dbghelp.dll"));
00320 if (dbghelp != NULL) {
00321 typedef BOOL (WINAPI *MiniDumpWriteDump_t)(HANDLE, DWORD, HANDLE,
00322 MINIDUMP_TYPE,
00323 CONST PMINIDUMP_EXCEPTION_INFORMATION,
00324 CONST PMINIDUMP_USER_STREAM_INFORMATION,
00325 CONST PMINIDUMP_CALLBACK_INFORMATION);
00326 MiniDumpWriteDump_t funcMiniDumpWriteDump = (MiniDumpWriteDump_t)GetProcAddress(dbghelp, "MiniDumpWriteDump");
00327 if (funcMiniDumpWriteDump != NULL) {
00328 seprintf(filename, filename_last, "%scrash.dmp", _personal_dir);
00329 HANDLE file = CreateFile(OTTD2FS(filename), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
00330 HANDLE proc = GetCurrentProcess();
00331 DWORD procid = GetCurrentProcessId();
00332 MINIDUMP_EXCEPTION_INFORMATION mdei;
00333 MINIDUMP_USER_STREAM userstream;
00334 MINIDUMP_USER_STREAM_INFORMATION musi;
00335
00336 userstream.Type = LastReservedStream + 1;
00337 userstream.Buffer = (void*)this->crashlog;
00338 userstream.BufferSize = (ULONG)strlen(this->crashlog) + 1;
00339
00340 musi.UserStreamCount = 1;
00341 musi.UserStreamArray = &userstream;
00342
00343 mdei.ThreadId = GetCurrentThreadId();
00344 mdei.ExceptionPointers = ep;
00345 mdei.ClientPointers = false;
00346
00347 funcMiniDumpWriteDump(proc, procid, file, MiniDumpWithDataSegs, &mdei, &musi, NULL);
00348 ret = 1;
00349 } else {
00350 ret = -1;
00351 }
00352 FreeLibrary(dbghelp);
00353 }
00354 return ret;
00355 }
00356 #endif
00357
00358 extern bool CloseConsoleLogIfActive();
00359 static void ShowCrashlogWindow();
00360
00365 void *_safe_esp = NULL;
00366
00367 static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *ep)
00368 {
00369 if (CrashLogWindows::current != NULL) {
00370 CrashLog::AfterCrashLogCleanup();
00371 ExitProcess(2);
00372 }
00373
00374 if (GamelogTestEmergency()) {
00375 static const TCHAR _emergency_crash[] =
00376 _T("A serious fault condition occured in the game. The game will shut down.\n")
00377 _T("As you loaded an emergency savegame no crash information will be generated.\n");
00378 MessageBox(NULL, _emergency_crash, _T("Fatal Application Failure"), MB_ICONERROR);
00379 ExitProcess(3);
00380 }
00381
00382 CrashLogWindows *log = new CrashLogWindows(ep);
00383 CrashLogWindows::current = log;
00384 log->FillCrashLog(log->crashlog, lastof(log->crashlog));
00385 log->WriteCrashLog(log->crashlog, log->crashlog_filename, lastof(log->crashlog_filename));
00386 log->WriteCrashDump(log->crashdump_filename, lastof(log->crashdump_filename));
00387 log->WriteScreenshot(log->screenshot_filename, lastof(log->screenshot_filename));
00388
00389
00390 CloseConsoleLogIfActive();
00391
00392 if (_safe_esp) {
00393 #ifdef _M_AMD64
00394 ep->ContextRecord->Rip = (DWORD64)ShowCrashlogWindow;
00395 ep->ContextRecord->Rsp = (DWORD64)_safe_esp;
00396 #else
00397 ep->ContextRecord->Eip = (DWORD)ShowCrashlogWindow;
00398 ep->ContextRecord->Esp = (DWORD)_safe_esp;
00399 #endif
00400 return EXCEPTION_CONTINUE_EXECUTION;
00401 }
00402
00403 CrashLog::AfterCrashLogCleanup();
00404 return EXCEPTION_EXECUTE_HANDLER;
00405 }
00406
00407 #ifdef _M_AMD64
00408 extern "C" void *_get_safe_esp();
00409 #endif
00410
00411 static void CDECL CustomAbort(int signal)
00412 {
00413 RaiseException(0xE1212012, 0, 0, NULL);
00414 }
00415
00416 void CrashLog::InitialiseCrashLog()
00417 {
00418 #if defined(_MSC_VER)
00419 #ifdef _M_AMD64
00420 _safe_esp = _get_safe_esp();
00421 #else
00422 _asm {
00423 mov _safe_esp, esp
00424 }
00425 #endif
00426 #else
00427 asm("movl %esp, __safe_esp");
00428 #endif
00429
00430
00431 signal(SIGABRT, CustomAbort);
00432 #if defined(_MSC_VER)
00433
00434 _set_abort_behavior(0, _WRITE_ABORT_MSG);
00435 #endif
00436 SetUnhandledExceptionFilter(ExceptionHandler);
00437 }
00438
00439
00440
00441 static bool _expanded;
00442
00443 static const TCHAR _crash_desc[] =
00444 _T("A serious fault condition occured in the game. The game will shut down.\n")
00445 _T("Please send the crash information and the crash.dmp file (if any) to the developers.\n")
00446 _T("This will greatly help debugging. The correct place to do this is http:
00447 _T("The information contained in the report is displayed below.\n")
00448 _T("Press \"Emergency save\" to attempt saving the game. Generated file(s):\n")
00449 _T("%s");
00450
00451 static const TCHAR _save_succeeded[] =
00452 _T("Emergency save succeeded.\nIts location is '%s'.\n")
00453 _T("Be aware that critical parts of the internal game state may have become ")
00454 _T("corrupted. The saved game is not guaranteed to work.");
00455
00456 static const TCHAR * const _expand_texts[] = {_T("S&how report >>"), _T("&Hide report <<") };
00457
00458 static void SetWndSize(HWND wnd, int mode)
00459 {
00460 RECT r, r2;
00461
00462 GetWindowRect(wnd, &r);
00463 SetDlgItemText(wnd, 15, _expand_texts[mode == 1]);
00464
00465 if (mode >= 0) {
00466 GetWindowRect(GetDlgItem(wnd, 11), &r2);
00467 int offs = r2.bottom - r2.top + 10;
00468 if (!mode) offs = -offs;
00469 SetWindowPos(wnd, HWND_TOPMOST, 0, 0,
00470 r.right - r.left, r.bottom - r.top + offs, SWP_NOMOVE | SWP_NOZORDER);
00471 } else {
00472 SetWindowPos(wnd, HWND_TOPMOST,
00473 (GetSystemMetrics(SM_CXSCREEN) - (r.right - r.left)) / 2,
00474 (GetSystemMetrics(SM_CYSCREEN) - (r.bottom - r.top)) / 2,
00475 0, 0, SWP_NOSIZE);
00476 }
00477 }
00478
00479 static INT_PTR CALLBACK CrashDialogFunc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
00480 {
00481 switch (msg) {
00482 case WM_INITDIALOG: {
00483 #if defined(UNICODE)
00484
00485
00486 wchar_t crash_msgW[lengthof(CrashLogWindows::current->crashlog)];
00487 #endif
00488
00489 const char *unix_nl = CrashLogWindows::current->crashlog;
00490 char dos_nl[lengthof(CrashLogWindows::current->crashlog)];
00491 char *p = dos_nl;
00492 WChar c;
00493 while ((c = Utf8Consume(&unix_nl)) && p < lastof(dos_nl) - 4) {
00494 if (c == '\n') p += Utf8Encode(p, '\r');
00495 p += Utf8Encode(p, c);
00496 }
00497 *p = '\0';
00498
00499
00500 size_t len = _tcslen(_crash_desc) + 2;
00501 len += _tcslen(OTTD2FS(CrashLogWindows::current->crashlog_filename)) + 2;
00502 len += _tcslen(OTTD2FS(CrashLogWindows::current->crashdump_filename)) + 2;
00503 len += _tcslen(OTTD2FS(CrashLogWindows::current->screenshot_filename)) + 1;
00504
00505 TCHAR *text = AllocaM(TCHAR, len);
00506 _sntprintf(text, len, _crash_desc, OTTD2FS(CrashLogWindows::current->crashlog_filename));
00507 if (OTTD2FS(CrashLogWindows::current->crashdump_filename)[0] != _T('\0')) {
00508 _tcscat(text, _T("\n"));
00509 _tcscat(text, OTTD2FS(CrashLogWindows::current->crashdump_filename));
00510 }
00511 if (OTTD2FS(CrashLogWindows::current->screenshot_filename)[0] != _T('\0')) {
00512 _tcscat(text, _T("\n"));
00513 _tcscat(text, OTTD2FS(CrashLogWindows::current->screenshot_filename));
00514 }
00515
00516 SetDlgItemText(wnd, 10, text);
00517 SetDlgItemText(wnd, 11, MB_TO_WIDE_BUFFER(dos_nl, crash_msgW, lengthof(crash_msgW)));
00518 SendDlgItemMessage(wnd, 11, WM_SETFONT, (WPARAM)GetStockObject(ANSI_FIXED_FONT), FALSE);
00519 SetWndSize(wnd, -1);
00520 } return TRUE;
00521 case WM_COMMAND:
00522 switch (wParam) {
00523 case 12:
00524 CrashLog::AfterCrashLogCleanup();
00525 ExitProcess(2);
00526 case 13:
00527 char filename[MAX_PATH];
00528 if (CrashLogWindows::current->WriteSavegame(filename, lastof(filename))) {
00529 size_t len = _tcslen(_save_succeeded) + _tcslen(OTTD2FS(filename)) + 1;
00530 TCHAR *text = AllocaM(TCHAR, len);
00531 _sntprintf(text, len, _save_succeeded, OTTD2FS(filename));
00532 MessageBox(wnd, text, _T("Save successful"), MB_ICONINFORMATION);
00533 } else {
00534 MessageBox(wnd, _T("Save failed"), _T("Save failed"), MB_ICONINFORMATION);
00535 }
00536 break;
00537 case 15:
00538 _expanded ^= 1;
00539 SetWndSize(wnd, _expanded);
00540 break;
00541 }
00542 return TRUE;
00543 case WM_CLOSE:
00544 CrashLog::AfterCrashLogCleanup();
00545 ExitProcess(2);
00546 }
00547
00548 return FALSE;
00549 }
00550
00551 static void ShowCrashlogWindow()
00552 {
00553 ShowCursor(TRUE);
00554 ShowWindow(GetActiveWindow(), FALSE);
00555 DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(100), NULL, CrashDialogFunc);
00556 }