]> icculus.org git repositories - taylor/freespace2.git/blob - src/exceptionhandler/exceptionhandler.cpp
merge gamepad support
[taylor/freespace2.git] / src / exceptionhandler / exceptionhandler.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
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
6  * the source.
7  */
8
9 /*
10  * $Logfile: /Freespace2/code/ExceptionHandler/ExceptionHandler.cpp $
11  * $Revision$
12  * $Date$
13  * $Author$
14  *
15  * Main file for dealing with exception handling
16  *
17  * $Log$
18  * Revision 1.5  2004/06/11 00:49:34  tigital
19  * casting to shutup gcc
20  *
21  * Revision 1.4  2002/06/09 04:41:16  relnev
22  * added copyright header
23  *
24  * Revision 1.3  2002/05/26 20:22:48  theoddone33
25  * Most of network/ works
26  *
27  * Revision 1.2  2002/05/07 03:16:43  theoddone33
28  * The Great Newline Fix
29  *
30  * Revision 1.1.1.1  2002/05/03 03:28:08  root
31  * Initial import.
32  *
33  * 
34  * 1     6/29/99 7:42p Dave
35  * 
36  * 3     1/19/99 9:07a Allender
37  * removed compiler warning pragma's since we are already ignoring them in
38  * vtypes.h
39  * 
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
43  *
44  * $NoKeywords: $
45  */
46
47 // Copyright © 1998 Bruce Dawson.
48 /*
49 This source file contains the exception handler for recording error
50 information after crashes. See exceptionhandler.h for information
51 on how to hook it in.
52 */
53
54
55 #ifndef PLAT_UNIX
56 #include <windows.h>
57 #else
58 #include <stdarg.h>
59 #include "pstypes.h"
60 #endif
61
62 // --------------------
63 //
64 // Defines
65 //
66 // --------------------
67 #define ONEK                    1024
68 #define SIXTYFOURK              (64*ONEK)
69 #define ONEM                    (ONEK*ONEK)
70 #define ONEG                    (ONEK*ONEK*ONEK) 
71
72 // --------------------
73 //
74 // Enumerated Types
75 //
76 // --------------------
77
78
79 // --------------------
80 //
81 // Structures
82 //
83 // --------------------
84
85
86 // --------------------
87 //
88 // Classes
89 //
90 // --------------------
91
92
93 // --------------------
94 //
95 // Global Variables
96 //
97 // --------------------
98
99
100 // --------------------
101 //
102 // Local Variables
103 //
104 // --------------------
105
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.
109
110 // --------------------
111 //
112 // Internal Functions
113 //
114 // --------------------
115
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, ...)
123 {
124 #ifdef PLAT_UNIX
125         STUB_FUNCTION;
126 #else
127         char buffer[2000];      // wvsprintf never prints more than one K.
128
129         va_list arglist;
130         va_start( arglist, Format);
131         wvsprintf(buffer, Format, arglist);
132         va_end( arglist);
133
134         DWORD NumBytes;
135         WriteFile(LogFile, buffer, lstrlen(buffer), &NumBytes, 0);
136 #endif
137 }
138
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)
142 {
143 #ifdef PLAT_UNIX
144         STUB_FUNCTION;
145 #else
146         WORD Date, Time;
147         if (FileTimeToLocalFileTime(&TimeToPrint, &TimeToPrint) &&
148                                 FileTimeToDosDateTime(&TimeToPrint, &Date, &Time))
149         {
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);
155         } else {
156                 output[0] = 0;
157         }
158 #endif
159 }
160
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)
164 {
165 #ifdef PLAT_UNIX
166         STUB_FUNCTION;
167 #else
168         char ModName[MAX_PATH];
169         __try {
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) {
176                     return;
177                         }
178
179                         IMAGE_NT_HEADERS *NTHeader = (IMAGE_NT_HEADERS*)((char *)DosHeader + DosHeader->e_lfanew);
180                         if (IMAGE_NT_SIGNATURE != NTHeader->Signature) {
181                     return;
182                         }
183
184                         // Open the code module file so that we can get its file date
185                         // and size.
186                         HANDLE ModuleFile = CreateFile(ModName, GENERIC_READ, 
187                                                 FILE_SHARE_READ, 0, OPEN_EXISTING,
188                                                 FILE_ATTRIBUTE_NORMAL, 0);
189                         char TimeBuffer[100] = "";
190                         DWORD FileSize = 0;
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);
197                                 }
198                                 CloseHandle(ModuleFile);
199                         }
200                         hprintf(LogFile, "%s, loaded at 0x%08x - %d bytes - %08x%s\r\n",
201                                                 ModName, ModuleHandle, FileSize,
202                                                 NTHeader->FileHeader.TimeDateStamp, TimeBuffer);
203                 }
204         }
205         // Handle any exceptions by continuing from this point.
206         __except(EXCEPTION_EXECUTE_HANDLER)
207         {
208         }
209 #endif
210 }
211
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
215 // modules.
216
217 static void RecordModuleList(HANDLE LogFile)
218 {
219 #ifdef PLAT_UNIX
220         STUB_FUNCTION;
221 #else
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);
231         size_t pageNum = 0;
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) {
237
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);
246                                 }
247                         } else {
248                                 pageNum += SIXTYFOURK / PageSize;
249                         }
250                 } else {
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;
254                 }
255         }
256 #endif
257 }
258
259 // Record information about the user's system, such as processor type, amount
260 // of memory, etc.
261
262 static void RecordSystemInformation(HANDLE LogFile)
263 {
264 #ifdef PLAT_UNIX
265         STUB_FUNCTION;
266 #else
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");
275         }
276         char    UserName[200];
277         DWORD UserNameSize = sizeof(UserName);
278         if (!GetUserName(UserName, &UserNameSize)) {
279                 lstrcpy(UserName, "Unknown");
280         }
281         hprintf(LogFile, "%s, run by %s.\r\n", ModuleName, UserName);
282
283         SYSTEM_INFO     SystemInfo;
284         GetSystemInfo(&SystemInfo);
285         hprintf(LogFile, "%d processor(s), type %d.\r\n",
286                                 SystemInfo.dwNumberOfProcessors, SystemInfo.dwProcessorType);
287
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 +
293                                 ONEM - 1) / ONEM);
294 #endif
295 }
296
297 // Translate the exception code into something human readable.
298
299 static const char *GetExceptionDescription(DWORD ExceptionCode)
300 {
301         struct ExceptionNames
302         {
303                 DWORD   ExceptionCode;
304                 char*   ExceptionName;
305         };
306
307         ExceptionNames ExceptionMap[] =
308         {
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"},
333         };
334
335         for (int i = 0; i < (int)(sizeof(ExceptionMap) / sizeof(ExceptionMap[0])); i++) {
336                 if (ExceptionCode == ExceptionMap[i].ExceptionCode) {
337                         return ExceptionMap[i].ExceptionName;
338                 }
339         }
340
341         return "Unknown exception type";
342 }
343
344 static char* GetFilePart(char *source)
345 {
346         char *result = strrchr(source, '\\');
347         if (result) {
348                 result++;
349         } else {
350                 result = source;
351         }
352         return result;
353 }
354
355 // --------------------
356 //
357 // External Functions
358 //
359 // --------------------
360
361
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.
365 //
366 // data:                        pointer to the exception data
367 // Message:             Any message     that should be printed out in the error log file
368 //
369 // returns: 
370 //
371 #ifndef PLAT_UNIX
372 int __cdecl RecordExceptionInfo(PEXCEPTION_POINTERS data, const char *Message)
373 {
374         static bool BeenHere = false;
375
376         // Going recursive! That must mean this routine crashed!
377         if (BeenHere) {
378                 return EXCEPTION_CONTINUE_SEARCH;
379         }
380
381         BeenHere = true;
382
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) {
388                 ModuleName[0] = 0;
389         }
390
391         char *FilePart = GetFilePart(ModuleName);
392
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, '.');
397         if (lastperiod) {
398                 lastperiod[0] = 0;
399         }
400
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;
408         }
409
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;
416
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);
425         }
426
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";
441                 }
442
443                 wsprintf(DebugMessage, "%s location %08x caused an access violation.\r\n", readwrite, Exception->ExceptionInformation[1]);
444
445 #ifdef  _DEBUG
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);
451 #endif
452
453                 hprintf(LogFile, "%s", DebugMessage);
454         }
455
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");
468
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++) {
476                 __try {
477                         hprintf(LogFile, "%02x ", code[codebyte]);
478
479                 }
480                 __except(EXCEPTION_EXECUTE_HANDLER) {
481                         hprintf(LogFile, "?? ");
482                 }
483         }
484
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"
488                                          "Stack dump:\r\n");
489         __try {
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;
493                 DWORD* pStackTop;
494                 __asm
495                 {
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.
499                         mov     eax, fs:[4]
500                         mov pStackTop, eax
501                 }
502                 if (pStackTop > pStack + MaxStackDump) {
503                         pStackTop = pStack + MaxStackDump;
504                 }
505
506                 int Count = 0;
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
510                 // hprintf directly.
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);
518                         }
519
520                         char *Suffix = " ";
521                         if ((++Count % StackColumns) == 0 || pStack + 2 > pStackTop) {
522                                 Suffix = "\r\n";
523                         }
524
525                         output += wsprintf(output, "%08x%s", *pStack, Suffix);
526                         pStack++;
527                         // Check for when the buffer is almost full, and flush it to disk.
528                         if (output > nearend) {
529                                 hprintf(LogFile, "%s", buffer);
530                                 buffer[0] = 0;
531                                 output = buffer;
532                         }
533                 }
534                 // Print out any final characters from the cache.
535                 hprintf(LogFile, "%s", buffer);
536         }
537         __except(EXCEPTION_EXECUTE_HANDLER) {
538                 hprintf(LogFile, "Exception encountered during stack dump.\r\n");
539         }
540
541         RecordModuleList(LogFile);
542
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
546         // normal.
547         return EXCEPTION_CONTINUE_SEARCH;
548 }
549 #endif
550