mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-12 02:01:29 -10:00
added myself to credit killed some of the stupid fscking warnings (including doing some casting and commenting out unused vars in the graphics modules) Release builds should have warning level set no higher than 2 (default is 1) Why the hell are we getting warning's about function selected for inline expansion... (killing them with warning disables) FS2_SPEECH was not defined (source file doesn't appear to capture preproc defines correctly either)
530 lines
16 KiB
C++
530 lines
16 KiB
C++
/*
|
|
* Copyright (C) Volition, Inc. 1999. All rights reserved.
|
|
*
|
|
* All source code herein is the property of Volition, Inc. You may not sell
|
|
* or otherwise commercially exploit the source or things you created based on the
|
|
* source.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* $Logfile: /Freespace2/code/ExceptionHandler/ExceptionHandler.cpp $
|
|
* $Revision: 2.2 $
|
|
* $Date: 2003-11-06 20:22:03 $
|
|
* $Author: Kazan $
|
|
*
|
|
* Main file for dealing with exception handling
|
|
*
|
|
* $Log: not supported by cvs2svn $
|
|
* Revision 2.1 2003/03/02 05:10:42 penguin
|
|
* Added #ifdef _MSC_VER around strange exception-handling code
|
|
* - penguin
|
|
*
|
|
* Revision 2.0 2002/06/03 04:02:22 penguin
|
|
* Warpcore CVS sync
|
|
*
|
|
* Revision 1.1 2002/05/02 18:03:05 mharris
|
|
* Initial checkin - converted filenames and includes to lower case
|
|
*
|
|
*
|
|
* 1 6/29/99 7:42p Dave
|
|
*
|
|
* 3 1/19/99 9:07a Allender
|
|
* removed compiler warning pragma's since we are already ignoring them in
|
|
* vtypes.h
|
|
*
|
|
* 2 1/18/99 4:34p Allender
|
|
* added the exception handler routines from Game Developer for structured
|
|
* exception handling in vsdk code
|
|
*
|
|
* $NoKeywords: $
|
|
*/
|
|
|
|
// 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.
|
|
*/
|
|
|
|
|
|
#include <windows.h>
|
|
|
|
#pragma warning(disable:4711)
|
|
|
|
// --------------------
|
|
//
|
|
// Defines
|
|
//
|
|
// --------------------
|
|
#define ONEK 1024
|
|
#define SIXTYFOURK (64*ONEK)
|
|
#define ONEM (ONEK*ONEK)
|
|
#define ONEG (ONEK*ONEK*ONEK)
|
|
|
|
// --------------------
|
|
//
|
|
// Enumerated Types
|
|
//
|
|
// --------------------
|
|
|
|
|
|
// --------------------
|
|
//
|
|
// Structures
|
|
//
|
|
// --------------------
|
|
|
|
|
|
// --------------------
|
|
//
|
|
// Classes
|
|
//
|
|
// --------------------
|
|
|
|
|
|
// --------------------
|
|
//
|
|
// Global Variables
|
|
//
|
|
// --------------------
|
|
|
|
|
|
// --------------------
|
|
//
|
|
// Local Variables
|
|
//
|
|
// --------------------
|
|
|
|
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.
|
|
|
|
// --------------------
|
|
//
|
|
// Internal Functions
|
|
//
|
|
// --------------------
|
|
|
|
// 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.
|
|
static void hprintf(HANDLE LogFile, char* Format, ...)
|
|
{
|
|
char buffer[2000]; // wvsprintf never prints more than one K.
|
|
|
|
va_list arglist;
|
|
va_start( arglist, Format);
|
|
wvsprintf(buffer, Format, arglist);
|
|
va_end( arglist);
|
|
|
|
DWORD NumBytes;
|
|
WriteFile(LogFile, buffer, lstrlen(buffer), &NumBytes, 0);
|
|
}
|
|
|
|
// Print the specified FILETIME to output in a human readable format,
|
|
// without using the C run time.
|
|
static 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, "%d/%d/%d %02d:%02d:%02d",
|
|
(Date / 32) & 15, Date & 31, (Date / 512) + 1980,
|
|
(Time / 2048), (Time / 32) & 63, (Time & 31) * 2);
|
|
} else {
|
|
output[0] = 0;
|
|
}
|
|
}
|
|
|
|
// Print information about a code module (DLL or EXE) such as its size,
|
|
// location, time stamp, etc.
|
|
static void ShowModuleInfo(HANDLE LogFile, HINSTANCE ModuleHandle)
|
|
{
|
|
char ModName[MAX_PATH];
|
|
#ifdef _MSC_VER
|
|
__try {
|
|
#endif
|
|
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);
|
|
}
|
|
hprintf(LogFile, "%s, loaded at 0x%08x - %d bytes - %08x%s\r\n",
|
|
ModName, ModuleHandle, FileSize,
|
|
NTHeader->FileHeader.TimeDateStamp, TimeBuffer);
|
|
}
|
|
#ifdef _MSC_VER
|
|
}
|
|
// Handle any exceptions by continuing from this point.
|
|
__except(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// 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 RecordModuleList(HANDLE LogFile)
|
|
{
|
|
hprintf(LogFile, "\r\n"
|
|
"\tModule 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(LogFile, (HINSTANCE)LastAllocationBase);
|
|
}
|
|
} else {
|
|
pageNum += SIXTYFOURK / PageSize;
|
|
}
|
|
} else {
|
|
// If VirtualQuery fails we advance by 64K because that is the
|
|
// granularity of address space doled out by VirtualAlloc().
|
|
pageNum += SIXTYFOURK / PageSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Record information about the user's system, such as processor type, amount
|
|
// of memory, etc.
|
|
|
|
static void RecordSystemInformation(HANDLE LogFile)
|
|
{
|
|
FILETIME CurrentTime;
|
|
GetSystemTimeAsFileTime(&CurrentTime);
|
|
char TimeBuffer[100];
|
|
PrintTime(TimeBuffer, CurrentTime);
|
|
hprintf(LogFile, "Error occurred at %s.\r\n", TimeBuffer);
|
|
char ModuleName[MAX_PATH];
|
|
if (GetModuleFileName(0, ModuleName, sizeof(ModuleName)) <= 0) {
|
|
lstrcpy(ModuleName, "Unknown");
|
|
}
|
|
char UserName[200];
|
|
DWORD UserNameSize = sizeof(UserName);
|
|
if (!GetUserName(UserName, &UserNameSize)) {
|
|
lstrcpy(UserName, "Unknown");
|
|
}
|
|
hprintf(LogFile, "%s, run by %s.\r\n", ModuleName, UserName);
|
|
|
|
SYSTEM_INFO SystemInfo;
|
|
GetSystemInfo(&SystemInfo);
|
|
hprintf(LogFile, "%d processor(s), type %d.\r\n",
|
|
SystemInfo.dwNumberOfProcessors, SystemInfo.dwProcessorType);
|
|
|
|
MEMORYSTATUS MemInfo;
|
|
MemInfo.dwLength = sizeof(MemInfo);
|
|
GlobalMemoryStatus(&MemInfo);
|
|
// Print out the amount of physical memory, rounded up.
|
|
hprintf(LogFile, "%d MBytes physical memory.\r\n", (MemInfo.dwTotalPhys +
|
|
ONEM - 1) / ONEM);
|
|
}
|
|
|
|
// Translate the exception code into something human readable.
|
|
|
|
static const char *GetExceptionDescription(DWORD ExceptionCode)
|
|
{
|
|
struct ExceptionNames
|
|
{
|
|
DWORD ExceptionCode;
|
|
char* ExceptionName;
|
|
};
|
|
|
|
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 (int i = 0; i < sizeof(ExceptionMap) / sizeof(ExceptionMap[0]); i++) {
|
|
if (ExceptionCode == ExceptionMap[i].ExceptionCode) {
|
|
return ExceptionMap[i].ExceptionName;
|
|
}
|
|
}
|
|
|
|
return "Unknown exception type";
|
|
}
|
|
|
|
static char* GetFilePart(char *source)
|
|
{
|
|
char *result = strrchr(source, '\\');
|
|
if (result) {
|
|
result++;
|
|
} else {
|
|
result = source;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// --------------------
|
|
//
|
|
// External Functions
|
|
//
|
|
// --------------------
|
|
|
|
|
|
// Entry point into the main exception handling routine. This routine is put into an except()
|
|
// statment at the beginning of a thread and is called anytime that there is a program exception
|
|
// The data is stored in a file called ErrorLog.txt in the data directory.
|
|
//
|
|
// data: pointer to the exception data
|
|
// Message: Any message that should be printed out in the error log file
|
|
//
|
|
// returns:
|
|
//
|
|
int __cdecl RecordExceptionInfo(PEXCEPTION_POINTERS data, const char *Message)
|
|
{
|
|
static bool BeenHere = false;
|
|
|
|
// Going recursive! That must mean this routine crashed!
|
|
if (BeenHere) {
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
BeenHere = true;
|
|
|
|
char ModuleName[MAX_PATH];
|
|
char FileName[MAX_PATH] = "Unknown";
|
|
// Create a filename to record the error information to.
|
|
// Storing it in the executable directory works well.
|
|
if (GetModuleFileName(0, ModuleName, sizeof(ModuleName)) <= 0) {
|
|
ModuleName[0] = 0;
|
|
}
|
|
|
|
char *FilePart = GetFilePart(ModuleName);
|
|
|
|
// Extract the file name portion and remove it's file extension. We'll
|
|
// use that name shortly.
|
|
lstrcpy(FileName, FilePart);
|
|
char *lastperiod = strrchr(FileName, '.');
|
|
if (lastperiod) {
|
|
lastperiod[0] = 0;
|
|
}
|
|
|
|
// Replace the executable filename with our error log file name.
|
|
lstrcpy(FilePart, "errorlog.txt");
|
|
HANDLE LogFile = CreateFile(ModuleName, GENERIC_WRITE, 0, 0,
|
|
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, 0);
|
|
if (LogFile == INVALID_HANDLE_VALUE) {
|
|
OutputDebugString("Error creating exception report");
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
// Append to the error log.
|
|
SetFilePointer(LogFile, 0, 0, FILE_END);
|
|
// Print out some blank lines to separate this error log from any previous ones.
|
|
hprintf(LogFile, "\r\n\r\n\r\n\r\n");
|
|
PEXCEPTION_RECORD Exception = data->ExceptionRecord;
|
|
PCONTEXT Context = data->ContextRecord;
|
|
|
|
char CrashModulePathName[MAX_PATH];
|
|
char *CrashModuleFileName = "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, CrashModulePathName, sizeof(CrashModulePathName)) > 0) {
|
|
CrashModuleFileName = GetFilePart(CrashModulePathName);
|
|
}
|
|
|
|
// Print out the beginning of the error log in a Win95 error window
|
|
// compatible format.
|
|
hprintf(LogFile, "%s caused %s in module %s at %04x:%08x.\r\n",
|
|
FileName, GetExceptionDescription(Exception->ExceptionCode),
|
|
CrashModuleFileName, Context->SegCs, Context->Eip);
|
|
hprintf(LogFile, "Exception handler called in %s.\r\n", Message);
|
|
RecordSystemInformation(LogFile);
|
|
// 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 DebugMessage[1000];
|
|
const char* readwrite = "Read from";
|
|
if (Exception->ExceptionInformation[0]) {
|
|
readwrite = "Write to";
|
|
}
|
|
|
|
wsprintf(DebugMessage, "%s location %08x caused an access violation.\r\n", readwrite, Exception->ExceptionInformation[1]);
|
|
|
|
#ifdef _DEBUG
|
|
// The VisualC++ 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("Exception handler: ");
|
|
OutputDebugString(DebugMessage);
|
|
#endif
|
|
|
|
hprintf(LogFile, "%s", DebugMessage);
|
|
}
|
|
|
|
// Print out the register values in a Win95 error window compatible format.
|
|
hprintf(LogFile, "\r\n");
|
|
hprintf(LogFile, "Registers:\r\n");
|
|
hprintf(LogFile, "EAX=%08x CS=%04x EIP=%08x EFLGS=%08x\r\n",
|
|
Context->Eax, Context->SegCs, Context->Eip, Context->EFlags);
|
|
hprintf(LogFile, "EBX=%08x SS=%04x ESP=%08x EBP=%08x\r\n",
|
|
Context->Ebx, Context->SegSs, Context->Esp, Context->Ebp);
|
|
hprintf(LogFile, "ECX=%08x DS=%04x ESI=%08x FS=%04x\r\n",
|
|
Context->Ecx, Context->SegDs, Context->Esi, Context->SegFs);
|
|
hprintf(LogFile, "EDX=%08x ES=%04x EDI=%08x GS=%04x\r\n",
|
|
Context->Edx, Context->SegEs, Context->Edi, Context->SegGs);
|
|
hprintf(LogFile, "Bytes at CS:EIP:\r\n");
|
|
|
|
// 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 '??'.
|
|
unsigned char *code = (unsigned char*)Context->Eip;
|
|
for (int codebyte = 0; codebyte < NumCodeBytes; codebyte++) {
|
|
#ifdef _MSC_VER
|
|
__try {
|
|
#endif
|
|
hprintf(LogFile, "%02x ", code[codebyte]);
|
|
#ifdef _MSC_VER
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER) {
|
|
hprintf(LogFile, "?? ");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef _MSC_VER
|
|
// 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.
|
|
hprintf(LogFile, "\r\n"
|
|
"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 *)Context->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;
|
|
}
|
|
|
|
int Count = 0;
|
|
// 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.
|
|
char buffer[1000] = "";
|
|
const int safetyzone = 50;
|
|
char* nearend = buffer + sizeof(buffer) - safetyzone;
|
|
char* output = buffer;
|
|
while (pStack + 1 <= pStackTop) {
|
|
if ((Count % StackColumns) == 0) {
|
|
output += wsprintf(output, "%08x: ", pStack);
|
|
}
|
|
|
|
char *Suffix = " ";
|
|
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) {
|
|
hprintf(LogFile, "%s", buffer);
|
|
buffer[0] = 0;
|
|
output = buffer;
|
|
}
|
|
}
|
|
// Print out any final characters from the cache.
|
|
hprintf(LogFile, "%s", buffer);
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER) {
|
|
hprintf(LogFile, "Exception encountered during stack dump.\r\n");
|
|
}
|
|
#endif
|
|
|
|
RecordModuleList(LogFile);
|
|
|
|
CloseHandle(LogFile);
|
|
// 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;
|
|
}
|