2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/ExceptionHandler/ExceptionHandler.cpp $
15 * Main file for dealing with exception handling
18 * Revision 1.5 2004/06/11 00:49:34 tigital
19 * casting to shutup gcc
21 * Revision 1.4 2002/06/09 04:41:16 relnev
22 * added copyright header
24 * Revision 1.3 2002/05/26 20:22:48 theoddone33
25 * Most of network/ works
27 * Revision 1.2 2002/05/07 03:16:43 theoddone33
28 * The Great Newline Fix
30 * Revision 1.1.1.1 2002/05/03 03:28:08 root
34 * 1 6/29/99 7:42p Dave
36 * 3 1/19/99 9:07a Allender
37 * removed compiler warning pragma's since we are already ignoring them in
40 * 2 1/18/99 4:34p Allender
41 * added the exception handler routines from Game Developer for structured
42 * exception handling in vsdk code
47 // Copyright © 1998 Bruce Dawson.
49 This source file contains the exception handler for recording error
50 information after crashes. See exceptionhandler.h for information
62 // --------------------
66 // --------------------
68 #define SIXTYFOURK (64*ONEK)
69 #define ONEM (ONEK*ONEK)
70 #define ONEG (ONEK*ONEK*ONEK)
72 // --------------------
76 // --------------------
79 // --------------------
83 // --------------------
86 // --------------------
90 // --------------------
93 // --------------------
97 // --------------------
100 // --------------------
104 // --------------------
106 const int NumCodeBytes = 16; // Number of code bytes to record.
107 const int MaxStackDump = 2048; // Maximum number of DWORDS in stack dumps.
108 const int StackColumns = 8; // Number of columns in stack dump.
110 // --------------------
112 // Internal Functions
114 // --------------------
116 // hprintf behaves similarly to printf, with a few vital differences.
117 // It uses wvsprintf to do the formatting, which is a system routine,
118 // thus avoiding C run time interactions. For similar reasons it
119 // uses WriteFile rather than fwrite.
120 // The one limitation that this imposes is that wvsprintf, and
121 // therefore hprintf, cannot handle floating point numbers.
122 static void hprintf(HANDLE LogFile, char* Format, ...)
127 char buffer[2000]; // wvsprintf never prints more than one K.
130 va_start( arglist, Format);
131 wvsprintf(buffer, Format, arglist);
135 WriteFile(LogFile, buffer, lstrlen(buffer), &NumBytes, 0);
139 // Print the specified FILETIME to output in a human readable format,
140 // without using the C run time.
141 static void PrintTime(char *output, FILETIME TimeToPrint)
147 if (FileTimeToLocalFileTime(&TimeToPrint, &TimeToPrint) &&
148 FileTimeToDosDateTime(&TimeToPrint, &Date, &Time))
150 // What a silly way to print out the file date/time. Oh well,
151 // it works, and I'm not aware of a cleaner way to do it.
152 wsprintf(output, "%d/%d/%d %02d:%02d:%02d",
153 (Date / 32) & 15, Date & 31, (Date / 512) + 1980,
154 (Time / 2048), (Time / 32) & 63, (Time & 31) * 2);
161 // Print information about a code module (DLL or EXE) such as its size,
162 // location, time stamp, etc.
163 static void ShowModuleInfo(HANDLE LogFile, HINSTANCE ModuleHandle)
168 char ModName[MAX_PATH];
170 if (GetModuleFileName(ModuleHandle, ModName, sizeof(ModName)) > 0) {
171 // If GetModuleFileName returns greater than zero then this must
172 // be a valid code module address. Therefore we can try to walk
173 // our way through its structures to find the link time stamp.
174 IMAGE_DOS_HEADER *DosHeader = (IMAGE_DOS_HEADER*)ModuleHandle;
175 if (IMAGE_DOS_SIGNATURE != DosHeader->e_magic) {
179 IMAGE_NT_HEADERS *NTHeader = (IMAGE_NT_HEADERS*)((char *)DosHeader + DosHeader->e_lfanew);
180 if (IMAGE_NT_SIGNATURE != NTHeader->Signature) {
184 // Open the code module file so that we can get its file date
186 HANDLE ModuleFile = CreateFile(ModName, GENERIC_READ,
187 FILE_SHARE_READ, 0, OPEN_EXISTING,
188 FILE_ATTRIBUTE_NORMAL, 0);
189 char TimeBuffer[100] = "";
191 if (ModuleFile != INVALID_HANDLE_VALUE) {
192 FileSize = GetFileSize(ModuleFile, 0);
193 FILETIME LastWriteTime;
194 if (GetFileTime(ModuleFile, 0, 0, &LastWriteTime)) {
195 wsprintf(TimeBuffer, " - file date is ");
196 PrintTime(TimeBuffer + lstrlen(TimeBuffer), LastWriteTime);
198 CloseHandle(ModuleFile);
200 hprintf(LogFile, "%s, loaded at 0x%08x - %d bytes - %08x%s\r\n",
201 ModName, ModuleHandle, FileSize,
202 NTHeader->FileHeader.TimeDateStamp, TimeBuffer);
205 // Handle any exceptions by continuing from this point.
206 __except(EXCEPTION_EXECUTE_HANDLER)
212 // Scan memory looking for code modules (DLLs or EXEs). VirtualQuery is used
213 // to find all the blocks of address space that were reserved or committed,
214 // and ShowModuleInfo will display module information if they are code
217 static void RecordModuleList(HANDLE LogFile)
222 hprintf(LogFile, "\r\n"
223 "\tModule list: names, addresses, sizes, time stamps "
224 "and file times:\r\n");
225 SYSTEM_INFO SystemInfo;
226 GetSystemInfo(&SystemInfo);
227 const size_t PageSize = SystemInfo.dwPageSize;
228 // Set NumPages to the number of pages in the 4GByte address space,
229 // while being careful to avoid overflowing ints.
230 const size_t NumPages = 4 * size_t(ONEG / PageSize);
232 void *LastAllocationBase = 0;
233 while (pageNum < NumPages) {
234 MEMORY_BASIC_INFORMATION MemInfo;
235 if (VirtualQuery((void *)(pageNum * PageSize), &MemInfo, sizeof(MemInfo))) {
236 if (MemInfo.RegionSize > 0) {
238 // Adjust the page number to skip over this block of memory.
239 pageNum += MemInfo.RegionSize / PageSize;
240 if (MemInfo.State == MEM_COMMIT && MemInfo.AllocationBase > LastAllocationBase) {
241 // Look for new blocks of committed memory, and try
242 // recording their module names - this will fail
243 // gracefully if they aren't code modules.
244 LastAllocationBase = MemInfo.AllocationBase;
245 ShowModuleInfo(LogFile, (HINSTANCE)LastAllocationBase);
248 pageNum += SIXTYFOURK / PageSize;
251 // If VirtualQuery fails we advance by 64K because that is the
252 // granularity of address space doled out by VirtualAlloc().
253 pageNum += SIXTYFOURK / PageSize;
259 // Record information about the user's system, such as processor type, amount
262 static void RecordSystemInformation(HANDLE LogFile)
267 FILETIME CurrentTime;
268 GetSystemTimeAsFileTime(&CurrentTime);
269 char TimeBuffer[100];
270 PrintTime(TimeBuffer, CurrentTime);
271 hprintf(LogFile, "Error occurred at %s.\r\n", TimeBuffer);
272 char ModuleName[MAX_PATH];
273 if (GetModuleFileName(0, ModuleName, sizeof(ModuleName)) <= 0) {
274 lstrcpy(ModuleName, "Unknown");
277 DWORD UserNameSize = sizeof(UserName);
278 if (!GetUserName(UserName, &UserNameSize)) {
279 lstrcpy(UserName, "Unknown");
281 hprintf(LogFile, "%s, run by %s.\r\n", ModuleName, UserName);
283 SYSTEM_INFO SystemInfo;
284 GetSystemInfo(&SystemInfo);
285 hprintf(LogFile, "%d processor(s), type %d.\r\n",
286 SystemInfo.dwNumberOfProcessors, SystemInfo.dwProcessorType);
288 MEMORYSTATUS MemInfo;
289 MemInfo.dwLength = sizeof(MemInfo);
290 GlobalMemoryStatus(&MemInfo);
291 // Print out the amount of physical memory, rounded up.
292 hprintf(LogFile, "%d MBytes physical memory.\r\n", (MemInfo.dwTotalPhys +
297 // Translate the exception code into something human readable.
299 static const char *GetExceptionDescription(DWORD ExceptionCode)
301 struct ExceptionNames
307 ExceptionNames ExceptionMap[] =
309 {0x40010005, "a Control-C"},
310 {0x40010008, "a Control-Break"},
311 {0x80000002, "a Datatype Misalignment"},
312 {0x80000003, "a Breakpoint"},
313 {0xc0000005, "an Access Violation"},
314 {0xc0000006, "an In Page Error"},
315 {0xc0000017, "a No Memory"},
316 {0xc000001d, "an Illegal Instruction"},
317 {0xc0000025, "a Noncontinuable Exception"},
318 {0xc0000026, "an Invalid Disposition"},
319 {0xc000008c, "a Array Bounds Exceeded"},
320 {0xc000008d, "a Float Denormal Operand"},
321 {0xc000008e, "a Float Divide by Zero"},
322 {0xc000008f, "a Float Inexact Result"},
323 {0xc0000090, "a Float Invalid Operation"},
324 {0xc0000091, "a Float Overflow"},
325 {0xc0000092, "a Float Stack Check"},
326 {0xc0000093, "a Float Underflow"},
327 {0xc0000094, "an Integer Divide by Zero"},
328 {0xc0000095, "an Integer Overflow"},
329 {0xc0000096, "a Privileged Instruction"},
330 {0xc00000fD, "a Stack Overflow"},
331 {0xc0000142, "a DLL Initialization Failed"},
332 {0xe06d7363, "a Microsoft C++ Exception"},
335 for (int i = 0; i < (int)(sizeof(ExceptionMap) / sizeof(ExceptionMap[0])); i++) {
336 if (ExceptionCode == ExceptionMap[i].ExceptionCode) {
337 return ExceptionMap[i].ExceptionName;
341 return "Unknown exception type";
344 static char* GetFilePart(char *source)
346 char *result = strrchr(source, '\\');
355 // --------------------
357 // External Functions
359 // --------------------
362 // Entry point into the main exception handling routine. This routine is put into an except()
363 // statment at the beginning of a thread and is called anytime that there is a program exception
364 // The data is stored in a file called ErrorLog.txt in the data directory.
366 // data: pointer to the exception data
367 // Message: Any message that should be printed out in the error log file
372 int __cdecl RecordExceptionInfo(PEXCEPTION_POINTERS data, const char *Message)
374 static bool BeenHere = false;
376 // Going recursive! That must mean this routine crashed!
378 return EXCEPTION_CONTINUE_SEARCH;
383 char ModuleName[MAX_PATH];
384 char FileName[MAX_PATH] = "Unknown";
385 // Create a filename to record the error information to.
386 // Storing it in the executable directory works well.
387 if (GetModuleFileName(0, ModuleName, sizeof(ModuleName)) <= 0) {
391 char *FilePart = GetFilePart(ModuleName);
393 // Extract the file name portion and remove it's file extension. We'll
394 // use that name shortly.
395 lstrcpy(FileName, FilePart);
396 char *lastperiod = strrchr(FileName, '.');
401 // Replace the executable filename with our error log file name.
402 lstrcpy(FilePart, "errorlog.txt");
403 HANDLE LogFile = CreateFile(ModuleName, GENERIC_WRITE, 0, 0,
404 OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, 0);
405 if (LogFile == INVALID_HANDLE_VALUE) {
406 OutputDebugString("Error creating exception report");
407 return EXCEPTION_CONTINUE_SEARCH;
410 // Append to the error log.
411 SetFilePointer(LogFile, 0, 0, FILE_END);
412 // Print out some blank lines to separate this error log from any previous ones.
413 hprintf(LogFile, "\r\n\r\n\r\n\r\n");
414 PEXCEPTION_RECORD Exception = data->ExceptionRecord;
415 PCONTEXT Context = data->ContextRecord;
417 char CrashModulePathName[MAX_PATH];
418 char *CrashModuleFileName = "Unknown";
419 MEMORY_BASIC_INFORMATION MemInfo;
420 // VirtualQuery can be used to get the allocation base associated with a
421 // code address, which is the same as the ModuleHandle. This can be used
422 // to get the filename of the module that the crash happened in.
423 if (VirtualQuery((void*)Context->Eip, &MemInfo, sizeof(MemInfo)) && GetModuleFileName((HINSTANCE)MemInfo.AllocationBase, CrashModulePathName, sizeof(CrashModulePathName)) > 0) {
424 CrashModuleFileName = GetFilePart(CrashModulePathName);
427 // Print out the beginning of the error log in a Win95 error window
428 // compatible format.
429 hprintf(LogFile, "%s caused %s in module %s at %04x:%08x.\r\n",
430 FileName, GetExceptionDescription(Exception->ExceptionCode),
431 CrashModuleFileName, Context->SegCs, Context->Eip);
432 hprintf(LogFile, "Exception handler called in %s.\r\n", Message);
433 RecordSystemInformation(LogFile);
434 // If the exception was an access violation, print out some additional
435 // information, to the error log and the debugger.
436 if (Exception->ExceptionCode == STATUS_ACCESS_VIOLATION && Exception->NumberParameters >= 2) {
437 char DebugMessage[1000];
438 const char* readwrite = "Read from";
439 if (Exception->ExceptionInformation[0]) {
440 readwrite = "Write to";
443 wsprintf(DebugMessage, "%s location %08x caused an access violation.\r\n", readwrite, Exception->ExceptionInformation[1]);
446 // The VisualC++ debugger doesn't actually tell you whether a read
447 // or a write caused the access violation, nor does it tell what
448 // address was being read or written. So I fixed that.
449 OutputDebugString("Exception handler: ");
450 OutputDebugString(DebugMessage);
453 hprintf(LogFile, "%s", DebugMessage);
456 // Print out the register values in a Win95 error window compatible format.
457 hprintf(LogFile, "\r\n");
458 hprintf(LogFile, "Registers:\r\n");
459 hprintf(LogFile, "EAX=%08x CS=%04x EIP=%08x EFLGS=%08x\r\n",
460 Context->Eax, Context->SegCs, Context->Eip, Context->EFlags);
461 hprintf(LogFile, "EBX=%08x SS=%04x ESP=%08x EBP=%08x\r\n",
462 Context->Ebx, Context->SegSs, Context->Esp, Context->Ebp);
463 hprintf(LogFile, "ECX=%08x DS=%04x ESI=%08x FS=%04x\r\n",
464 Context->Ecx, Context->SegDs, Context->Esi, Context->SegFs);
465 hprintf(LogFile, "EDX=%08x ES=%04x EDI=%08x GS=%04x\r\n",
466 Context->Edx, Context->SegEs, Context->Edi, Context->SegGs);
467 hprintf(LogFile, "Bytes at CS:EIP:\r\n");
469 // Print out the bytes of code at the instruction pointer. Since the
470 // crash may have been caused by an instruction pointer that was bad,
471 // this code needs to be wrapped in an exception handler, in case there
472 // is no memory to read. If the dereferencing of code[] fails, the
473 // exception handler will print '??'.
474 unsigned char *code = (unsigned char*)Context->Eip;
475 for (int codebyte = 0; codebyte < NumCodeBytes; codebyte++) {
477 hprintf(LogFile, "%02x ", code[codebyte]);
480 __except(EXCEPTION_EXECUTE_HANDLER) {
481 hprintf(LogFile, "?? ");
485 // Time to print part or all of the stack to the error log. This allows
486 // us to figure out the call stack, parameters, local variables, etc.
487 hprintf(LogFile, "\r\n"
490 // Esp contains the bottom of the stack, or at least the bottom of
491 // the currently used area.
492 DWORD* pStack = (DWORD *)Context->Esp;
496 // Load the top (highest address) of the stack from the
497 // thread information block. It will be found there in
498 // Win9x and Windows NT.
502 if (pStackTop > pStack + MaxStackDump) {
503 pStackTop = pStack + MaxStackDump;
507 // Too many calls to WriteFile can take a long time, causing
508 // confusing delays when programs crash. Therefore I implemented
509 // simple buffering for the stack dumping code instead of calling
511 char buffer[1000] = "";
512 const int safetyzone = 50;
513 char* nearend = buffer + sizeof(buffer) - safetyzone;
514 char* output = buffer;
515 while (pStack + 1 <= pStackTop) {
516 if ((Count % StackColumns) == 0) {
517 output += wsprintf(output, "%08x: ", pStack);
521 if ((++Count % StackColumns) == 0 || pStack + 2 > pStackTop) {
525 output += wsprintf(output, "%08x%s", *pStack, Suffix);
527 // Check for when the buffer is almost full, and flush it to disk.
528 if (output > nearend) {
529 hprintf(LogFile, "%s", buffer);
534 // Print out any final characters from the cache.
535 hprintf(LogFile, "%s", buffer);
537 __except(EXCEPTION_EXECUTE_HANDLER) {
538 hprintf(LogFile, "Exception encountered during stack dump.\r\n");
541 RecordModuleList(LogFile);
543 CloseHandle(LogFile);
544 // Return the magic value which tells Win32 that this handler didn't
545 // actually handle the exception - so that things will proceed as per
547 return EXCEPTION_CONTINUE_SEARCH;