// ExceptionHandler.cpp Version 1.5 // // 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 // // Latest Modified by: Xiel // // Modified by: Hans Dietrich // hdietrich2@hotmail.com // // Version 1.5 - remove TChar support, change log file's name // 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 // /////////////////////////////////////////////////////////////////////////////// #ifdef _WIN32 // Disable warnings generated by the Windows header files. #pragma warning(disable : 4514) #pragma warning(disable : 4201) #endif //_WIN32 #include "precompile.h" #include "DumpException.h" #include #include #ifdef _WIN32 #include #include "modCheck.h" 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) /////////////////////////////////////////////////////////////////////////////// // lstrrchr (avoid the C Runtime ) static CHAR * lstrrchr(LPCSTR string, int ch) { CHAR *start = (CHAR *)string; while (*string++) /* find end of string */ ; /* search towards front */ while (--string != start && *string != (CHAR) ch) ; if (*string == (CHAR) ch) /* char found ? */ return (CHAR *)string; return NULL; } /////////////////////////////////////////////////////////////////////////////// // hflush static void hflush(HANDLE LogFile) { if (toolkit_getResource()->hprintf_index > 0) { DWORD NumBytes; WriteFile(LogFile, toolkit_getResource()->hprintf_buffer, lstrlenA(toolkit_getResource()->hprintf_buffer), &NumBytes, 0); toolkit_getResource()->hprintf_index = 0; } } /////////////////////////////////////////////////////////////////////////////// // hprintf static void hprintf(HANDLE LogFile, LPCSTR Format, ...) { va_list arglist; if (toolkit_getResource()->hprintf_index > (HPRINTF_BUFFER_SIZE-1024)) { DWORD NumBytes; WriteFile(LogFile, toolkit_getResource()->hprintf_buffer, lstrlenA(toolkit_getResource()->hprintf_buffer), &NumBytes, 0); toolkit_getResource()->hprintf_index = 0; } va_start( arglist, Format); toolkit_getResource()->hprintf_index += wvsprintfA(&(toolkit_getResource()->hprintf_buffer[toolkit_getResource()->hprintf_index]), Format, arglist); va_end( arglist); } /////////////////////////////////////////////////////////////////////////////// // FormatTime // // Format the specified FILETIME to output in a human readable format, // without using the C run time. static void FormatTime(LPSTR output, FILETIME TimeToPrint) { WORD Date, Time; output[0] = '\0'; if (FileTimeToLocalFileTime(&TimeToPrint, &TimeToPrint) && FileTimeToDosDateTime(&TimeToPrint, &Date, &Time)) { wsprintfA(output, "%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; CHAR szModName[MAX_PATH*2]; ZeroMemory(szModName, sizeof(szModName)); __try { if (GetModuleFileNameA(ModuleHandle, szModName, sizeof(szModName)-2) > 0) { IMAGE_NT_HEADERS *NTHeader; IMAGE_DOS_HEADER *DosHeader; HANDLE ModuleFile; CHAR TimeBuffer[100]; DWORD FileSize = 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. DosHeader = (IMAGE_DOS_HEADER*)ModuleHandle; if (IMAGE_DOS_SIGNATURE != DosHeader->e_magic) return FALSE; NTHeader = (IMAGE_NT_HEADERS*)((CHAR *)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 ModuleFile = CreateFileA(szModName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); TimeBuffer[0] = '\0'; if (ModuleFile != INVALID_HANDLE_VALUE) { FILETIME LastWriteTime; FileSize = GetFileSize(ModuleFile, 0); if (GetFileTime(ModuleFile, 0, 0, &LastWriteTime)) { FormatTime(TimeBuffer, LastWriteTime); } CloseHandle(ModuleFile); } hprintf(LogFile, "Module %d\r\n", nModuleNo); hprintf(LogFile, "%s\r\n", szModName); hprintf(LogFile, "Image Base: 0x%08x Image Size: 0x%08x\r\n", NTHeader->OptionalHeader.ImageBase, NTHeader->OptionalHeader.SizeOfImage), hprintf(LogFile, "Checksum: 0x%08x Time Stamp: 0x%08x\r\n", NTHeader->OptionalHeader.CheckSum, NTHeader->FileHeader.TimeDateStamp); hprintf(LogFile, "File Size: %-10d File Time: %s\r\n", FileSize, TimeBuffer); hprintf(LogFile, "Version Information:\r\n"); hprintf(LogFile, "\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) { size_t pageNum; size_t NumPages; size_t PageSize; void *LastAllocationBase; SYSTEM_INFO SystemInfo; int nModuleNo; GetSystemInfo(&SystemInfo); PageSize = SystemInfo.dwPageSize; // Set NumPages to the number of pages in the 4GByte address space, // while being careful to avoid overflowing ints NumPages = 4 * ((size_t)(ONEG / PageSize)); pageNum = 0; LastAllocationBase = 0; 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; CHAR szTimeBuffer[100]; CHAR szUserName[200]; long crashID; DWORD UserNameSize; SYSTEM_INFO SystemInfo; CHAR szModuleName[MAX_PATH*2]; MEMORYSTATUS MemInfo;; GetSystemTimeAsFileTime(&CurrentTime); FormatTime(szTimeBuffer, CurrentTime); crashID = CurrentTime.dwLowDateTime; ZeroMemory(toolkit_getResource()->szCrashID, sizeof(toolkit_getResource()->szCrashID)); _ltoa_s( crashID, toolkit_getResource()->szCrashID, 20, 16 ); hprintf(LogFile, "\r\nCRASH REFERENCE CODE: %s\r\n\r\n", toolkit_getResource()->szCrashID ); hprintf(LogFile, "Error occurred at %s.\r\n", szTimeBuffer); ZeroMemory(szModuleName, sizeof(szModuleName)); if (GetModuleFileNameA(0, szModuleName, _countof(szModuleName)-2) <= 0) lstrcpyA(szModuleName, "Unknown"); ZeroMemory(szUserName, sizeof(szUserName)); UserNameSize = _countof(szUserName)-2; if (!GetUserNameA(szUserName, &UserNameSize)) lstrcpyA(szUserName, "Unknown"); hprintf(LogFile, "%s, run by %s.\r\n", szModuleName, szUserName); GetSystemInfo(&SystemInfo); hprintf(LogFile, "%d processor(s), type %d.\r\n", SystemInfo.dwNumberOfProcessors, SystemInfo.dwProcessorType); MemInfo.dwLength = sizeof(MemInfo); GlobalMemoryStatus(&MemInfo); // Print out info on memory, rounded up. hprintf(LogFile, "%d%% memory in use.\r\n", MemInfo.dwMemoryLoad); hprintf(LogFile, "%d MBytes physical memory.\r\n", (MemInfo.dwTotalPhys + ONEM - 1) / ONEM); hprintf(LogFile, "%d MBytes physical memory free.\r\n", (MemInfo.dwAvailPhys + ONEM - 1) / ONEM); hprintf(LogFile, "%d MBytes paging file.\r\n", (MemInfo.dwTotalPageFile + ONEM - 1) / ONEM); hprintf(LogFile, "%d MBytes paging file free.\r\n", (MemInfo.dwAvailPageFile + ONEM - 1) / ONEM); hprintf(LogFile, "%d MBytes user address space.\r\n", (MemInfo.dwTotalVirtual + ONEM - 1) / ONEM); hprintf(LogFile, "%d MBytes user address space free.\r\n", (MemInfo.dwAvailVirtual + ONEM - 1) / ONEM); } /////////////////////////////////////////////////////////////////////////////// // GetExceptionDescription // // Translate the exception code into something human readable static const CHAR *GetExceptionDescription(DWORD ExceptionCode) { int i; struct ExceptionNames { DWORD ExceptionCode; CHAR * ExceptionName; }; struct ExceptionNames ExceptionMap[] = { {0x40010005, "a Control-C"}, {0x40010008, "a Control-Break"}, {0x80000002, "a Datatype Misalignment"}, {0x80000003, "a Breakpoint"}, {0xc0000005, "an Access Violation"}, {0xc0000006, "an In Page Error"}, {0xc0000017, "a No Memory"}, {0xc000001d, "an Illegal Instruction"}, {0xc0000025, "a Noncontinuable Exception"}, {0xc0000026, "an Invalid Disposition"}, {0xc000008c, "a Array Bounds Exceeded"}, {0xc000008d, "a Float Denormal Operand"}, {0xc000008e, "a Float Divide by Zero"}, {0xc000008f, "a Float Inexact Result"}, {0xc0000090, "a Float Invalid Operation"}, {0xc0000091, "a Float Overflow"}, {0xc0000092, "a Float Stack Check"}, {0xc0000093, "a Float Underflow"}, {0xc0000094, "an Integer Divide by Zero"}, {0xc0000095, "an Integer Overflow"}, {0xc0000096, "a Privileged Instruction"}, {0xc00000fD, "a Stack Overflow"}, {0xc0000142, "a DLL Initialization Failed"}, {0xe06d7363, "a Microsoft C++ Exception"}, }; for (i = 0; i < sizeof(ExceptionMap) / sizeof(ExceptionMap[0]); i++) if (ExceptionCode == ExceptionMap[i].ExceptionCode) return ExceptionMap[i].ExceptionName; return "an Unknown exception type"; } /////////////////////////////////////////////////////////////////////////////// // GetFilePart static CHAR * GetFilePart(LPCSTR source) { CHAR *result = lstrrchr(source, '\\'); if (result) result++; else result = (CHAR *)source; return result; } /////////////////////////////////////////////////////////////////////////////// // DumpStack static void DumpStack(HANDLE LogFile, DWORD *pStack) { hprintf(LogFile, "\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; DWORD* pStackStart; int Count; int nDwordsPrinted; __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; Count = 0; pStackStart = pStack; nDwordsPrinted = 0; while (pStack + 1 <= pStackTop) { if ((Count % StackColumns) == 0) { pStackStart = pStack; nDwordsPrinted = 0; hprintf(LogFile, "0x%08x: ", pStack); } if ((++Count % StackColumns) == 0 || pStack + 2 > pStackTop) { int n, i; hprintf(LogFile, "%08x ", *pStack); nDwordsPrinted++; n = nDwordsPrinted; while (n < 4) { hprintf(LogFile, " "); n++; } for (i = 0; i < nDwordsPrinted; i++) { int j; DWORD dwStack = *pStackStart; for (j = 0; j < 4; j++) { char c = (char)(dwStack & 0xFF); if (c < 0x20 || c > 0x7E) c = '.'; hprintf(LogFile, "%c", c); dwStack = dwStack >> 8; } pStackStart++; } hprintf(LogFile, "\r\n"); } else { hprintf(LogFile, "%08x ", *pStack); nDwordsPrinted++; } pStack++; } hprintf(LogFile, "\r\n"); } __except(EXCEPTION_EXECUTE_HANDLER) { hprintf(LogFile, "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, "\r\n"); hprintf(LogFile, "Context:\r\n"); hprintf(LogFile, "EDI: 0x%08x ESI: 0x%08x EAX: 0x%08x\r\n", Context->Edi, Context->Esi, Context->Eax); hprintf(LogFile, "EBX: 0x%08x ECX: 0x%08x EDX: 0x%08x\r\n", Context->Ebx, Context->Ecx, Context->Edx); hprintf(LogFile, "EIP: 0x%08x EBP: 0x%08x SegCs: 0x%08x\r\n", Context->Eip, Context->Ebp, Context->SegCs); hprintf(LogFile, "EFlags: 0x%08x ESP: 0x%08x SegSs: 0x%08x\r\n", Context->EFlags, Context->Esp, Context->SegSs); } #endif /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // // RecordExceptionInfo // /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// TOOLKIT_API int TOOLKIT_CC DumpExceptionInfo(PEXCEPTION_POINTERS pExceptPtrs, HANDLE hLogFile) { #ifdef _WIN32 PEXCEPTION_RECORD Exception = pExceptPtrs->ExceptionRecord; PCONTEXT Context = pExceptPtrs->ContextRecord; CHAR *pszCrashModuleFileName; CHAR szCrashModulePathName[MAX_PATH*2]; MEMORY_BASIC_INFORMATION MemInfo; BYTE * code; int codebyte; DWORD* pStack; ZeroMemory(szCrashModulePathName, sizeof(szCrashModulePathName)); pszCrashModuleFileName = "Unknown"; // 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)) && (GetModuleFileNameA((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, "caused %s (0x%08x) \r\nin module %s at %04x:%08x.\r\n\r\n", GetExceptionDescription(Exception->ExceptionCode), Exception->ExceptionCode, pszCrashModuleFileName, Context->SegCs, Context->Eip); hprintf(hLogFile, "Exception handler called.\r\n"); 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) { CHAR szDebugMessage[1000]; const CHAR* readwrite = "Read from"; if (Exception->ExceptionInformation[0]) readwrite = "Write to"; wsprintfA(szDebugMessage, "%s location %08x caused an access violation.\r\n", readwrite, Exception->ExceptionInformation[1]); hprintf(hLogFile, "%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, "\r\nBytes at CS:EIP:\r\n"); code = (BYTE *)Context->Eip; for (codebyte = 0; codebyte < NumCodeBytes; codebyte++) { __try { hprintf(hLogFile, "%02x ", code[codebyte]); } __except(EXCEPTION_EXECUTE_HANDLER) { hprintf(hLogFile, "?? "); } } // 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 pStack = (DWORD *)Context->Esp; DumpStack(hLogFile, pStack); DumpModuleList(hLogFile); hflush(hLogFile); #endif //_WIN32 return EXCEPTION_EXECUTE_HANDLER; }