Files
source/Standard Gaming Platform/ExceptionHandling.cpp
T

558 lines
17 KiB
C++

#ifdef JA2_PRECOMPILED_HEADERS
#include "JA2 SGP ALL.H"
#elif defined( WIZ8_PRECOMPILED_HEADERS )
#include "WIZ8 SGP ALL.H"
#else
#include "Types.h"
#include <stdlib.h>
#include <malloc.h>
#include <stdio.h>
#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