// ExceptionHandler.cpp Version 1.4 // // Copyright 1998 Bruce Dawson // // This source file contains the exception handler for recording error // information after crashes. See ExceptionHandler.h for information // on how to hook it in. // // Author: Bruce Dawson // brucedawson@cygnus-software.com // // Modified by: Hans Dietrich // hdietrich2@hotmail.com // // Version 1.4: - Added invocation of XCrashReport.exe // // Version 1.3: - Added minidump output // // Version 1.1: - reformatted output for XP-like error report // - added ascii output to stack dump // // A paper by the original author can be found at: // http://www.cygnus-software.com/papers/release_debugging.html // /////////////////////////////////////////////////////////////////////////////// // This is derived from code by Hans Deitrich, found at: // http://www.codeproject.com/debug/XCrashReportPt4.asp // Modifications for P4Win: // adapted to build with UNICODE // tries first to load dbghelp.dll from executable directory // this allows distributing an updated version of this dll // places files in new folder alongside app // new folder is called "error_reports" // under that folder, a new folder is created for each error report // foldername is concatenation of appname and date // added registry saving here, adapted from XCrashReport // Disable warnings generated by the Windows header files. #pragma warning(disable : 4514) #pragma warning(disable : 4201) #define _WIN32_WINDOWS 0x0500 // for IsDebuggerPresent // does not require MFC; use 'Not using precompiled headers' #include <windows.h> #include <tchar.h> #include <dbghelp.h> #include "GetWinVer.h" #include "MiniVersion.h" #include "CrashFileNames.h" #include "WriteRegistry.h" #ifndef _countof #define _countof(array) (sizeof(array)/sizeof(array[0])) #endif const int NumCodeBytes = 16; // Number of code bytes to record. const int MaxStackDump = 3072; // Maximum number of DWORDS in stack dumps. const int StackColumns = 4; // Number of columns in stack dump. #define ONEK 1024 #define SIXTYFOURK (64*ONEK) #define ONEM (ONEK*ONEK) #define ONEG (ONEK*ONEK*ONEK) static bool allowER = false; void EnableErrorRecording(bool allow) { allowER = allow; } /////////////////////////////////////////////////////////////////////////////// // lstrrchr (avoid the C Runtime ) static TCHAR * lstrrchr(LPCTSTR string, int ch) { TCHAR *start = (TCHAR *)string; while (*string++) /* find end of string */ ; /* search towards front */ while (--string != start && *string != (TCHAR) ch) ; if (*string == (TCHAR) ch) /* char found ? */ return (TCHAR *)string; return NULL; } /////////////////////////////////////////////////////////////////////////////// // hprintf behaves similarly to printf, with a few vital differences. // It uses wvsprintf to do the formatting, which is a system routine, // thus avoiding C run time interactions. For similar reasons it // uses WriteFile rather than fwrite. // The one limitation that this imposes is that wvsprintf, and // therefore hprintf, cannot handle floating point numbers. // Too many calls to WriteFile can take a long time, causing // confusing delays when programs crash. Therefore I implemented // a simple buffering scheme for hprintf #define HPRINTF_BUFFER_SIZE (8*1024) // must be at least 2048 static TCHAR hprintf_buffer[HPRINTF_BUFFER_SIZE]; // wvsprintf never prints more than one K. #ifdef UNICODE static char hprintf_bufferA[HPRINTF_BUFFER_SIZE*2]; #endif static int hprintf_index = 0; /////////////////////////////////////////////////////////////////////////////// // hflush static void hflush(HANDLE LogFile, int size = 0) { if (hprintf_index > size) { DWORD NumBytes; #ifdef UNICODE NumBytes = WideCharToMultiByte(CP_ACP, 0, hprintf_buffer, lstrlen(hprintf_buffer), hprintf_bufferA, sizeof(hprintf_bufferA), NULL, NULL); if(NumBytes) WriteFile(LogFile, hprintf_bufferA, NumBytes - 1, &NumBytes, 0); #else WriteFile(LogFile, hprintf_buffer, lstrlen(hprintf_buffer), &NumBytes, 0); #endif hprintf_index = 0; } } /////////////////////////////////////////////////////////////////////////////// // hprintf static void hprintf(HANDLE LogFile, LPCTSTR Format, ...) { hflush(LogFile, (HPRINTF_BUFFER_SIZE-1024)); va_list arglist; va_start( arglist, Format); hprintf_index += wvsprintf(&hprintf_buffer[hprintf_index], Format, arglist); va_end( arglist); } typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType, CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam ); static LPCTSTR dbgHelpDll = _T("DBGHELP.DLL"); static HMODULE LoadDbgHelpDll() { // try to load dbghelp.dll. Just using LoadLibrary will try to match to // a loaded module. This might get us an old one, like if running on // Win2k, which we have replaced when we installed. So first, look in // the same dir as this .exe. HMODULE hDll = NULL; TCHAR path[_MAX_PATH]; if (GetModuleFileName( NULL, path, _MAX_PATH )) { TCHAR *sep = lstrrchr( path, '\\' ); if (sep) { lstrcpy( sep+1, dbgHelpDll ); hDll = ::LoadLibrary( path ); } } if (hDll==NULL) { // couldn't load one we installed, so take what we can get // and hope for the best hDll = ::LoadLibrary( dbgHelpDll ); } return hDll; } /////////////////////////////////////////////////////////////////////////////// // DumpMiniDump static void DumpMiniDump(HANDLE hFile, PEXCEPTION_POINTERS excpInfo) { if (excpInfo == NULL) { // Generate exception to get proper context in dump __try { OutputDebugString(_T("raising exception\r\n")); RaiseException(EXCEPTION_BREAKPOINT, 0, 0, NULL); } __except(DumpMiniDump(hFile, GetExceptionInformation()), EXCEPTION_CONTINUE_EXECUTION) { } } else { OutputDebugString(_T("writing minidump\r\n")); MINIDUMP_EXCEPTION_INFORMATION eInfo; eInfo.ThreadId = GetCurrentThreadId(); eInfo.ExceptionPointers = excpInfo; eInfo.ClientPointers = FALSE; HMODULE hDll = LoadDbgHelpDll(); if(!hDll) { OutputDebugString(_T("dbghelp.dll not found\r\n")); return; } MINIDUMPWRITEDUMP pDump = (MINIDUMPWRITEDUMP)::GetProcAddress( hDll, "MiniDumpWriteDump" ); if (!pDump) { OutputDebugString(_T("dbghelp.dll too old\r\n")); return; } // note: MiniDumpWithIndirectlyReferencedMemory does not work on Win98 pDump( GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, excpInfo ? &eInfo : NULL, NULL, NULL); } } /////////////////////////////////////////////////////////////////////////////// // FormatTime // // Format the specified FILETIME to output in a human readable format, // without using the C run time. static void FormatTime(LPTSTR output, FILETIME TimeToPrint) { output[0] = _T('\0'); WORD Date, Time; if (FileTimeToLocalFileTime(&TimeToPrint, &TimeToPrint) && FileTimeToDosDateTime(&TimeToPrint, &Date, &Time)) { wsprintf(output, _T("%d/%d/%d %02d:%02d:%02d"), (Date / 32) & 15, Date & 31, (Date / 512) + 1980, (Time >> 11), (Time >> 5) & 0x3F, (Time & 0x1F) * 2); } } /////////////////////////////////////////////////////////////////////////////// // DumpModuleInfo // // Print information about a code module (DLL or EXE) such as its size, // location, time stamp, etc. static bool DumpModuleInfo(HANDLE LogFile, HINSTANCE ModuleHandle, int nModuleNo) { bool rc = false; TCHAR szModName[MAX_PATH*2]; ZeroMemory(szModName, sizeof(szModName)); __try { if (GetModuleFileName(ModuleHandle, szModName, sizeof(szModName)-2) > 0) { // If GetModuleFileName returns greater than zero then this must // be a valid code module address. Therefore we can try to walk // our way through its structures to find the link time stamp. IMAGE_DOS_HEADER *DosHeader = (IMAGE_DOS_HEADER*)ModuleHandle; if (IMAGE_DOS_SIGNATURE != DosHeader->e_magic) return false; IMAGE_NT_HEADERS *NTHeader = (IMAGE_NT_HEADERS*)((TCHAR *)DosHeader + DosHeader->e_lfanew); if (IMAGE_NT_SIGNATURE != NTHeader->Signature) return false; // open the code module file so that we can get its file date and size HANDLE ModuleFile = CreateFile(szModName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); TCHAR TimeBuffer[100]; TimeBuffer[0] = _T('\0'); DWORD FileSize = 0; if (ModuleFile != INVALID_HANDLE_VALUE) { FileSize = GetFileSize(ModuleFile, 0); FILETIME LastWriteTime; if (GetFileTime(ModuleFile, 0, 0, &LastWriteTime)) { FormatTime(TimeBuffer, LastWriteTime); } CloseHandle(ModuleFile); } hprintf(LogFile, _T("Module %d\r\n"), nModuleNo); hprintf(LogFile, _T("%s\r\n"), szModName); hprintf(LogFile, _T("Image Base: 0x%08x Image Size: 0x%08x\r\n"), NTHeader->OptionalHeader.ImageBase, NTHeader->OptionalHeader.SizeOfImage), hprintf(LogFile, _T("Checksum: 0x%08x Time Stamp: 0x%08x\r\n"), NTHeader->OptionalHeader.CheckSum, NTHeader->FileHeader.TimeDateStamp); hprintf(LogFile, _T("File Size: %-10d File Time: %s\r\n"), FileSize, TimeBuffer); hprintf(LogFile, _T("Version Information:\r\n")); CMiniVersion ver(szModName); TCHAR szBuf[200]; WORD dwBuf[4]; ver.GetCompanyName(szBuf, sizeof(szBuf)-1); hprintf(LogFile, _T(" Company: %s\r\n"), szBuf); ver.GetProductName(szBuf, sizeof(szBuf)-1); hprintf(LogFile, _T(" Product: %s\r\n"), szBuf); ver.GetFileDescription(szBuf, sizeof(szBuf)-1); hprintf(LogFile, _T(" FileDesc: %s\r\n"), szBuf); ver.GetFileVersion(dwBuf); hprintf(LogFile, _T(" FileVer: %d.%d.%d.%d\r\n"), dwBuf[0], dwBuf[1], dwBuf[2], dwBuf[3]); ver.GetProductVersion(dwBuf); hprintf(LogFile, _T(" ProdVer: %d.%d.%d.%d\r\n"), dwBuf[0], dwBuf[1], dwBuf[2], dwBuf[3]); ver.Release(); hprintf(LogFile, _T("\r\n")); rc = true; } } // Handle any exceptions by continuing from this point. __except(EXCEPTION_EXECUTE_HANDLER) { } return rc; } /////////////////////////////////////////////////////////////////////////////// // DumpModuleList // // Scan memory looking for code modules (DLLs or EXEs). VirtualQuery is used // to find all the blocks of address space that were reserved or committed, // and ShowModuleInfo will display module information if they are code // modules. static void DumpModuleList(HANDLE LogFile) { SYSTEM_INFO SystemInfo; GetSystemInfo(&SystemInfo); const size_t PageSize = SystemInfo.dwPageSize; // Set NumPages to the number of pages in the 4GByte address space, // while being careful to avoid overflowing ints const size_t NumPages = 4 * size_t(ONEG / PageSize); size_t pageNum = 0; void *LastAllocationBase = 0; int nModuleNo = 1; while (pageNum < NumPages) { MEMORY_BASIC_INFORMATION MemInfo; if (VirtualQuery((void *)(pageNum * PageSize), &MemInfo, sizeof(MemInfo))) { if (MemInfo.RegionSize > 0) { // Adjust the page number to skip over this block of memory pageNum += MemInfo.RegionSize / PageSize; if (MemInfo.State == MEM_COMMIT && MemInfo.AllocationBase > LastAllocationBase) { // Look for new blocks of committed memory, and try // recording their module names - this will fail // gracefully if they aren't code modules LastAllocationBase = MemInfo.AllocationBase; if (DumpModuleInfo(LogFile, (HINSTANCE)LastAllocationBase, nModuleNo)) { nModuleNo++; } } } else pageNum += SIXTYFOURK / PageSize; } else pageNum += SIXTYFOURK / PageSize; // If VirtualQuery fails we advance by 64K because that is the // granularity of address space doled out by VirtualAlloc() } } /////////////////////////////////////////////////////////////////////////////// // DumpSystemInformation // // Record information about the user's system, such as processor type, amount // of memory, etc. static void DumpSystemInformation(HANDLE LogFile) { FILETIME CurrentTime; GetSystemTimeAsFileTime(&CurrentTime); TCHAR szTimeBuffer[100]; FormatTime(szTimeBuffer, CurrentTime); hprintf(LogFile, _T("Error occurred at %s.\r\n"), szTimeBuffer); TCHAR szModuleName[MAX_PATH*2]; ZeroMemory(szModuleName, sizeof(szModuleName)); if (GetModuleFileName(0, szModuleName, _countof(szModuleName)-2) <= 0) lstrcpy(szModuleName, _T("Unknown")); TCHAR szUserName[200]; ZeroMemory(szUserName, sizeof(szUserName)); DWORD UserNameSize = _countof(szUserName)-2; if (!GetUserName(szUserName, &UserNameSize)) lstrcpy(szUserName, _T("Unknown")); hprintf(LogFile, _T("%s, run by %s.\r\n"), szModuleName, szUserName); // print out operating system TCHAR szWinVer[50], szMajorMinorBuild[50]; int nWinVer; GetWinVer(szWinVer, &nWinVer, szMajorMinorBuild); hprintf(LogFile, _T("Operating system: %s (%s).\r\n"), szWinVer, szMajorMinorBuild); SYSTEM_INFO SystemInfo; GetSystemInfo(&SystemInfo); hprintf(LogFile, _T("%d processor(s), type %d.\r\n"), SystemInfo.dwNumberOfProcessors, SystemInfo.dwProcessorType); MEMORYSTATUS MemInfo; MemInfo.dwLength = sizeof(MemInfo); GlobalMemoryStatus(&MemInfo); // Print out info on memory, rounded up. hprintf(LogFile, _T("%d%% memory in use.\r\n"), MemInfo.dwMemoryLoad); hprintf(LogFile, _T("%d MBytes physical memory.\r\n"), (MemInfo.dwTotalPhys + ONEM - 1) / ONEM); hprintf(LogFile, _T("%d MBytes physical memory free.\r\n"), (MemInfo.dwAvailPhys + ONEM - 1) / ONEM); hprintf(LogFile, _T("%d MBytes paging file.\r\n"), (MemInfo.dwTotalPageFile + ONEM - 1) / ONEM); hprintf(LogFile, _T("%d MBytes paging file free.\r\n"), (MemInfo.dwAvailPageFile + ONEM - 1) / ONEM); hprintf(LogFile, _T("%d MBytes user address space.\r\n"), (MemInfo.dwTotalVirtual + ONEM - 1) / ONEM); hprintf(LogFile, _T("%d MBytes user address space free.\r\n"), (MemInfo.dwAvailVirtual + ONEM - 1) / ONEM); } /////////////////////////////////////////////////////////////////////////////// // GetExceptionDescription // // Translate the exception code into something human readable static const TCHAR *GetExceptionDescription(DWORD ExceptionCode) { struct ExceptionNames { DWORD ExceptionCode; TCHAR * ExceptionName; }; #if 0 // from winnt.h #define STATUS_WAIT_0 ((DWORD )0x00000000L) #define STATUS_ABANDONED_WAIT_0 ((DWORD )0x00000080L) #define STATUS_USER_APC ((DWORD )0x000000C0L) #define STATUS_TIMEOUT ((DWORD )0x00000102L) #define STATUS_PENDING ((DWORD )0x00000103L) #define STATUS_SEGMENT_NOTIFICATION ((DWORD )0x40000005L) #define STATUS_GUARD_PAGE_VIOLATION ((DWORD )0x80000001L) #define STATUS_DATATYPE_MISALIGNMENT ((DWORD )0x80000002L) #define STATUS_BREAKPOINT ((DWORD )0x80000003L) #define STATUS_SINGLE_STEP ((DWORD )0x80000004L) #define STATUS_ACCESS_VIOLATION ((DWORD )0xC0000005L) #define STATUS_IN_PAGE_ERROR ((DWORD )0xC0000006L) #define STATUS_INVALID_HANDLE ((DWORD )0xC0000008L) #define STATUS_NO_MEMORY ((DWORD )0xC0000017L) #define STATUS_ILLEGAL_INSTRUCTION ((DWORD )0xC000001DL) #define STATUS_NONCONTINUABLE_EXCEPTION ((DWORD )0xC0000025L) #define STATUS_INVALID_DISPOSITION ((DWORD )0xC0000026L) #define STATUS_ARRAY_BOUNDS_EXCEEDED ((DWORD )0xC000008CL) #define STATUS_FLOAT_DENORMAL_OPERAND ((DWORD )0xC000008DL) #define STATUS_FLOAT_DIVIDE_BY_ZERO ((DWORD )0xC000008EL) #define STATUS_FLOAT_INEXACT_RESULT ((DWORD )0xC000008FL) #define STATUS_FLOAT_INVALID_OPERATION ((DWORD )0xC0000090L) #define STATUS_FLOAT_OVERFLOW ((DWORD )0xC0000091L) #define STATUS_FLOAT_STACK_CHECK ((DWORD )0xC0000092L) #define STATUS_FLOAT_UNDERFLOW ((DWORD )0xC0000093L) #define STATUS_INTEGER_DIVIDE_BY_ZERO ((DWORD )0xC0000094L) #define STATUS_INTEGER_OVERFLOW ((DWORD )0xC0000095L) #define STATUS_PRIVILEGED_INSTRUCTION ((DWORD )0xC0000096L) #define STATUS_STACK_OVERFLOW ((DWORD )0xC00000FDL) #define STATUS_CONTROL_C_EXIT ((DWORD )0xC000013AL) #define STATUS_FLOAT_MULTIPLE_FAULTS ((DWORD )0xC00002B4L) #define STATUS_FLOAT_MULTIPLE_TRAPS ((DWORD )0xC00002B5L) #define STATUS_ILLEGAL_VLM_REFERENCE ((DWORD )0xC00002C0L) #endif ExceptionNames ExceptionMap[] = { {0x40010005, _T("a Control-C")}, {0x40010008, _T("a Control-Break")}, {0x80000002, _T("a Datatype Misalignment")}, {0x80000003, _T("a Breakpoint")}, {0xc0000005, _T("an Access Violation")}, {0xc0000006, _T("an In Page Error")}, {0xc0000017, _T("a No Memory")}, {0xc000001d, _T("an Illegal Instruction")}, {0xc0000025, _T("a Noncontinuable Exception")}, {0xc0000026, _T("an Invalid Disposition")}, {0xc000008c, _T("a Array Bounds Exceeded")}, {0xc000008d, _T("a Float Denormal Operand")}, {0xc000008e, _T("a Float Divide by Zero")}, {0xc000008f, _T("a Float Inexact Result")}, {0xc0000090, _T("a Float Invalid Operation")}, {0xc0000091, _T("a Float Overflow")}, {0xc0000092, _T("a Float Stack Check")}, {0xc0000093, _T("a Float Underflow")}, {0xc0000094, _T("an Integer Divide by Zero")}, {0xc0000095, _T("an Integer Overflow")}, {0xc0000096, _T("a Privileged Instruction")}, {0xc00000fD, _T("a Stack Overflow")}, {0xc0000142, _T("a DLL Initialization Failed")}, {0xe06d7363, _T("a Microsoft C++ Exception")}, }; for (int i = 0; i < sizeof(ExceptionMap) / sizeof(ExceptionMap[0]); i++) if (ExceptionCode == ExceptionMap[i].ExceptionCode) return ExceptionMap[i].ExceptionName; return _T("an Unknown exception type"); } /////////////////////////////////////////////////////////////////////////////// // GetFilePart static TCHAR * GetFilePart(LPCTSTR source) { TCHAR *result = lstrrchr(source, _T('\\')); if (result) result++; else result = (TCHAR *)source; return result; } /////////////////////////////////////////////////////////////////////////////// // DumpStack static void DumpStack(HANDLE LogFile, DWORD *pStack) { hprintf(LogFile, _T("\r\n\r\nStack:\r\n")); __try { // Esp contains the bottom of the stack, or at least the bottom of // the currently used area. DWORD* pStackTop; __asm { // Load the top (highest address) of the stack from the // thread information block. It will be found there in // Win9x and Windows NT. mov eax, fs:[4] mov pStackTop, eax } if (pStackTop > pStack + MaxStackDump) pStackTop = pStack + MaxStackDump; int Count = 0; DWORD* pStackStart = pStack; int nDwordsPrinted = 0; while (pStack + 1 <= pStackTop) { if ((Count % StackColumns) == 0) { pStackStart = pStack; nDwordsPrinted = 0; hprintf(LogFile, _T("0x%08x: "), pStack); } if ((++Count % StackColumns) == 0 || pStack + 2 > pStackTop) { hprintf(LogFile, _T("%08x "), *pStack); nDwordsPrinted++; int n = nDwordsPrinted; while (n < 4) { hprintf(LogFile, _T(" ")); n++; } for (int i = 0; i < nDwordsPrinted; i++) { DWORD dwStack = *pStackStart; for (int j = 0; j < 4; j++) { char c = (char)(dwStack & 0xFF); if (c < 0x20 || c > 0x7E) c = '.'; #ifdef _UNICODE WCHAR w = (WCHAR)c; hprintf(LogFile, _T("%c"), w); #else hprintf(LogFile, _T("%c"), c); #endif dwStack = dwStack >> 8; } pStackStart++; } hprintf(LogFile, _T("\r\n")); } else { hprintf(LogFile, _T("%08x "), *pStack); nDwordsPrinted++; } pStack++; } hprintf(LogFile, _T("\r\n")); } __except(EXCEPTION_EXECUTE_HANDLER) { hprintf(LogFile, _T("Exception encountered during stack dump.\r\n")); } } /////////////////////////////////////////////////////////////////////////////// // DumpRegisters static void DumpRegisters(HANDLE LogFile, PCONTEXT Context) { // Print out the register values in an XP error window compatible format. hprintf(LogFile, _T("\r\n")); hprintf(LogFile, _T("Context:\r\n")); hprintf(LogFile, _T("EDI: 0x%08x ESI: 0x%08x EAX: 0x%08x\r\n"), Context->Edi, Context->Esi, Context->Eax); hprintf(LogFile, _T("EBX: 0x%08x ECX: 0x%08x EDX: 0x%08x\r\n"), Context->Ebx, Context->Ecx, Context->Edx); hprintf(LogFile, _T("EIP: 0x%08x EBP: 0x%08x SegCs: 0x%08x\r\n"), Context->Eip, Context->Ebp, Context->SegCs); hprintf(LogFile, _T("EFlags: 0x%08x ESP: 0x%08x SegSs: 0x%08x\r\n"), Context->EFlags, Context->Esp, Context->SegSs); } static bool WriteErrorLog(LPCTSTR pszPath, PEXCEPTION_POINTERS pExceptPtrs, LPCTSTR pszModuleFileName) { HANDLE hLogFile = CreateFile(pszPath, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, 0); if (hLogFile == INVALID_HANDLE_VALUE) { OutputDebugString(_T("Error creating exception report\r\n")); return false; } // Append to the error log SetFilePointer(hLogFile, 0, 0, FILE_END); PEXCEPTION_RECORD Exception = pExceptPtrs->ExceptionRecord; PCONTEXT Context = pExceptPtrs->ContextRecord; TCHAR szCrashModulePathName[MAX_PATH*2]; ZeroMemory(szCrashModulePathName, sizeof(szCrashModulePathName)); TCHAR *pszCrashModuleFileName = _T("Unknown"); MEMORY_BASIC_INFORMATION MemInfo; // VirtualQuery can be used to get the allocation base associated with a // code address, which is the same as the ModuleHandle. This can be used // to get the filename of the module that the crash happened in. if (VirtualQuery((void*)Context->Eip, &MemInfo, sizeof(MemInfo)) && (GetModuleFileName((HINSTANCE)MemInfo.AllocationBase, szCrashModulePathName, sizeof(szCrashModulePathName)-2) > 0)) { pszCrashModuleFileName = GetFilePart(szCrashModulePathName); } // Print out the beginning of the error log in a Win95 error window // compatible format. hprintf(hLogFile, _T("%s caused %s (0x%08x) \r\nin module %s at %04x:%08x.\r\n\r\n"), pszModuleFileName, GetExceptionDescription(Exception->ExceptionCode), Exception->ExceptionCode, pszCrashModuleFileName, Context->SegCs, Context->Eip); DumpSystemInformation(hLogFile); // If the exception was an access violation, print out some additional // information, to the error log and the debugger. if (Exception->ExceptionCode == STATUS_ACCESS_VIOLATION && Exception->NumberParameters >= 2) { TCHAR szDebugMessage[1000]; const TCHAR* readwrite = _T("Read from"); if (Exception->ExceptionInformation[0]) readwrite = _T("Write to"); wsprintf(szDebugMessage, _T("%s location %08x caused an access violation.\r\n"), readwrite, Exception->ExceptionInformation[1]); #ifdef _DEBUG // The Visual C++ debugger doesn't actually tell you whether a read // or a write caused the access violation, nor does it tell what // address was being read or written. So I fixed that. OutputDebugString(_T("Exception handler: ")); OutputDebugString(szDebugMessage); #endif hprintf(hLogFile, _T("%s"), szDebugMessage); } DumpRegisters(hLogFile, Context); // Print out the bytes of code at the instruction pointer. Since the // crash may have been caused by an instruction pointer that was bad, // this code needs to be wrapped in an exception handler, in case there // is no memory to read. If the dereferencing of code[] fails, the // exception handler will print '??'. hprintf(hLogFile, _T("\r\nBytes at CS:EIP:\r\n")); BYTE * code = (BYTE *)Context->Eip; for (int codebyte = 0; codebyte < NumCodeBytes; codebyte++) { __try { hprintf(hLogFile, _T("%02x "), code[codebyte]); } __except(EXCEPTION_EXECUTE_HANDLER) { hprintf(hLogFile, _T("?? ")); } } // Time to print part or all of the stack to the error log. This allows // us to figure out the call stack, parameters, local variables, etc. // Esp contains the bottom of the stack, or at least the bottom of // the currently used area DWORD* pStack = (DWORD *)Context->Esp; DumpStack(hLogFile, pStack); DumpModuleList(hLogFile); hprintf(hLogFile, _T("\r\n===== [end of %s] =====\r\n"), XCRASHREPORT_ERROR_LOG_FILE); hflush(hLogFile); CloseHandle(hLogFile); return true; } static void WriteMiniDump(LPCTSTR pszPath, PEXCEPTION_POINTERS pExceptPtrs) { // Create the file HANDLE hMiniDumpFile = CreateFile( pszPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, NULL); // Write the minidump to the file if (hMiniDumpFile != INVALID_HANDLE_VALUE) { DumpMiniDump(hMiniDumpFile, pExceptPtrs); // Close file CloseHandle(hMiniDumpFile); } } void WriteRegistryDetails(LPCTSTR pszPath) { WriteRegistryTreeToFile(_T("HKLM\\Software\\Perforce"), pszPath); WriteRegistryTreeToFile(_T("HKCU\\Software\\Perforce"), pszPath); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // // RecordExceptionInfo // /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// int __cdecl RecordExceptionInfo(PEXCEPTION_POINTERS pExceptPtrs) { static bool bFirstTime = true; if (!bFirstTime) // Going recursive! That must mean this routine crashed! return EXCEPTION_CONTINUE_SEARCH; bFirstTime = false; if(!allowER) return EXCEPTION_CONTINUE_SEARCH; // Create a filename to record the error information to. // Storing it in the executable directory works well. TCHAR szModuleName[MAX_PATH*2]; ZeroMemory(szModuleName, sizeof(szModuleName)); if (GetModuleFileName(0, szModuleName, _countof(szModuleName)-2) <= 0) lstrcpy(szModuleName, _T("Unknown")); TCHAR *pszFilePart = GetFilePart(szModuleName); // Extract the file name portion and remove it's file extension TCHAR szFileName[MAX_PATH*2]; lstrcpy(szFileName, pszFilePart); TCHAR *lastperiod = lstrrchr(szFileName, _T('.')); if (lastperiod) lastperiod[0] = 0; // See if they want to create the error report TCHAR msg[1024]; wsprintf(msg, _T("%s has encountered a problem and needs to close. We are sorry for the inconvenience.\r\rWould you like to create an error report that you can send to Perforce support?"), CRASHING_APP_NAME); if(::MessageBox(NULL, msg, CRASHING_APP_NAME, MB_YESNO | MB_ICONERROR | MB_TASKMODAL | MB_TOPMOST) != IDYES) return EXCEPTION_CONTINUE_SEARCH; // Get path to executable directory and append error folder name TCHAR szPath[MAX_PATH*2]; lstrcpyn(szPath, szModuleName, pszFilePart - szModuleName); szPath[pszFilePart - szModuleName] = 0; lstrcat(szPath, XCRASHREPORT_ERROR_FOLDER); CreateDirectory(szPath,NULL); // Use module name + date to create a unique folder under error folder SYSTEMTIME st; GetLocalTime(&st); wsprintf(szPath+lstrlen(szPath), _T("%s_%04d-%02d-%02d_%02d-%02d-%02d\\"), szFileName, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); pszFilePart = szPath + lstrlen(szPath); CreateDirectory(szPath,NULL); // Replace the executable filename with our error log file name lstrcpy(pszFilePart, XCRASHREPORT_ERROR_LOG_FILE); if(!WriteErrorLog(szPath, pExceptPtrs, szFileName)) return EXCEPTION_CONTINUE_SEARCH; // Replace the filename with our minidump file name lstrcpy(pszFilePart, XCRASHREPORT_MINI_DUMP_FILE); WriteMiniDump(szPath, pExceptPtrs); // Replace the filename with our minidump file name lstrcpy(pszFilePart, XCRASHREPORT_REGISTRY_DUMP_FILE); WriteRegistryDetails(szPath); #if 0 if (IsDebuggerPresent()) { // let the debugger catch this - // return the magic value which tells Win32 that this handler didn't // actually handle the exception - so that things will proceed as per // normal. return EXCEPTION_CONTINUE_SEARCH; } else #endif { TCHAR msg[MAX_PATH*2]; TCHAR *lastslash = lstrrchr(szPath, _T('\\')); if (lastslash) lastslash[0] = 0; wsprintf(msg, _T("An error report was saved at %s."), szPath); ::MessageBox(NULL, msg, CRASHING_APP_NAME, MB_OK | MB_ICONINFORMATION | MB_TASKMODAL | MB_TOPMOST); return EXCEPTION_EXECUTE_HANDLER; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 16169 | perforce_software | Move files to follow new path scheme for branches. | ||
//guest/perforce_software/p4win/common/ExceptionHandler.cpp | |||||
#1 | 8562 | Matt Attaway |
These feet never stop running. Initial commit of the P4Win source code. To the best of our knowledge this compiles and runs using the 2013.3 P4 API and VS 2010. Expect a few changes as we refine the build process. Please post any build issues to the forums. |