Source

doom / src / win32 / win_dbg.c

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
// Emacs style mode select   -*- C++ -*- 
//-----------------------------------------------------------------------------
//
// $Id: win_dbg.c 538 2009-09-23 23:24:07Z smite-meister $
//
// Copyright (C) 1998-2000 by DooM Legacy Team.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
//
// $Log: win_dbg.c,v $
// Revision 1.8  2001/01/25 22:15:45  bpereira
// added heretic support
//
// Revision 1.7  2000/09/28 20:57:22  bpereira
// no message
//
// Revision 1.6  2000/09/01 19:34:38  bpereira
// no message
//
// Revision 1.5  2000/08/03 17:57:42  bpereira
// no message
//
// Revision 1.4  2000/04/24 20:24:39  bpereira
// no message
//
// Revision 1.3  2000/04/16 18:38:07  bpereira
// no message
//
// Revision 1.2  2000/02/27 00:42:12  hurdler
// fix CR+LF problem
//
// Revision 1.1.1.1  2000/02/22 20:32:33  hurdler
// Initial import into CVS (v1.29 pr3)
//
//
// DESCRIPTION:
//      this source file contains the exception handler for recording error
//      information after crashes.
//      Sources from GameDeveloper magazine article, January 1998, by Bruce Dawson.
//
//-----------------------------------------------------------------------------

#include "../doomdef.h" //just for VERSION ..
#include "win_dbg.h"
#include "win_main.h"

#include "../m_argv.h" //print the parameter in the log

#define NumCodeBytes    16          // Number of code bytes to record.
#define MaxStackDump    2048    // Maximum number of DWORDS in stack dumps.
#define StackColumns    8               // Number of columns in stack dump.

#define ONEK                    1024
#define SIXTYFOURK              (64*ONEK)
#define ONEM                    (ONEK*ONEK)
#define ONEG                    (ONEK*ONEK*ONEK)


// --------------------------------------------------------------------------
// return a description for an ExceptionCode
// --------------------------------------------------------------------------
static char* GetExceptionDescription (DWORD ExceptionCode)
{
    int i;

    struct ExceptionNames
    {
        DWORD   ExceptionCode;
        char*   ExceptionName;
    };
    
    struct ExceptionNames ExceptionMap[] =
    {
        {EXCEPTION_ACCESS_VIOLATION, "an Access Violation"},
        {EXCEPTION_ARRAY_BOUNDS_EXCEEDED, "a Array Bounds Exceeded"},
        {EXCEPTION_BREAKPOINT, "a Breakpoint"},
        {EXCEPTION_DATATYPE_MISALIGNMENT, "a Datatype Misalignment"},
        {EXCEPTION_FLT_DENORMAL_OPERAND, "a Float Denormal Operand"},
        {EXCEPTION_FLT_DIVIDE_BY_ZERO, "a Float Divide By Zero"},
        {EXCEPTION_FLT_INEXACT_RESULT, "a Float Inexact Result"},
        {EXCEPTION_FLT_INVALID_OPERATION, "a Float Invalid Operation"},
        {EXCEPTION_FLT_OVERFLOW, "a Float Overflow"},
        {EXCEPTION_FLT_STACK_CHECK, "a Float Stack Check"},
        {EXCEPTION_FLT_UNDERFLOW, "a Float Underflow"},
        {EXCEPTION_ILLEGAL_INSTRUCTION, "an Illegal Instruction"},
        {EXCEPTION_IN_PAGE_ERROR, "an In Page Error"},
        {EXCEPTION_INT_DIVIDE_BY_ZERO, "an Integer Divide By Zero"},
        {EXCEPTION_INT_OVERFLOW, "an Integer Overflow"},
        {EXCEPTION_INVALID_DISPOSITION, "an Invalid Disposition"},
        {EXCEPTION_NONCONTINUABLE_EXCEPTION, "Noncontinuable Exception"},
        {EXCEPTION_PRIV_INSTRUCTION, "a Privileged Instruction"},
        {EXCEPTION_SINGLE_STEP, "a Single Step"},
        {EXCEPTION_STACK_OVERFLOW, "a Stack Overflow"},
        {0x40010005, "a Control-C"},
        {0x40010008, "a Control-Break"},
        {0xc0000006, "an In Page Error"},
        {0xc0000017, "a No Memory"},
        {0xc000001d, "an Illegal Instruction"},
        {0xc0000025, "a Noncontinuable Exception"},
        {0xc0000142, "a DLL Initialization Failed"},
        {0xe06d7363, "a Microsoft C++ Exception"},
    };
    
    for (i = 0; i < sizeof(ExceptionMap) / sizeof(ExceptionMap[0]); i++)
        if (ExceptionCode == ExceptionMap[i].ExceptionCode)
            return ExceptionMap[i].ExceptionName;
        
    return "Unknown exception type";
}


// --------------------------------------------------------------------------
// Directly output a formatted string to the errorlog file, using win32 funcs
// --------------------------------------------------------------------------
void FPrintf (HANDLE fileHandle, LPCTSTR lpFmt, ...)
{
    char    str[1999];
    va_list arglist;
    DWORD   bytesWritten;

    va_start (arglist, lpFmt);
    vsprintf (str, lpFmt, arglist);
    va_end   (arglist);

    WriteFile (fileHandle, str, strlen(str), &bytesWritten, NULL);
}


// --------------------------------------------------------------------------
// 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.
        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;
}


static char* GetFilePart(char *source)
{
    char *result = strrchr(source, '\\');
    if (result)
        result++;
    else
        result = source;
    return result;
}


// --------------------------------------------------------------------------
// 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];
    IMAGE_DOS_HEADER *DosHeader;
    IMAGE_NT_HEADERS *NTHeader;
    HANDLE ModuleFile;
    char TimeBuffer[100] = "";
    DWORD FileSize = 0;
    __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.
            DosHeader = (IMAGE_DOS_HEADER*)ModuleHandle;
            if (IMAGE_DOS_SIGNATURE != DosHeader->e_magic)
                return;
            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.
            ModuleFile = CreateFile(ModName, GENERIC_READ,
                FILE_SHARE_READ, 0, OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL, 0);
            if (ModuleFile != INVALID_HANDLE_VALUE)
            {
                FILETIME        LastWriteTime;
                FileSize = GetFileSize(ModuleFile, 0);
                if (GetFileTime(ModuleFile, 0, 0, &LastWriteTime))
                {
                    wsprintf(TimeBuffer, " - file date is ");
                    PrintTime(TimeBuffer + lstrlen(TimeBuffer), LastWriteTime);
                }
                CloseHandle(ModuleFile);
            }
            FPrintf (LogFile, "%s, loaded at 0x%08x - %d bytes - %08x%s\r\n",
                ModName, ModuleHandle, FileSize,
                NTHeader->FileHeader.TimeDateStamp, TimeBuffer);
        }
    }
    // Handle any exceptions by continuing from this point.
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
    }
}

// --------------------------------------------------------------------------
// 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)
{
    SYSTEM_INFO     SystemInfo;
    size_t PageSize;
    size_t NumPages;
    size_t pageNum = 0;
    void *LastAllocationBase = 0;
    
    FPrintf (LogFile, "\r\n"
        "\tModule list: names, addresses, sizes, time stamps "
        "and file times:\r\n");
    
    // Set NumPages to the number of pages in the 4GByte address space,
    // while being careful to avoid overflowing ints.
    GetSystemInfo(&SystemInfo);
    PageSize = SystemInfo.dwPageSize;
    NumPages = 4 * (unsigned int)(ONEG / PageSize);
    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
            pageNum += SIXTYFOURK / PageSize;
        // If VirtualQuery fails we advance by 64K because that is the
        // granularity of address space doled out by VirtualAlloc().
    }
}


// --------------------------------------------------------------------------
// Record information about the user's system, such as processor type, amount
// of memory, etc.
// --------------------------------------------------------------------------
static void RecordSystemInformation(HANDLE fileHandle)
{
    FILETIME        CurrentTime;
    char        TimeBuffer[100];
    char        ModuleName[MAX_PATH];
    char        UserName[200];
    DWORD       UserNameSize;
    SYSTEM_INFO     SystemInfo;
    MEMORYSTATUS MemInfo;
    
    GetSystemTimeAsFileTime (&CurrentTime);
    PrintTime (TimeBuffer, CurrentTime);
    FPrintf(fileHandle, "Error occurred at %s.\r\n", TimeBuffer);
    
    if (GetModuleFileName (NULL, ModuleName, sizeof(ModuleName)) <= 0)
        lstrcpy (ModuleName, "Unknown");
    UserNameSize = sizeof(UserName);
    if (!GetUserName (UserName, &UserNameSize))
        lstrcpy (UserName, "Unknown");
    FPrintf(fileHandle, "%s, run by %s.\r\n", ModuleName, UserName);
    
    GetSystemInfo (&SystemInfo);
    FPrintf (fileHandle, "%d processor(s), type %d %d.%d.\r\n"
        "Program Memory from 0x%p to 0x%p\r\n",
        SystemInfo.dwNumberOfProcessors, 
        SystemInfo.dwProcessorType,
        SystemInfo.wProcessorLevel,
        SystemInfo.wProcessorRevision,
        SystemInfo.lpMinimumApplicationAddress,
        SystemInfo.lpMaximumApplicationAddress);
    
    MemInfo.dwLength = sizeof(MemInfo);
    GlobalMemoryStatus(&MemInfo);
    // Print out the amount of physical memory, rounded up.
    FPrintf(fileHandle, "%d MBytes physical memory.\r\n", (MemInfo.dwTotalPhys +
        ONEM - 1) / ONEM);
}


// --------------------------------------------------------------------------
// What we do here is trivial : open a file, write out the register information
// from the PEXCEPTION_POINTERS structure, then return EXCEPTION_CONTINUE_SEARCH
// whose magic value tells Win32 to proceed with its normal error handling
// mechanism. This is important : an error dialog will popup if possible and
// the debugger will hopefully coexist peacefully with the structured exception
// handler.
// --------------------------------------------------------------------------
int __cdecl RecordExceptionInfo (PEXCEPTION_POINTERS data, const char *Message, LPSTR lpCmdLine)
{
    PEXCEPTION_RECORD   Exception = data->ExceptionRecord;
    PCONTEXT            Context = data->ContextRecord;
    char                ModuleName[MAX_PATH];
    char                FileName[MAX_PATH] = "Unknown";
    char*               FilePart, *lastperiod;
    char                CrashModulePathName[MAX_PATH];
    char*               CrashModuleFileName = "Unknown";
    MEMORY_BASIC_INFORMATION    MemInfo;
    static int          BeenHere=false;
    HANDLE              fileHandle;
    unsigned char *     code;
    int                 codebyte,i;
    
    if (BeenHere)       // Going recursive! That must mean this routine crashed!
        return EXCEPTION_CONTINUE_SEARCH;
    BeenHere = true;
    
    // Create a filename to record the error information to.
    // Store it in the executable directory.
    if (GetModuleFileName(NULL, ModuleName, sizeof(ModuleName)) <= 0)
        ModuleName[0] = 0;
    FilePart = GetFilePart(ModuleName);
    
    // Extract the file name portion and remove it's file extension. We'll
    // use that name shortly.
    lstrcpy (FileName, FilePart);
    lastperiod = strrchr (FileName, '.');
    if (lastperiod)
        lastperiod[0] = 0;
    // Replace the executable filename with our error log file name.
    lstrcpy (FilePart, "errorlog.txt");
    fileHandle = CreateFile (ModuleName, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, NULL);
    if (fileHandle == INVALID_HANDLE_VALUE)
    {
        OutputDebugString ("Error creating exception report");
        return EXCEPTION_CONTINUE_SEARCH;
    }
    
    // Append to the error log.
    SetFilePointer (fileHandle, 0, 0, FILE_END);
    
    // Print out some blank lines to separate this error log from any previous ones.
    FPrintf (fileHandle, "Email this file to Doom legacy support so we can fix the bugs\r\n");
    FPrintf (fileHandle, "\r\n\r\n\r\n\r\n");
    FPrintf (fileHandle, "%s -ERROR LOG-\r\n\r\n", VERSION_BANNER);
    FPrintf (fileHandle, "Command Line parameters: ");
    for (i = 1;i<myargc;i++)
        FPrintf (fileHandle, "%s ", myargv[i] );

    FPrintf (fileHandle, "\r\n");
    // 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.
    FPrintf (fileHandle, "%s caused an %s in module %s at %04x:%08x.\r\n",
        FileName, GetExceptionDescription(Exception->ExceptionCode),
        CrashModuleFileName, Context->SegCs, Context->Eip);
    FPrintf (fileHandle, "Exception handler called in %s.\r\n", Message);
    
    RecordSystemInformation (fileHandle);
    
    // 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
        FPrintf(fileHandle, "%s", DebugMessage);
    }
    
    FPrintf(fileHandle, "\r\n");
    
    // Print out the register values in a Win95 error window compatible format.
    if ((Context->ContextFlags & CONTEXT_FULL) == CONTEXT_FULL)
    {
        FPrintf (fileHandle, "Registers:\r\n");
        FPrintf (fileHandle, "EAX=%.8lx CS=%.4x EIP=%.8lx EFLGS=%.8lx\r\n",
            Context->Eax,Context->SegCs,Context->Eip,Context->EFlags);
        FPrintf (fileHandle, "EBX=%.8lx SS=%.4x ESP=%.8lx EBP=%.8lx\r\n",
            Context->Ebx,Context->SegSs,Context->Esp,Context->Ebp);
        FPrintf (fileHandle, "ECX=%.8lx DS=%.4x ESI=%.8lx FS=%.4x\r\n",
            Context->Ecx,Context->SegDs,Context->Esi,Context->SegFs);
        FPrintf (fileHandle, "EDX=%.8lx ES=%.4x EDI=%.8lx GS=%.4x\r\n",
            Context->Edx,Context->SegEs,Context->Edi,Context->SegGs);
    }
    
    FPrintf (fileHandle, "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 '??'.
    code = (unsigned char*)Context->Eip;
    for (codebyte = 0; codebyte < NumCodeBytes; codebyte++)
    {
        __try
        {
            FPrintf (fileHandle, "%02x ", code[codebyte]);
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            FPrintf (fileHandle, "?? ");
        }
    }
    
    // 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.
    FPrintf (fileHandle, "\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;
        int Count = 0;
        char    buffer[1000] = "";
        const int safetyzone = 50;
        char*   nearend = buffer + sizeof(buffer) - safetyzone;
        char*   output = buffer;
        char *Suffix;
        __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
        // FPrintf directly.
        while (pStack + 1 <= pStackTop)
        {
            if ((Count % StackColumns) == 0)
                output += wsprintf(output, "%08x: ", pStack);
            if ((++Count % StackColumns) == 0 || pStack + 2 > pStackTop)
                Suffix = "\r\n";
            else
                Suffix = " ";
            output += wsprintf(output, "%08x%s", *pStack, Suffix);
            pStack++;
            // Check for when the buffer is almost full, and flush it to disk.
            if (output > nearend)
            {
                FPrintf (fileHandle, "%s", buffer);
                buffer[0] = 0;
                output = buffer;
            }
        }
        // Print out any final characters from the cache.
        FPrintf (fileHandle, "%s", buffer);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        FPrintf(fileHandle, "Exception encountered during stack dump.\r\n");
    }
    
    RecordModuleList (fileHandle);
    
    CloseHandle (fileHandle);
    
    // Return the magic value which tells Win32 that this handler didn't
    // actually handle the exception - so that things will proceed as per
    // normal.
    //BP: should put message for end user to send this file to fix any bug
    return EXCEPTION_CONTINUE_SEARCH;
}


    /*
    //
    //FPrintf ("e-mail this file to legacy@newdoom.com , so that we can fix the problem.\r\n\r\n");
    
    FPrintf ("Exception handler called in %s.\r\n", Message);
    
    GetSystemTime (&systemTime);
    FPrintf ("Error occured at %02d/%02d/%04d %02d:%02d:%02d.\r\n",
             systemTime.wMonth, systemTime.wDay, systemTime.wYear,
             systemTime.wHour, systemTime.wMinute, systemTime.wSecond);
    

    FPrintf ("%s\r\n", filename);
    FPrintf ("Cmd-line: %s\r\n", lpCmdLine);

    // Nested exceptions can occur, get info for each one

    nER = 1;
    while (ER)
    {
        if (nER++>1)
            FPrintf ("Exception Record %d.\r\n", nER);

        FPrintf ("application caused an %s", GetExceptionCodeStr(Exception->ExceptionCode));
        
        if (Context->ContextFlags & CONTEXT_CONTROL)
            FPrintf (" at %.4x:%.8x.\r\n", Context->SegCs, Context->Eip);
        
        // in case of..
        if (Context->Eip != (unsigned long)Exception->ExceptionAddress)
            FPrintf ("Exception Address = %.8x\r\n", Exception->ExceptionAddress);

        if (Exception->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
        {
            FPrintf ("\r\n%s location 0x%x caused an access violation.\r\n", 
                    (Exception->ExceptionInformation[0] ? "Write to" : "Read from"), 
                     Exception->ExceptionInformation[1]);
        }

        ER = Exception->ExceptionRecord;
    }

    
    if (Context->ContextFlags & CONTEXT_DEBUG_REGISTERS)
    {
        FPrintf ("\r\nDebug Registers:\r\n");
        FPrintf ("Dr0=%.8x  Dr1=%.8x  Dr2=%.8x\r\n"
                 "Dr3=%.8x  Dr6=%.8x  Dr7=%.8x\r\n",
                 Context->Dr0, Context->Dr1, Context->Dr2,
                 Context->Dr3, Context->Dr6, Context->Dr7);
    }

    if (Context->ContextFlags & CONTEXT_FLOATING_POINT)
    {
        FPrintf ("\r\nFloating Save Area:\r\n");
        FPrintf ("ControlWord  =%.8x  TagWord      =%.8x  ErrorSelector=%.8x  DataSelector =%.8x\r\n"
                 "StatusWord   =%.8x  ErrorOffset  =%.8x  DataOffset   =%.8x  Cr0NpxState  =%.8x\r\n",
                 Context->FloatSave.ControlWord, Context->FloatSave.TagWord, Context->FloatSave.ErrorSelector, Context->FloatSave.DataSelector,
                 Context->FloatSave.StatusWord, Context->FloatSave.ErrorOffset, Context->FloatSave.DataOffset, Context->FloatSave.Cr0NpxState
                 );

        //BYTE    RegisterArea[SIZE_OF_80387_REGISTERS];
    }


    // in case of...
    if ((Context->ContextFlags & CONTEXT_FULL) != CONTEXT_FULL)
    {
        if (!(Context->ContextFlags & CONTEXT_SEGMENTS))
            FPrintf ("Note! GS,FS,ES,DS are unspecified\r\n");
        if (!(Context->ContextFlags & CONTEXT_INTEGER))
            FPrintf ("Note! EDI,ESI,EBX,EDX,ECX,EAX are unspecified\r\n");
        if (!(Context->ContextFlags & CONTEXT_CONTROL))
            FPrintf ("Note! EBP,CS:EIP,EFlags,SS:ESP are unspecified\r\n");
    }

    if (hwdInstance!=NULL)
    {
        FPrintf ("\r\nHWDDriver loaded at %.8x\r\n", hwdInstance);
        FPrintf ("DLL function pointers:\r\n");
        for (loadfunc = hwdFuncTable, i=0; loadfunc->fnName!=NULL; loadfunc++,i++)
        {
            FPrintf ("hwdFuncTable[%d]: %s loaded at %.8x\r\n", i, loadfunc->fnName,
                                                    (unsigned long) *((void**)loadfunc->fnPointer) );
        }

    }

    FPrintf ("\r\nBytes at CS:EIP:\r\n");
    ucptr = (unsigned char*)Context->Eip;
    for (i=0; i<16; i++)
        FPrintf ("%.2x ", *ucptr++);
    
    FPrintf ("\r\n\r\nStack dump:\r\n");
    ulptr = (unsigned long*)Context->Esp;
    for (i=0; i<16; i++)
        FPrintf ("%.8x ", *ulptr++);

    //FPrintf ("Bytes at CS:EIP:\r\n");
    //FPrintf ("%.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x ");
             
    for (i=0; i<16; i++)
    {
        FPrintf ("%x
    }
*/