#ifdef JA2_PRECOMPILED_HEADERS #include "JA2 SGP ALL.H" #elif defined( WIZ8_PRECOMPILED_HEADERS ) #include "WIZ8 SGP ALL.H" #else #include "Types.h" #include #include #include #endif #include "ExceptionHandling.h" #ifdef JA2 #include "GameVersion.h" #endif //If we are to use exception handling #ifdef ENABLE_EXCEPTION_HANDLING const int NumCodeBytes = 16; // Number of code bytes to record. const int MaxStackDump = 2048; // Maximum number of DWORDS in stack dumps. const int StackColumns = 8; // Number of columns in stack dump. #define ONEK 1024 #define SIXTYFOURK (64*ONEK) #define ONEM (ONEK*ONEK) #define ONEG (ONEK*ONEK*ONEK) //ppp void ErrorLog(HWFILE LogFile, char* Format, ...); STR GetExceptionString( DWORD uiExceptionCode ); void DisplayRegisters( HWFILE hFile, CONTEXT *pContext ); BOOLEAN GetAndDisplayModuleAndSystemInfo( HWFILE hFile, CONTEXT *pContext ); BOOLEAN DisplayStack( HWFILE hFile, CONTEXT *pContext ); void RecordModuleList(HWFILE hFile ); void PrintTime(char *output, FILETIME TimeToPrint); static void ShowModuleInfo(HWFILE hFile, HINSTANCE ModuleHandle); INT32 RecordExceptionInfo( EXCEPTION_POINTERS *pExceptInfo ) { EXCEPTION_RECORD Record; CONTEXT Context; CHAR8 zFileName[512]; CHAR8 zDate[512]; CHAR8 zTime[512]; HWFILE hFile; CHAR8 zString[2048]; SYSTEMTIME SysTime; CHAR8 zNewLine[] = "\r\n"; //create local copies of the exception info memcpy( &Record, pExceptInfo->ExceptionRecord , sizeof( EXCEPTION_RECORD ) ); memcpy( &Context, pExceptInfo->ContextRecord, sizeof( CONTEXT ) ); // // Open a file to output the current state of the game // // Get the current time GetLocalTime( &SysTime ); //create a date string sprintf( zDate, "%02d_%02d_%d", SysTime.wDay, SysTime.wMonth, SysTime.wYear ); //create a time string sprintf( zTime, "%02d_%02d", SysTime.wHour, SysTime.wMinute ); //create the crash file sprintf( zFileName, "Crash Report_%s___%s.txt", zDate, zTime ); // create the save game file hFile = FileOpen( zFileName, FILE_ACCESS_WRITE | FILE_OPEN_ALWAYS, FALSE ); if( !hFile ) { FileClose( hFile ); return( 0 ); } // // Display the version number // #ifdef JA2 //Dispay Ja's version number ErrorLog( hFile, "%S: %s. %S",zVersionLabel, czVersionNumber, zTrackingNumber ); //Insert a new line ErrorLog( hFile, zNewLine ); //Insert a new line ErrorLog( hFile, zNewLine ); #endif // // Write out the current state of the system // GetAndDisplayModuleAndSystemInfo( hFile, &Context ); //Insert a new line ErrorLog( hFile, zNewLine ); //Display the address of where the exception occured sprintf( zString, "Exception occured at address: 0x%08x\r\n", Record.ExceptionAddress ); ErrorLog( hFile, zString ); //if the exception was an access violation, display the offending address if( Record.ExceptionCode == EXCEPTION_ACCESS_VIOLATION && Record.NumberParameters != 0 ) { if( Record.ExceptionInformation[0] != 0 ) { //Display the address of where the access violation occured sprintf( zString, "\tWrite Access Violation at: 0x%08x\r\n", Record.ExceptionInformation[1] ); } else { //Display the address of where the access violation occured sprintf( zString, "\tWrite Access Violation at: 0x%08x\r\n", Record.ExceptionInformation[1] ); } ErrorLog( hFile, zString ); } //Insert a new line ErrorLog( hFile, zNewLine ); //Display the exception that caused this sprintf( zString, "Exact Error Message \r\n \"%s\"\r\n", GetExceptionString( Record.ExceptionCode ) ); ErrorLog( hFile, zString ); //Insert a new line ErrorLog( hFile, zNewLine ); //Dispay if the code 'could' continue if( Record.ExceptionFlags != 0 ) sprintf( zString, "%s\r\n", "The game 'can NOT' continue" ); else sprintf( zString, "%s\r\n", "The game 'CAN' continue" ); ErrorLog( hFile, zString ); //Insert a new line ErrorLog( hFile, zNewLine ); ErrorLog( hFile, zNewLine ); // // Display the current context information // DisplayRegisters( hFile, &Context ); ErrorLog( hFile, zNewLine ); ErrorLog( hFile, zNewLine ); //display the stack ( call stack + local variables ) DisplayStack( hFile, &Context); //Display some spaces ErrorLog( hFile, zNewLine ); ErrorLog( hFile, zNewLine ); //Display all modules currently loaded RecordModuleList(hFile ); //eee FileClose( hFile ); return( EXCEPTION_EXECUTE_HANDLER ); } void ErrorLog( HWFILE hFile, char* Format, ...) { char buffer[2000]; // wvsprintf never prints more than one K. UINT32 uiNumBytesWritten=0; UINT32 uiStringWidth; va_list arglist; va_start( arglist, Format); wvsprintf(buffer, Format, arglist); va_end( arglist); //WriteFile(LogFile, buffer, lstrlen(buffer), &NumBytes, 0); uiStringWidth = lstrlen(buffer); //write out the string FileWrite( hFile, buffer, uiStringWidth, &uiNumBytesWritten ); if( uiNumBytesWritten != uiStringWidth ) { FileClose( hFile ); return; } } STR GetExceptionString( DWORD uiExceptionCode ) { switch( uiExceptionCode ) { case EXCEPTION_ACCESS_VIOLATION: return( "The thread tried to read from or write to a virtual address for which it does not have the appropriate access."); break; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return( "The thread tried to access an array element that is out of bounds and the underlying hardware supports bounds checking."); case EXCEPTION_BREAKPOINT: return( "A breakpoint was encountered."); case EXCEPTION_DATATYPE_MISALIGNMENT: return( "The thread tried to read or write data that is misaligned on hardware that does not provide alignment. For example, 16-bit values must be aligned on 2-byte boundaries; 32-bit values on 4-byte boundaries, and so on."); case EXCEPTION_FLT_DENORMAL_OPERAND: return( "One of the operands in a floating-point operation is denormal. A denormal value is one that is too small to represent as a standard floating-point value."); case EXCEPTION_FLT_DIVIDE_BY_ZERO: return( "The thread tried to divide a floating-point value by a floating-point divisor of zero."); case EXCEPTION_FLT_INEXACT_RESULT: return( "The result of a floating-point operation cannot be represented exactly as a decimal fraction."); case EXCEPTION_FLT_INVALID_OPERATION: return( "This exception represents any floating-point exception not included in this list."); case EXCEPTION_FLT_OVERFLOW: return( "The exponent of a floating-point operation is greater than the magnitude allowed by the corresponding type."); case EXCEPTION_FLT_STACK_CHECK: return( "The stack overflowed or underflowed as the result of a floating-point operation."); case EXCEPTION_FLT_UNDERFLOW: return( "The exponent of a floating-point operation is less than the magnitude allowed by the corresponding type."); case EXCEPTION_ILLEGAL_INSTRUCTION: return( "The thread tried to execute an invalid instruction."); case EXCEPTION_IN_PAGE_ERROR: return( "The thread tried to access a page that was not present, and the system was unable to load the page. For example, this exception might occur if a network connection is lost while running a program over the network."); case EXCEPTION_INT_DIVIDE_BY_ZERO: return( "The thread tried to divide an integer value by an integer divisor of zero."); case EXCEPTION_INT_OVERFLOW: return( "The result of an integer operation caused a carry out of the most significant bit of the result."); case EXCEPTION_INVALID_DISPOSITION: return( "An exception handler returned an invalid disposition to the exception dispatcher. Programmers using a high-level language such as C should never encounter this exception."); case EXCEPTION_NONCONTINUABLE_EXCEPTION: return( "The thread tried to continue execution after a noncontinuable exception occurred."); case EXCEPTION_PRIV_INSTRUCTION: return( "The thread tried to execute an instruction whose operation is not allowed in the current machine mode."); case EXCEPTION_SINGLE_STEP: return( "A trace trap or other single-instruction mechanism signaled that one instruction has been executed."); case EXCEPTION_STACK_OVERFLOW: return( "The thread used up its stack."); default: return("Exception not in case "); break; } } void DisplayRegisters( HWFILE hFile, CONTEXT *pContext ) { ErrorLog( hFile, "Registers:\r\n"); ErrorLog( hFile, "\tEAX=%08x CS=%04x EIP=%08x EFLGS=%08x\r\n", pContext->Eax, pContext->SegCs, pContext->Eip, pContext->EFlags); ErrorLog( hFile, "\tEBX=%08x SS=%04x ESP=%08x EBP=%08x\r\n", pContext->Ebx, pContext->SegSs, pContext->Esp, pContext->Ebp); ErrorLog( hFile, "\tECX=%08x DS=%04x ESI=%08x FS=%04x\r\n", pContext->Ecx, pContext->SegDs, pContext->Esi, pContext->SegFs); ErrorLog( hFile, "\tEDX=%08x ES=%04x EDI=%08x GS=%04x\r\n", pContext->Edx, pContext->SegEs, pContext->Edi, pContext->SegGs); // ErrorLog( hFile, "Bytes at CS:EIP:\r\n"); } BOOLEAN GetAndDisplayModuleAndSystemInfo( HWFILE hFile, CONTEXT *pContext ) { char zFileName[2048]; char zString[2048]; SYSTEM_INFO SystemInfo; MEMORYSTATUS MemInfo; // MEMORY_BASIC_INFORMATION MemBasicInfo; size_t PageSize; size_t pageNum = 0; FILETIME LastWriteTime; if( GetModuleFileName(0, zFileName, sizeof(zFileName) ) == 0) { return( FALSE ); } MemInfo.dwLength = sizeof(MemInfo); GlobalMemoryStatus(&MemInfo); //Display the filename ErrorLog( hFile, "File:\r\n\t%s\r\n", zFileName); //Get the time the file was created if (GetFileTime(GetRealFileHandleFromFileManFileHandle( hFile ), 0, 0, &LastWriteTime)) { PrintTime( zString, LastWriteTime); ErrorLog( hFile, "\tFile created on: %s\r\n", zString ); } //Get cpu type and number GetSystemInfo(&SystemInfo); ErrorLog( hFile, "\t%d type %d processor.\r\n", SystemInfo.dwNumberOfProcessors, SystemInfo.dwProcessorType ); //Get free ram ErrorLog( hFile, "\tTotal Physical Memory: %d Megs.\r\n", MemInfo.dwTotalPhys/(1024*1024) ); PageSize = SystemInfo.dwPageSize; pageNum = 0; /* //Get current instruction pointer if( VirtualQuery((void *)(pageNum * PageSize), &MemBasicInfo, sizeof(MemBasicInfo) ) ) { if (MemBasicInfo.RegionSize > 0) { ErrorLog( hFile, "\r\n\r\nJagged is loaded into memory at: 0x%08d.\r\n", MemBasicInfo.AllocationBase ); } } */ ErrorLog( hFile, "Segment( CS:EIP ):\t%04x:%08x.\r\n", pContext->SegCs, pContext->Eip ); return( TRUE ); } // // This code for this function is based ( stolen ) from Bruce Dawson's article in Game Developer Magazine Jan 99 // BOOLEAN DisplayStack( HWFILE hFile, CONTEXT *pContext ) { int Count = 0; char buffer[1000] = ""; const int safetyzone = 50; char* nearend = buffer + sizeof(buffer) - safetyzone; char* output = buffer; // 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. ErrorLog( hFile, "Stack dump:\r\n" ); __try { // Esp contains the bottom of the stack, or at least the bottom of // the currently used area. DWORD* pStack = (DWORD *)pContext->Esp; 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; // Too many calls to WriteFile can take a long time, causing // confusing delays when programs crash. Therefore I implemented // simple buffering for the stack dumping code instead of calling // hprintf directly. while (pStack + 1 <= pStackTop) { char *Suffix = " "; if ((Count % StackColumns) == 0) output += wsprintf(output, "%08x: ", pStack); if ((++Count % StackColumns) == 0 || pStack + 2 > pStackTop) Suffix = "\r\n"; output += wsprintf(output, "%08x%s", *pStack, Suffix); pStack++; // Check for when the buffer is almost full, and flush it to disk. if (output > nearend) { ErrorLog( hFile, "%s", buffer); buffer[0] = 0; output = buffer; } } // Print out any final characters from the cache. ErrorLog( hFile, "%s", buffer); } __except(EXCEPTION_EXECUTE_HANDLER) { ErrorLog( hFile, "Exception encountered during stack dump.\r\n"); } return( TRUE ); } // // This code for this function is ( stolen :) from Bruce Dawson's article in Game Developer Magazine Jan 99 // // Print the specified FILETIME to output in a human readable format, // without using the C run time. void PrintTime(char *output, FILETIME TimeToPrint) { WORD Date, Time; if (FileTimeToLocalFileTime(&TimeToPrint, &TimeToPrint) && FileTimeToDosDateTime(&TimeToPrint, &Date, &Time)) { // What a silly way to print out the file date/time. Oh well, // it works, and I'm not aware of a cleaner way to do it. wsprintf(output, "%02d/%02d/%d %02d:%02d:%02d", (Date / 32) & 15, Date & 31, (Date / 512) + 1980, (Time / 2048), (Time / 32) & 63, (Time & 31) * 2); } else output[0] = 0; } // // This code for this function is ( stolen :) from Bruce Dawson's article in Game Developer Magazine Jan 99 // // 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. void RecordModuleList(HWFILE hFile ) { ErrorLog( hFile, "\r\n" "Module list: names, addresses, sizes, time stamps " "and file times:\r\n"); 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; 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; ShowModuleInfo(hFile, (HINSTANCE)LastAllocationBase); } } 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(). } } // // This code for this function is ( stolen :) from Bruce Dawson's article in Game Developer Magazine Jan 99 // static void ShowModuleInfo(HWFILE hFile, HINSTANCE ModuleHandle) { char ModName[MAX_PATH]; __try { if (GetModuleFileName(ModuleHandle, ModName, sizeof(ModName)) > 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; IMAGE_NT_HEADERS *NTHeader = (IMAGE_NT_HEADERS*)((char *)DosHeader + DosHeader->e_lfanew); if (IMAGE_NT_SIGNATURE != NTHeader->Signature) return; // Open the code module file so that we can get its file date // and size. HANDLE ModuleFile = CreateFile(ModName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); char TimeBuffer[100] = ""; DWORD FileSize = 0; if (ModuleFile != INVALID_HANDLE_VALUE) { FileSize = GetFileSize(ModuleFile, 0); FILETIME LastWriteTime; if (GetFileTime(ModuleFile, 0, 0, &LastWriteTime)) { wsprintf(TimeBuffer, " - file date is "); PrintTime(TimeBuffer + lstrlen(TimeBuffer), LastWriteTime); } CloseHandle(ModuleFile); } ErrorLog( hFile, "%-35s, loaded at 0x%08x - %7d bytes - %08x%s\r\n", ModName, ModuleHandle, FileSize, NTHeader->FileHeader.TimeDateStamp, TimeBuffer); } } // Handle any exceptions by continuing from this point. __except(EXCEPTION_EXECUTE_HANDLER) { } } #endif