]> icculus.org git repositories - taylor/freespace2.git/blob - src/exceptionhandler/exceptionhandler.cpp
Initial revision
[taylor/freespace2.git] / src / exceptionhandler / exceptionhandler.cpp
1 /*
2  * $Logfile: /Freespace2/code/ExceptionHandler/ExceptionHandler.cpp $
3  * $Revision$
4  * $Date$
5  * $Author$
6  *
7  * Main file for dealing with exception handling
8  *
9  * $Log$
10  * Revision 1.1  2002/05/03 03:28:08  root
11  * Initial revision
12  *
13  * 
14  * 1     6/29/99 7:42p Dave
15  * 
16  * 3     1/19/99 9:07a Allender
17  * removed compiler warning pragma's since we are already ignoring them in
18  * vtypes.h
19  * 
20  * 2     1/18/99 4:34p Allender
21  * added the exception handler routines from Game Developer for structured
22  * exception handling in vsdk code
23  *
24  * $NoKeywords: $
25  */
26
27 // Copyright © 1998 Bruce Dawson.
28 /*
29 This source file contains the exception handler for recording error
30 information after crashes. See exceptionhandler.h for information
31 on how to hook it in.
32 */
33
34
35 #include <windows.h>
36
37 // --------------------
38 //
39 // Defines
40 //
41 // --------------------
42 #define ONEK                    1024
43 #define SIXTYFOURK              (64*ONEK)
44 #define ONEM                    (ONEK*ONEK)
45 #define ONEG                    (ONEK*ONEK*ONEK) 
46
47 // --------------------
48 //
49 // Enumerated Types
50 //
51 // --------------------
52
53
54 // --------------------
55 //
56 // Structures
57 //
58 // --------------------
59
60
61 // --------------------
62 //
63 // Classes
64 //
65 // --------------------
66
67
68 // --------------------
69 //
70 // Global Variables
71 //
72 // --------------------
73
74
75 // --------------------
76 //
77 // Local Variables
78 //
79 // --------------------
80
81 const int NumCodeBytes = 16;    // Number of code bytes to record.
82 const int MaxStackDump = 2048;  // Maximum number of DWORDS in stack dumps.
83 const int StackColumns = 8;             // Number of columns in stack dump.
84
85 // --------------------
86 //
87 // Internal Functions
88 //
89 // --------------------
90
91 // hprintf behaves similarly to printf, with a few vital differences.
92 // It uses wvsprintf to do the formatting, which is a system routine,
93 // thus avoiding C run time interactions. For similar reasons it
94 // uses WriteFile rather than fwrite.
95 // The one limitation that this imposes is that wvsprintf, and
96 // therefore hprintf, cannot handle floating point numbers.
97 static void hprintf(HANDLE LogFile, char* Format, ...)
98 {
99         char buffer[2000];      // wvsprintf never prints more than one K.
100
101         va_list arglist;
102         va_start( arglist, Format);
103         wvsprintf(buffer, Format, arglist);
104         va_end( arglist);
105
106         DWORD NumBytes;
107         WriteFile(LogFile, buffer, lstrlen(buffer), &NumBytes, 0);
108 }
109
110 // Print the specified FILETIME to output in a human readable format,
111 // without using the C run time.
112 static void PrintTime(char *output, FILETIME TimeToPrint)
113 {
114         WORD Date, Time;
115         if (FileTimeToLocalFileTime(&TimeToPrint, &TimeToPrint) &&
116                                 FileTimeToDosDateTime(&TimeToPrint, &Date, &Time))
117         {
118                 // What a silly way to print out the file date/time. Oh well,
119                 // it works, and I'm not aware of a cleaner way to do it.
120                 wsprintf(output, "%d/%d/%d %02d:%02d:%02d",
121                                         (Date / 32) & 15, Date & 31, (Date / 512) + 1980,
122                                         (Time / 2048), (Time / 32) & 63, (Time & 31) * 2);
123         } else {
124                 output[0] = 0;
125         }
126 }
127
128 // Print information about a code module (DLL or EXE) such as its size,
129 // location, time stamp, etc.
130 static void ShowModuleInfo(HANDLE LogFile, HINSTANCE ModuleHandle)
131 {
132         char ModName[MAX_PATH];
133         __try {
134                 if (GetModuleFileName(ModuleHandle, ModName, sizeof(ModName)) > 0) {
135                         // If GetModuleFileName returns greater than zero then this must
136                         // be a valid code module address. Therefore we can try to walk
137                         // our way through its structures to find the link time stamp.
138                         IMAGE_DOS_HEADER *DosHeader = (IMAGE_DOS_HEADER*)ModuleHandle;
139                         if (IMAGE_DOS_SIGNATURE != DosHeader->e_magic) {
140                     return;
141                         }
142
143                         IMAGE_NT_HEADERS *NTHeader = (IMAGE_NT_HEADERS*)((char *)DosHeader + DosHeader->e_lfanew);
144                         if (IMAGE_NT_SIGNATURE != NTHeader->Signature) {
145                     return;
146                         }
147
148                         // Open the code module file so that we can get its file date
149                         // and size.
150                         HANDLE ModuleFile = CreateFile(ModName, GENERIC_READ, 
151                                                 FILE_SHARE_READ, 0, OPEN_EXISTING,
152                                                 FILE_ATTRIBUTE_NORMAL, 0);
153                         char TimeBuffer[100] = "";
154                         DWORD FileSize = 0;
155                         if (ModuleFile != INVALID_HANDLE_VALUE) {
156                                 FileSize = GetFileSize(ModuleFile, 0);
157                                 FILETIME        LastWriteTime;
158                                 if (GetFileTime(ModuleFile, 0, 0, &LastWriteTime)) {
159                                         wsprintf(TimeBuffer, " - file date is ");
160                                         PrintTime(TimeBuffer + lstrlen(TimeBuffer), LastWriteTime);
161                                 }
162                                 CloseHandle(ModuleFile);
163                         }
164                         hprintf(LogFile, "%s, loaded at 0x%08x - %d bytes - %08x%s\r\n",
165                                                 ModName, ModuleHandle, FileSize,
166                                                 NTHeader->FileHeader.TimeDateStamp, TimeBuffer);
167                 }
168         }
169         // Handle any exceptions by continuing from this point.
170         __except(EXCEPTION_EXECUTE_HANDLER)
171         {
172         }
173 }
174
175 // Scan memory looking for code modules (DLLs or EXEs). VirtualQuery is used
176 // to find all the blocks of address space that were reserved or committed,
177 // and ShowModuleInfo will display module information if they are code
178 // modules.
179
180 static void RecordModuleList(HANDLE LogFile)
181 {
182         hprintf(LogFile, "\r\n"
183                                          "\tModule list: names, addresses, sizes, time stamps "
184                         "and file times:\r\n");
185         SYSTEM_INFO     SystemInfo;
186         GetSystemInfo(&SystemInfo);
187         const size_t PageSize = SystemInfo.dwPageSize;
188         // Set NumPages to the number of pages in the 4GByte address space,
189         // while being careful to avoid overflowing ints.
190         const size_t NumPages = 4 * size_t(ONEG / PageSize);
191         size_t pageNum = 0;
192         void *LastAllocationBase = 0;
193         while (pageNum < NumPages) {
194                 MEMORY_BASIC_INFORMATION        MemInfo;
195                 if (VirtualQuery((void *)(pageNum * PageSize), &MemInfo, sizeof(MemInfo))) {
196                         if (MemInfo.RegionSize > 0) {
197
198                                 // Adjust the page number to skip over this block of memory.
199                                 pageNum += MemInfo.RegionSize / PageSize;
200                                 if (MemInfo.State == MEM_COMMIT && MemInfo.AllocationBase > LastAllocationBase) {
201                                         // Look for new blocks of committed memory, and try
202                                         // recording their module names - this will fail
203                                         // gracefully if they aren't code modules.
204                                         LastAllocationBase = MemInfo.AllocationBase;
205                                         ShowModuleInfo(LogFile, (HINSTANCE)LastAllocationBase);
206                                 }
207                         } else {
208                                 pageNum += SIXTYFOURK / PageSize;
209                         }
210                 } else {
211                         // If VirtualQuery fails we advance by 64K because that is the
212                         // granularity of address space doled out by VirtualAlloc().
213                         pageNum += SIXTYFOURK / PageSize;
214                 }
215         }
216 }
217
218 // Record information about the user's system, such as processor type, amount
219 // of memory, etc.
220
221 static void RecordSystemInformation(HANDLE LogFile)
222 {
223         FILETIME        CurrentTime;
224         GetSystemTimeAsFileTime(&CurrentTime);
225         char TimeBuffer[100];
226         PrintTime(TimeBuffer, CurrentTime);
227         hprintf(LogFile, "Error occurred at %s.\r\n", TimeBuffer);
228         char    ModuleName[MAX_PATH];
229         if (GetModuleFileName(0, ModuleName, sizeof(ModuleName)) <= 0) {
230                 lstrcpy(ModuleName, "Unknown");
231         }
232         char    UserName[200];
233         DWORD UserNameSize = sizeof(UserName);
234         if (!GetUserName(UserName, &UserNameSize)) {
235                 lstrcpy(UserName, "Unknown");
236         }
237         hprintf(LogFile, "%s, run by %s.\r\n", ModuleName, UserName);
238
239         SYSTEM_INFO     SystemInfo;
240         GetSystemInfo(&SystemInfo);
241         hprintf(LogFile, "%d processor(s), type %d.\r\n",
242                                 SystemInfo.dwNumberOfProcessors, SystemInfo.dwProcessorType);
243
244         MEMORYSTATUS    MemInfo;
245         MemInfo.dwLength = sizeof(MemInfo);
246         GlobalMemoryStatus(&MemInfo);
247         // Print out the amount of physical memory, rounded up.
248         hprintf(LogFile, "%d MBytes physical memory.\r\n", (MemInfo.dwTotalPhys +
249                                 ONEM - 1) / ONEM);
250 }
251
252 // Translate the exception code into something human readable.
253
254 static const char *GetExceptionDescription(DWORD ExceptionCode)
255 {
256         struct ExceptionNames
257         {
258                 DWORD   ExceptionCode;
259                 char*   ExceptionName;
260         };
261
262         ExceptionNames ExceptionMap[] =
263         {
264                 {0x40010005, "a Control-C"},
265                 {0x40010008, "a Control-Break"},
266                 {0x80000002, "a Datatype Misalignment"},
267                 {0x80000003, "a Breakpoint"},
268                 {0xc0000005, "an Access Violation"},
269                 {0xc0000006, "an In Page Error"},
270                 {0xc0000017, "a No Memory"},
271                 {0xc000001d, "an Illegal Instruction"},
272                 {0xc0000025, "a Noncontinuable Exception"},
273                 {0xc0000026, "an Invalid Disposition"},
274                 {0xc000008c, "a Array Bounds Exceeded"},
275                 {0xc000008d, "a Float Denormal Operand"},
276                 {0xc000008e, "a Float Divide by Zero"},
277                 {0xc000008f, "a Float Inexact Result"},
278                 {0xc0000090, "a Float Invalid Operation"},
279                 {0xc0000091, "a Float Overflow"},
280                 {0xc0000092, "a Float Stack Check"},
281                 {0xc0000093, "a Float Underflow"},
282                 {0xc0000094, "an Integer Divide by Zero"},
283                 {0xc0000095, "an Integer Overflow"},
284                 {0xc0000096, "a Privileged Instruction"},
285                 {0xc00000fD, "a Stack Overflow"},
286                 {0xc0000142, "a DLL Initialization Failed"},
287                 {0xe06d7363, "a Microsoft C++ Exception"},
288         };
289
290         for (int i = 0; i < sizeof(ExceptionMap) / sizeof(ExceptionMap[0]); i++) {
291                 if (ExceptionCode == ExceptionMap[i].ExceptionCode) {
292                         return ExceptionMap[i].ExceptionName;
293                 }
294         }
295
296         return "Unknown exception type";
297 }
298
299 static char* GetFilePart(char *source)
300 {
301         char *result = strrchr(source, '\\');
302         if (result) {
303                 result++;
304         } else {
305                 result = source;
306         }
307         return result;
308 }
309
310 // --------------------
311 //
312 // External Functions
313 //
314 // --------------------
315
316
317 // Entry point into the main exception handling routine.  This routine is put into an except()
318 // statment at the beginning of a thread and is called anytime that there is a program exception
319 // The data is stored in a file called ErrorLog.txt in the data directory.
320 //
321 // data:                        pointer to the exception data
322 // Message:             Any message     that should be printed out in the error log file
323 //
324 // returns: 
325 //
326 int __cdecl RecordExceptionInfo(PEXCEPTION_POINTERS data, const char *Message)
327 {
328         static bool BeenHere = false;
329
330         // Going recursive! That must mean this routine crashed!
331         if (BeenHere) {
332                 return EXCEPTION_CONTINUE_SEARCH;
333         }
334
335         BeenHere = true;
336
337         char    ModuleName[MAX_PATH];
338         char    FileName[MAX_PATH] = "Unknown";
339         // Create a filename to record the error information to.
340         // Storing it in the executable directory works well.
341         if (GetModuleFileName(0, ModuleName, sizeof(ModuleName)) <= 0) {
342                 ModuleName[0] = 0;
343         }
344
345         char *FilePart = GetFilePart(ModuleName);
346
347         // Extract the file name portion and remove it's file extension. We'll
348         // use that name shortly.
349         lstrcpy(FileName, FilePart);
350         char *lastperiod = strrchr(FileName, '.');
351         if (lastperiod) {
352                 lastperiod[0] = 0;
353         }
354
355         // Replace the executable filename with our error log file name.
356         lstrcpy(FilePart, "errorlog.txt");
357         HANDLE LogFile = CreateFile(ModuleName, GENERIC_WRITE, 0, 0,
358                                 OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, 0);
359         if (LogFile == INVALID_HANDLE_VALUE) {
360                 OutputDebugString("Error creating exception report");
361                 return EXCEPTION_CONTINUE_SEARCH;
362         }
363
364         // Append to the error log.
365         SetFilePointer(LogFile, 0, 0, FILE_END);
366         // Print out some blank lines to separate this error log from any previous ones.
367         hprintf(LogFile, "\r\n\r\n\r\n\r\n");
368         PEXCEPTION_RECORD       Exception = data->ExceptionRecord;
369         PCONTEXT                        Context = data->ContextRecord;
370
371         char    CrashModulePathName[MAX_PATH];
372         char    *CrashModuleFileName = "Unknown";
373         MEMORY_BASIC_INFORMATION        MemInfo;
374         // VirtualQuery can be used to get the allocation base associated with a
375         // code address, which is the same as the ModuleHandle. This can be used
376         // to get the filename of the module that the crash happened in.
377         if (VirtualQuery((void*)Context->Eip, &MemInfo, sizeof(MemInfo)) && GetModuleFileName((HINSTANCE)MemInfo.AllocationBase, CrashModulePathName, sizeof(CrashModulePathName)) > 0) {
378                 CrashModuleFileName = GetFilePart(CrashModulePathName);
379         }
380
381         // Print out the beginning of the error log in a Win95 error window
382         // compatible format.
383         hprintf(LogFile, "%s caused %s in module %s at %04x:%08x.\r\n",
384                                 FileName, GetExceptionDescription(Exception->ExceptionCode),
385                                 CrashModuleFileName, Context->SegCs, Context->Eip);
386         hprintf(LogFile, "Exception handler called in %s.\r\n", Message);
387         RecordSystemInformation(LogFile);
388         // If the exception was an access violation, print out some additional
389         // information, to the error log and the debugger.
390         if (Exception->ExceptionCode == STATUS_ACCESS_VIOLATION && Exception->NumberParameters >= 2) {
391                 char DebugMessage[1000];
392                 const char* readwrite = "Read from";
393                 if (Exception->ExceptionInformation[0]) {
394                         readwrite = "Write to";
395                 }
396
397                 wsprintf(DebugMessage, "%s location %08x caused an access violation.\r\n", readwrite, Exception->ExceptionInformation[1]);
398
399 #ifdef  _DEBUG
400                 // The VisualC++ debugger doesn't actually tell you whether a read
401                 // or a write caused the access violation, nor does it tell what
402                 // address was being read or written. So I fixed that.
403                 OutputDebugString("Exception handler: ");
404                 OutputDebugString(DebugMessage);
405 #endif
406
407                 hprintf(LogFile, "%s", DebugMessage);
408         }
409
410         // Print out the register values in a Win95 error window compatible format.
411         hprintf(LogFile, "\r\n");
412         hprintf(LogFile, "Registers:\r\n");
413         hprintf(LogFile, "EAX=%08x CS=%04x EIP=%08x EFLGS=%08x\r\n",
414                                 Context->Eax, Context->SegCs, Context->Eip, Context->EFlags);
415         hprintf(LogFile, "EBX=%08x SS=%04x ESP=%08x EBP=%08x\r\n",
416                                 Context->Ebx, Context->SegSs, Context->Esp, Context->Ebp);
417         hprintf(LogFile, "ECX=%08x DS=%04x ESI=%08x FS=%04x\r\n",
418                                 Context->Ecx, Context->SegDs, Context->Esi, Context->SegFs);
419         hprintf(LogFile, "EDX=%08x ES=%04x EDI=%08x GS=%04x\r\n",
420                                 Context->Edx, Context->SegEs, Context->Edi, Context->SegGs);
421         hprintf(LogFile, "Bytes at CS:EIP:\r\n");
422
423         // Print out the bytes of code at the instruction pointer. Since the
424         // crash may have been caused by an instruction pointer that was bad,
425         // this code needs to be wrapped in an exception handler, in case there
426         // is no memory to read. If the dereferencing of code[] fails, the
427         // exception handler will print '??'.
428         unsigned char *code = (unsigned char*)Context->Eip;
429         for (int codebyte = 0; codebyte < NumCodeBytes; codebyte++) {
430                 __try {
431                         hprintf(LogFile, "%02x ", code[codebyte]);
432
433                 }
434                 __except(EXCEPTION_EXECUTE_HANDLER) {
435                         hprintf(LogFile, "?? ");
436                 }
437         }
438
439         // Time to print part or all of the stack to the error log. This allows
440         // us to figure out the call stack, parameters, local variables, etc.
441         hprintf(LogFile, "\r\n"
442                                          "Stack dump:\r\n");
443         __try {
444                 // Esp contains the bottom of the stack, or at least the bottom of
445                 // the currently used area.
446                 DWORD* pStack = (DWORD *)Context->Esp;
447                 DWORD* pStackTop;
448                 __asm
449                 {
450                         // Load the top (highest address) of the stack from the
451                         // thread information block. It will be found there in
452                         // Win9x and Windows NT.
453                         mov     eax, fs:[4]
454                         mov pStackTop, eax
455                 }
456                 if (pStackTop > pStack + MaxStackDump) {
457                         pStackTop = pStack + MaxStackDump;
458                 }
459
460                 int Count = 0;
461                 // Too many calls to WriteFile can take a long time, causing
462                 // confusing delays when programs crash. Therefore I implemented
463                 // simple buffering for the stack dumping code instead of calling
464                 // hprintf directly.
465                 char    buffer[1000] = "";
466                 const int safetyzone = 50;
467                 char*   nearend = buffer + sizeof(buffer) - safetyzone;
468                 char*   output = buffer;
469                 while (pStack + 1 <= pStackTop)         {
470                         if ((Count % StackColumns) == 0) {
471                                 output += wsprintf(output, "%08x: ", pStack);
472                         }
473
474                         char *Suffix = " ";
475                         if ((++Count % StackColumns) == 0 || pStack + 2 > pStackTop) {
476                                 Suffix = "\r\n";
477                         }
478
479                         output += wsprintf(output, "%08x%s", *pStack, Suffix);
480                         pStack++;
481                         // Check for when the buffer is almost full, and flush it to disk.
482                         if (output > nearend) {
483                                 hprintf(LogFile, "%s", buffer);
484                                 buffer[0] = 0;
485                                 output = buffer;
486                         }
487                 }
488                 // Print out any final characters from the cache.
489                 hprintf(LogFile, "%s", buffer);
490         }
491         __except(EXCEPTION_EXECUTE_HANDLER) {
492                 hprintf(LogFile, "Exception encountered during stack dump.\r\n");
493         }
494
495         RecordModuleList(LogFile);
496
497         CloseHandle(LogFile);
498         // Return the magic value which tells Win32 that this handler didn't
499         // actually handle the exception - so that things will proceed as per
500         // normal.
501         return EXCEPTION_CONTINUE_SEARCH;
502 }