windows-nt/Source/XPSP1/NT/base/mvdm/wow16/sherlock/drwatson.c
2020-09-26 16:20:57 +08:00

1576 lines
45 KiB
C

/* sherlock.c - Help deduce cause of Unrecoverable Application Errors
(C) Copyright 1991, Microsoft Corp
Written by Don Corbitt, based upon work of Cameron Stevens and others.
Features -
Help Script
Dialog box to change options
Option to trap Ctrl-Alt-SysRq to break endless loops
Disassembler should look up symbols for CALL instructions
Button could call up editor - file extension associations
Toggle Icon when message occurs
Write formal spec - (Program is done, so write the spec)
Disable operation in Real Mode
Dump stack bytes
If all is blown, dump message to text monitor
Data Symbols in disassembly
Bugs -
Doesn't buffer file output - this could be slow (but how many GP/hour?)
Need to watch for invalid memory
Need to handle Jump to bad address
What if there aren't any file handles left at fault time???
Need to handle no file handles available for .SYM file reader
Should open files (.sym, .log) with proper share flags
Could dump config.sys and autoexec.bat also
Can't handle fault in Sherlock - locks up machine - very ugly
Need to check InDOS flag
Some errors not detected
Jump/Call to invalid address
Load invalid selector
Run twice in Real Mode causes system hang
GP Continue doesn't update 32 bit registers for string moves
*/
#define DRWATSON_C
#define STRICT
#include <windows.h>
#include <string.h> /* strcpy() */
#include <stdarg.h> /* va_stuff() */
#include <io.h> /* dup() - why is this spread over 3 files ??? */
#include "toolhelp.h" /* all the good stuff */
#include "disasm.h" /* DisAsm86(), memXxxx vars */
#include "drwatson.h"
#include "str.h" /* Support for string resources */
#define STATIC /*static */
char far foo[1]; /* force far data seg, make single instance */
// Does this make sense considering we have code and strings to
// tell the user they're in error to run two copies of Dr. Watson?
/******************/
/***** Macros *****/
/******************/
#define version "1.00b"
/* This string is concatenated with other strings in various places */
/* so it can't be an array variable. It must stay a #define. */
/* These strings are not localized. */
#define szAppNameMacro "Dr. Watson"
#define szAppNameShortMacro "drwatson"
STATIC char szAppName[] = szAppNameMacro;
STATIC char szAppNameShort[] = szAppNameShortMacro;
STATIC char szAppNameShortLog[] = szAppNameShortMacro ".log";
static char szAppNameVers[] = szAppNameMacro " " version;
#define YOO_HOO (WM_USER+22) /* user activated Dr. Watson */
#define HEAP_BIG_FILE (WM_USER+23) /* log file is getting large */
#define JUST_THE_FACTS (WM_USER+24) /* tell me about your problem */
#define BIG_FILE 100000L
/* Don't like MSC-style FP macros, use my own */
#undef MK_FP
#undef FP_SEG
#undef FP_OFF
#define MK_FP(seg, off) (void far *)(((long)(seg) << 16) | (unsigned short)(off))
#define FP_SEG(fp) (unsigned)((long)(fp) >> 16)
#define FP_OFF(fp) (unsigned)(long)fp
/***************************/
/***** Data Structures *****/
/***************************/
LPSTR aszStrings[STRING_COUNT];
/* This points to the stack the GP fault handler should use */
char *newsp;
/* These structures are used by Watson.asm and Disasm.c - don't change */
/* Also, they can't be static. They contain the CPU register contents */
/* at the time the fault occurred. */
struct {
word ax, cx, dx, bx, sp, bp, si, di, ip, flags;
word es, cs, ss, ds, fs, gs, intNum;
} regs;
/* If we have a 32 bit CPU, the full 32 bit values will be stored here. */
/* The lower 16 bits will still be in the generic regs above. */
struct {
DWORD eax, ecx, edx, ebx, esp, ebp, esi, edi, eip, eflags;
} regs32;
/* Each of these flags disables a part of the error output report */
/* The error report itself indicates how each section is named. */
/* The word in () can be added to the [Dr. Watson] section of WIN.INI */
/* to disable that section of the report. */
/* clu Clues dialog box */
/* deb OutputDebugString trapping */
/* dis Simple disassembly */
/* err Error logging */
/* inf System info */
/* loc Local vars on stack dump */
/* mod Module dump */
/* par Parameter error logging */
/* reg Register dump */
/* sum 3 line summary */
/* seg not visible to users, but available */
/* sou But I _like_ the sound effects! */
/* sta Stack trace */
/* tas Task dump */
/* tim Time start/stop */
/* 32b 32 bit register dump */
STATIC char syms[] =
"clu deb dis err inf lin loc mod par reg sum seg sou sta tas tim 32b ";
#define cntFlag (sizeof(syms)/4)
/* This array is used to decode the flags in WIN.INI. I only check */
/* the first 3 chars of an entry. Each entry must be separated by a */
/* space from the previous one. */
unsigned long ddFlag;
int retflag; /* used in watson.asm */
struct {
char bit, name;
} flBit[] = {
11, 'O',
10, 'D',
9, 'I',
7, 'S',
6, 'Z',
4, 'A',
2, 'P',
0, 'C',
};
#define cntFlBit (sizeof(flBit)/sizeof(flBit[0]))
STATIC int disLen = 8; /* Number of instructions to disassemble */
STATIC int trapZero = 0; /* Should I trap divide by 0 faults */
STATIC int iFeelLucky = 1; /* Should we restart after GP fault? */
/* 1 = allow continue
2 = skip report
4 = continue in Kernel
8 = continue in User
16 = allow sound
*/
STATIC int imTrying; /* trying to continue operation */
STATIC struct {
FARPROC adr;
WORD code;
HTASK task;
DWORD parm;
} lastErr;
STATIC int disStack = 2; /* Disassemble 2 levels of stack trace */
int cpu32; /* True if cpu has 32 bit regs */
STATIC int fh = -1; /* Handle of open log file */
STATIC int level; /* if >0, in nested FileOpen() call */
STATIC int bugCnt, sound;
STATIC int pending; /* If a pending Clues dialog */
STATIC int whined; /* If already warned about a large file */
STATIC long pitch, deltaPitch = 250L << 16;
STATIC HINSTANCE hInst;
STATIC char logFile[80]; /* Default log file is "drwatson.log" */
/* and is stored in the windows dir */
STATIC struct { /* Help print out value of CPU flags */
WORD mask;
LPSTR name;
} wf[] = {
WF_80x87, (LPSTR) IDSTRCoprocessor, // IDSTRs are fixed up to pointers
WF_CPU086, (LPSTR) IDSTR8086, // by LoadStringResources
WF_CPU186, (LPSTR) IDSTR80186,
WF_CPU286, (LPSTR) IDSTR80286,
WF_CPU386, (LPSTR) IDSTR80386,
WF_CPU486, (LPSTR) IDSTR80486,
WF_ENHANCED, (LPSTR) IDSTREnhancedMode,
WF_PMODE, (LPSTR) IDSTRProtectMode,
WF_STANDARD, (LPSTR) IDSTRStandardMode,
WF_WINNT, (LPSTR) IDSTRWindowsNT,
};
#define wfCnt (sizeof(wf)/sizeof(wf[0]))
HWND hWnd; /* Handle to main window */
HANDLE hTask; /* current task (me) */
/***********************/
/***** Extern Defs *****/
/***********************/
/* Get base 32 bit linear address of a memory segment - calls DPMI */
extern DWORD SegBase(WORD segVal);
/* Get segment flags - 0 if error */
extern WORD SegRights(WORD segVal);
/* Get (segment length -1) */
extern DWORD SegLimit(WORD segVal);
/* Fills in regs32 structure with value from regs struct and current high */
/* word of registers - don't do any 32 bit ops before calling this func */
extern void GetRegs32(void);
/* Fills in non-standard time/date structure using DOS calls. The C */
/* run-time has a similar function (asctime()), but it pulls in over */
/* 6K of other functions. This is much smaller and faster, and */
/* doesn't depend on environment variables, etc. */
extern void GetTimeDate(void *tdstruc);
/* Called by ToolHelp as a notify hook */
extern BOOL far /*pascal*/ CallMe(WORD, DWORD);
char *LogParamErrorStr(WORD err, FARPROC lpfn, DWORD param);
extern int FindFile(void *ffstruct, char *name);
/* This routine is called by ToolHelp when a GP fault occurs. It */
/* switches stacks and calls Sherlock() to handle the fault. */
extern void CALLBACK GPFault(void);
/* Return name of nearest symbol in file, or 0 */
extern char *NearestSym(int segIndex, unsigned offset, char *exeName);
STATIC void cdecl Show(const LPSTR format, ...);
/************************************/
/***** Segment Helper Functions *****/
/************************************/
/************************************
Name: LPSTR SegFlags(WORD segVal)
Desc: Given a selector, SegFlags checks for validity and then returns
an ascii string indicating whether it is a code or data selector,
and read or writeable.
Bugs: Should check other flags (accessed), and call gates.
Returns pointer to static array, overwritten on each new call.
*************************************/
STATIC LPSTR SegFlags(WORD segVal) {
static char flag[10];
if (segVal == 0) return STR(NullPtr);
segVal = SegRights(segVal);
if (segVal == 0) return STR(Invalid);
segVal >>= 8;
if (!(0x80 & segVal)) return STR(NotPresent);
if (segVal & 8) {
lstrcpy(flag, STR(Code));
lstrcat(flag, segVal & 2 ? STR(ExR) : STR(ExO));
} else {
lstrcpy(flag, STR(Data));
lstrcat(flag, segVal&2 ? STR(RW) : STR(RO));
}
return flag;
} /* SegFlags */
/************************************
Name: char *SegInfo(WORD seg)
Desc: Given a selector, SegInfo returns an ascii string indicating the
linear base address, limit, and attribute flags of the selector.
Bugs: Returns pointer to static array, overwritten on each new call.
*************************************/
STATIC char *SegInfo(WORD seg) {
static char info[30];
if (noSeg) return "";
wsprintf(info, "%8lx:%04lx %-9s",
SegBase(seg), SegLimit(seg), (FP)SegFlags(seg));
return info;
} /* SegInfo */
/************************************
Name: WORD SegNum(WORD segVal)
Desc: Returns the index of this segment in the module table. Used to
translate between a physical segment number and the index as
seen in e.g. the map file.
Bugs: Don't know what ToolHelp returns for data or GlobalAlloc segments.
This is mainly useful for converting a code segment value.
Check for GT_DATA - will also be valid index.
*************************************/
STATIC WORD SegNum(HGLOBAL segVal) {
GLOBALENTRY ge;
ge.dwSize = sizeof(ge);
if (GlobalEntryHandle(&ge, segVal) && (ge.wType == GT_CODE)) {
return ge.wData; /* defined to be 'file segment index' */
}
return (WORD)-1;
} /* SegNum */
/************************************
Name: LPSTR ModuleName(WORD segVal)
Desc: Returns name of this code segment's module
Bugs:
*************************************/
STATIC LPSTR ModuleName(WORD segVal) {
static char name[12];
GLOBALENTRY ge;
MODULEENTRY me;
ge.dwSize = sizeof(ge);
me.dwSize = sizeof(me);
if (GlobalEntryHandle(&ge, (HGLOBAL)segVal) && (ge.wType == GT_CODE)) {
if (ModuleFindHandle(&me, ge.hOwner)) {
strcpy(name, me.szModule);
return name;
} /* else Show("ModuleFindHandle() failed\n"); */
} /* else Show("GlobalEntryHandle() failed\n"); */
return STR(Unknown);
} /* ModuleName */
/**********************************/
/***** Other Helper Functions *****/
/**********************************/
/************************************
Name: char *FaultType(void)
Desc: Returns ascii string indicating what kind of fault caused ToolHelp
to call our GPFault handler.
Bugs: May not handle Ctrl-Alt-SysR nicely (we shouldn't trap it)
*************************************/
/* static char *FaultType(void) {
switch (regs.intNum) {
case 0: return STR(DivideByZero);
case 6: return STR(InvalidOpcode);
case 13: return STR(GeneralProtection);
default: return STR(Unknown);
}
} /* FaultType */
/************************************
Name: char *DecodeFault(int op, word seg, dword offset, word size)
Desc: Pokes at memory address passed in, trying to determine fault cause
Segment wrap-around
Null selector
Write to read only data
Write to code segment
Read from execute only code segment
Exceed segment limit
Invalid selector
Bugs: Jump, string, call, and stack memory adr's aren't set by DisAsm
*************************************/
STATIC LPSTR DecodeFault(int op, word seg, dword offset, word size) {
int v;
dword lim;
switch (op) {
case memNOP:
break; /* since no mem access, no fault */
case memSegMem: /* load seg reg from memory */
seg = *(short far *)MK_FP(seg, offset);
/* fall through */
case memSegReg: /* load seg reg with value */
v = SegRights(seg); /* lets see if this is a selector */
if (!v) return STR(InvalidSelector);
break; /* See no evil... */
case memRead:
case memRMW:
case memWrite:
if (seg == 0) return STR(NullSelector);
v = SegRights(seg);
if (!v) return STR(InvalidSelector);
v >>= 8;
if (!(0x80 & v)) return STR(SegmentNotPresent);
lim = SegLimit(seg);
if (lim < (offset+size)) return STR(ExceedSegmentBounds);
if (v & 8) { /* code segment */
if ((op == memRMW) || (op == memWrite))
return /* Write to */ STR(CodeSegment);
else if (!(v&2)) return /* Read */ STR(ExecuteOnlySegment);
} else { /* data segment */
if (((op == memRMW) || (op == memWrite)) && !(v&2))
return /* Write to */ STR(ReadOnlySegment);
}
break;
default:
return 0; /* obviously unknown condition */
}
return 0;
} /* DecodeFault */
LPSTR SafeDisAsm86(void far *code, int *len) {
unsigned long limit = SegLimit(FP_SEG(code));
if ((unsigned long)(FP_OFF(code)+10) > limit) {
*len = 1;
return STR(SegNotPresentOrPastEnd);
}
return DisAsm86(code, (int *)len);
} /* SafeDisAsm86 */
/************************************
Name: LPSTR FaultCause(void)
Desc: Decodes the actual cause of the fault. This is trivial for Div0
and Invalid Opcode, but much trickier for GP Faults. I need to
try to detect at least the following:
Segment wrap-around
Null selector
Write to read only data
Write to code segment
Read from execute only code segment
Exceed segment limit
Invalid selector
Bugs:
*************************************/
STATIC LPSTR FaultCause(void) {
int foo;
LPSTR s, s1;
static char cause[54];
switch (regs.intNum) {
case 0: return STR(DivideByZero);
case 6: return STR(InvalidOpcode);
case 20: return STR(ErrorLog);
case 21: return STR(ParameterErrorLog);
case 13:
SafeDisAsm86(MK_FP(regs.cs, regs.ip), &foo); /* Set global memXxxx vars */
/* See if first memory access caused fault */
s = DecodeFault(memOp, memSeg, memLinear, memSize);
s1 = memName[memOp];
/* no, see if second memory access caused fault */
if (!s && memDouble) {
s = DecodeFault(memOp2, memSeg2, memLinear2, memSize2);
s1 = memName[memOp2];
}
if (s) {
wsprintf(cause, "%s (%s)", s, s1);
return cause;
}
}
return STR(Unknown);
} /* FaultCause */
/************************************
Name: LPSTR CurModuleName(hTask task)
Desc: Call ToolHelp to find name of faulting module
Bugs:
*************************************/
STATIC LPSTR CurModuleName(HTASK hTask) {
TASKENTRY te;
static char name[10];
te.dwSize = sizeof(te);
if (!TaskFindHandle(&te, hTask)) /* Thanks, ToolHelp */
return STR(Unknown);
strcpy(name, te.szModule);
return name;
} /* ModuleName */
/************************************
Name: LPSTR FileInfo(char *name)
Desc: Find file time, date, and size
Bugs:
*************************************/
STATIC LPSTR FileInfo(char *name) {
struct {
char resv[21];
char attr;
unsigned time;
unsigned date;
long len;
char name[13];
char resv1[10];
} f;
static char buf[30];
if (FindFile(&f, name)) return STR(FileNotFound);
wsprintf(buf, "%7ld %02d-%02d-%02d %2d:%02d",
f.len,
(f.date >> 5) & 15, f.date & 31, (f.date >> 9) + 80,
f.time >> 11, (f.time >> 5) & 63);
return buf;
} /* FileInfo */
/************************************
Name: char *CurFileName(void)
Desc: Call ToolHelp to find filename and path of faulting module
Bugs:
*************************************/
/* STATIC char *CurFileName(void) {
TASKENTRY te;
MODULEENTRY me;
static char name[80];
te.dwSize = sizeof(te);
me.dwSize = sizeof(me);
if (!TaskFindHandle(&te, GetCurrentTask()) ||
!ModuleFindName(&me, te.szModule))
return STR(Unknown);
strcpy(name, me.szExePath);
return name;
} /* FileName */
/************************************
Name: char *CurTime(void)
Desc: Generates string with current time and date. Similar to asctime(),
except it doesn't pull in another 6K of run-time library code :-)
Bugs: Magic structure passed to asm routine
*************************************/
STATIC char *CurTime(void) {
static char t[48];
struct { /* This magic struct is hard-coded to */
char week, resv; /* match the assembly language in */
short year; /* watson.asm GetTimeDate() */
char day, month; /* This means I recommend you don't */
char minute, hour; /* change the size or order of the */
char hund, second; /* fields! */
} td;
GetTimeDate(&td);
wsprintf(t, "%s %s %2d %02d:%02d:%02d %d",
aszStrings[IDSTRSun + td.week], aszStrings[IDSTRJan + td.month - 1],
td.day, td.hour, td.minute, td.second, td.year);
return t;
} /* CurTime */
/************************************
Name: LPSTR Tab2Spc(LPSTR temp)
Desc: Converts tabs found in string 'temp' into the proper number of
spaces. I need this since DisAsm86() returns a string with tabs
in it, and TextOut() didn't like them. This was easier than
getting TabbedTextOut() set up to work. Since I'm no longer dumping
to the screen, this routine may be superfluous.
Bugs:
*************************************/
STATIC LPSTR Tab2Spc(LPSTR temp) {
char newbuf[80];
LPSTR s1, s2;
s1 = temp;
s2 = newbuf;
while ((*s2 = *s1++) != 0) {
if (*s2++ == 9) {
s2[-1] = ' ';
while ((s2-(LPSTR)newbuf) & 7) *s2++ = ' ';
}
}
lstrcpy(temp, newbuf);
return temp;
} /* Tab2Spc */
/************************************
Name: void Show(const LPSTR format, ...)
Desc: Think of this as (minor) shortcut fprintf(). I originally had this
dumping info to a Windows window, and then changed it to write to
the file we want. All output goes through this func, so if you
want to change something, this is the place.
Bugs: Now writing to a file handle, opened in text mode so it does the
LF->CR/LF translation for me.
No buffering performed on writes, except for what DOS might do.
Blows up if stuff passed in expands to longer than 200 chars.
*************************************/
STATIC void cdecl Show(const LPSTR format, ...) {
char line[CCH_MAX_STRING_RESOURCE];
char *prev, *cur;
wvsprintf(line, format, (LPSTR)(&format + 1));
if (fh != -1) {
prev = cur = line;
while (*cur) { /* expand LF to CR/LF */
if (cur[0] == '\n' && /* at LF */
((prev == cur) || /* and first of line */
(cur[-1] != '\r'))) { /* or previous wasn't CR */
cur[0] = '\r'; /* append CR to text up to LF */
_lwrite(fh, prev, cur-prev+1);
cur[0] = '\n'; /* leave LF for next write */
prev = cur;
}
cur++;
}
if (prev != cur) /* write trailing part */
_lwrite(fh, prev, cur-prev);
}
} /* Show */
/************************************
Name: void MyFlush(void)
Desc: Any routine named MyXxxx() had better be a private hack, and this
one is. It just appends an extra CRLF to the output file, and makes
sure that the info written so far makes it to disk. This way, if
a later part of the program blows up, at least you will know this
much.
Bugs:
*************************************/
STATIC void MyFlush(void) {
int h;
Show("\n");
if (fh != -1) {
h = dup(fh);
if (h != -1) _lclose(h);
}
if (sound) {
StopSound();
SetVoiceSound(1, pitch, 20);
pitch += deltaPitch;
StartSound();
}
} /* MyFlush */
/************************************
Name: void DisAsmAround(char far *cp, int count)
Desc: The 'cp' parameter is a pointer to a code segment in memory. This
routine backs up a few instructions from the current point, and
dumps a disassembly showing the context of the selected instruction.
Bugs: Needs to check for segmentation problems, such as invalid selector.
*************************************/
STATIC void DisAsmAround(byte far *cp, int count) {
int len, back;
byte far *oldcp = cp;
byte far *cp1;
GLOBALENTRY ge;
MODULEENTRY me;
char *szSym = 0;
long limit;
unsigned segLim;
char symBuf[40];
ge.dwSize = sizeof(ge);
me.dwSize = sizeof(me);
if (GlobalEntryHandle(&ge, (HGLOBAL)FP_SEG(cp)) && (ge.wType == GT_CODE)) {
if (ModuleFindHandle(&me, ge.hOwner)) {
szSym = NearestSym(ge.wData, FP_OFF(cp), me.szExePath);
if (!szSym) { /* if we know module name, but no syms */
sprintf(symBuf, "%d:%04x", ge.wData, FP_OFF(cp));
szSym = symBuf;
}
}
}
cp -= count*2 + 10; /* back up */
if ((FP_OFF(cp) & 0xff00) == 0xff00) /* if wrapped around, trunc to 0 */
cp = MK_FP(FP_SEG(cp), 0);
cp1 = cp;
limit = SegLimit(FP_SEG(cp));
segLim = limit > 0xffffL ? 0xffff : (int)limit;
if (segLim == 0) {
Show(STR(CodeSegmentNPOrInvalid));
return;
}
back = 0;
while (cp < oldcp) { /* count how many instructions to point */
SafeDisAsm86(cp, &len);
cp += len;
back++;
}
cp = cp1;
back -= (count >> 1);
while (back>0) { /* step forward until (len/2) remain */
SafeDisAsm86(cp, &len); /* before desired instruction point */
cp += len;
back--;
}
while (count--) { /* display desired instructions */
if (cp == oldcp) {
if (szSym) Show("(%s:%s)\n", (FP)me.szModule, (FP)szSym);
else Show(STR(NoSymbolsFound));
}
Show("%04x:%04x %-22s %s\n",
FP_SEG(cp), FP_OFF(cp), /* address */
(FP)hexData, /* opcodes in hex */
(FP)/*Tab2Spc*/(SafeDisAsm86(cp, &len)));/* actual disassembly */
cp += len;
}
} /* DisAsmAround */
/************************************
Name: int MyOpen(void)
Desc: Tries to open logFile for append. If this fails, tries to
create it.
Bugs: Should set sharing flags?
*************************************/
STATIC int MyOpen(void) {
if (fh != -1) return fh; /* Already open */
fh = _lopen(logFile, OF_WRITE | OF_SHARE_DENY_WRITE);
if (fh == -1) {
fh = _lcreat(logFile, 0);
} else _llseek(fh, 0L, 2);
if (fh != -1) level++;
return fh != -1;
} /* MyOpen */
/************************************
Name: void MyClose(void)
Desc: close output file, clear handle to -1
Bugs: Should set sharing flags?
*************************************/
STATIC void MyClose(void) {
if (--level == 0) {
if (fh != -1) _lclose(fh);
fh = -1;
}
} /* MyClose */
void PutDate(LPSTR msg) {
MyOpen();
if (fh == -1) return;
Show("%s %s - %s\n", (FP)msg, (FP)szAppNameVers, (FP)CurTime());
MyClose();
} /* PutDate */
int far pascal SherlockDialog(HWND hDlg, WORD wMsg, WPARAM wParam, LPARAM lParam) {
char line[255];
int i, len, count;
HWND hItem;
lParam = lParam;
if (wMsg == WM_INITDIALOG) return 1;
if ((wMsg != WM_COMMAND) ||
(wParam != IDOK && wParam != IDCANCEL))
return 0;
if (wParam == IDOK) {
MyOpen();
if (fh != -1) {
hItem = GetDlgItem(hDlg, 102);
if (hItem) {
count = (int)SendMessage(hItem, EM_GETLINECOUNT, 0, 0L);
for (i=0; i<count; i++) {
*(int *)line = sizeof(line) - sizeof(int) -1;
len = (int)SendMessage(hItem, EM_GETLINE, i, (long)((void far *)line));
line[len] = 0;
Show("%d> %s\n", i+1, (FP)line);
}
}
MyClose();
}
}
EndDialog(hDlg, 0);
return 1;
} /* SherlockDialog */
extern int far pascal SysErrorBox(char far *text, char far *caption,
int b1, int b2, int b3);
#define SEB_OK 1 /* Button with "OK". */
#define SEB_CANCEL 2 /* Button with "Cancel" */
#define SEB_YES 3 /* Button with "&Yes" */
#define SEB_NO 4 /* Button with "&No" */
#define SEB_RETRY 5 /* Button with "&Retry" */
#define SEB_ABORT 6 /* Button with "&Abort" */
#define SEB_IGNORE 7 /* Button with "&Ignore" */
#define SEB_CLOSE 8 /* Button with "Close" */
#define SEB_DEFBUTTON 0x8000 /* Mask to make this button default */
#define SEB_BTN1 1 /* Button 1 was selected */
#define SEB_BTN2 2 /* Button 1 was selected */
#define SEB_BTN3 3 /* Button 1 was selected */
/************************************
Name: int PrepareToParty(LPSTR modName, LPSTR appName)
Desc: Checks whether we can continue the current app by skipping an
instruction. If so, it performs the side effects of the
instruction. This must be called after a call to DisAsm86() has
set the gpXxxx global vars.
Checks value of iFeelLucky, bit 0 must be set to continue a fault.
Bugs: Should do more checking, should check for within a device driver,
shouldn't require that DisAsm86() be called for the failing
instruction immediately before call.
*************************************/
int PrepareToParty(LPSTR modName, LPSTR appName) {
if (!(iFeelLucky&1)) return 0;
if (!gpSafe) return 0;
/* compare module to KERNEL */
if (!(iFeelLucky&4) && !lstrcmp(modName, "KERNEL")) return 0;
/* compare module to USER */
if (!(iFeelLucky&8) && !lstrcmp(modName, "USER")) return 0;
/* already asked, trying to continue, skip this fault */
if (imTrying>0) return 1;
if (3 != SysErrorBox(STR(GPText), appName, SEB_CLOSE|SEB_DEFBUTTON, 0, SEB_IGNORE))
return 0;
imTrying = 100;
return 1;
} /* PrepareToParty */
STATIC void DumpInfo(void) {
WORD w = (int)GetVersion();
DWORD lw = GetWinFlags();
SYSHEAPINFO si;
int i;
MEMMANINFO mm;
Show(STR(SystemInfoInfo));
Show(STR(WindowsVersion), w&0xff, w>>8);
if (GetSystemMetrics(SM_DEBUG)) Show(STR(DebugBuild));
else Show(STR(RetailBuild));
{
HANDLE hUser = GetModuleHandle("USER");
char szBuffer[80];
if (LoadString(hUser, 516, szBuffer, sizeof(szBuffer)))
Show(STR(WindowsBuild), (FP)szBuffer);
if (LoadString(hUser, 514, szBuffer, sizeof(szBuffer)))
Show(STR(Username), (FP)szBuffer);
if (LoadString(hUser, 515, szBuffer, sizeof(szBuffer)))
Show(STR(Organization), (FP)szBuffer);
}
Show(STR(SystemFreeSpace), GetFreeSpace(0));
if (SegLimit(regs.ss) > 0x10) {
int far *ip = MK_FP(regs.ss, 0);
Show(STR(StackBaseTopLowestSize),
ip[5], ip[7], ip[6], ip[7]-ip[5]);
}
si.dwSize = sizeof(si);
if (SystemHeapInfo(&si))
Show(STR(SystemResourcesUserGDI),
si.wUserFreePercent, si.hUserSegment,
si.wGDIFreePercent, si.hGDISegment);
mm.dwSize = sizeof(mm);
if (MemManInfo(&mm)) {
Show(STR(MemManInfo1),
mm.dwLargestFreeBlock, mm.dwMaxPagesAvailable, mm.dwMaxPagesLockable);
Show(STR(MemManInfo2),
mm.dwTotalLinearSpace, mm.dwTotalUnlockedPages, mm.dwFreePages);
Show(STR(MemManInfo3),
mm.dwTotalPages, mm.dwFreeLinearSpace, mm.dwSwapFilePages);
Show(STR(MemManInfo4), mm.wPageSize);
}
Show(STR(TasksExecuting), GetNumTasks());
Show(STR(WinFlags));
for (i=0; i<wfCnt; i++) if (lw & wf[i].mask)
Show(" %s\n", (FP)wf[i].name);
MyFlush();
} /* DumpInfo */
LPSTR GetProcName(FARPROC fn) {
GLOBALENTRY ge;
MODULEENTRY me;
LPSTR szSym = STR(UnknownAddress);
static char symBuf[80];
ge.dwSize = sizeof(ge);
me.dwSize = sizeof(me);
if (GlobalEntryHandle(&ge, (HGLOBAL)FP_SEG(fn)) && (ge.wType == GT_CODE)) {
if (ModuleFindHandle(&me, ge.hOwner)) {
szSym = NearestSym(ge.wData, FP_OFF(fn), me.szExePath);
if (!szSym) { /* if we know module name, but no syms */
sprintf(symBuf, "%s %d:%04x", (FP)me.szModule, ge.wData, FP_OFF(fn));
} else sprintf(symBuf, "%s %s", (FP)me.szModule, szSym);
szSym = symBuf;
}
}
return szSym;
} /* GetProcName */
STATIC void DumpStack(int disCnt, int parmCnt, int cnt, int first) {
STACKTRACEENTRY ste;
MODULEENTRY me;
int frame = 0;
unsigned oldsp = regs.sp+16;
ste.dwSize = sizeof(ste);
me.dwSize = sizeof(me);
Show(STR(StackDumpStack));
if (StackTraceCSIPFirst(&ste, regs.ss, regs.cs, regs.ip, regs.bp)) do {
if (frame >= first--) {
me.szModule[0] = 0;
ModuleFindHandle(&me, ste.hModule);
Show(STR(StackFrameInfo),
frame++,
(FP)GetProcName((FARPROC)MK_FP(ste.wCS, ste.wIP)),
ste.wSS, ste.wBP);
if (!noLocal && (parmCnt-- > 0)) {
if (oldsp & 15) {
int i;
Show("ss:%04x ", oldsp & ~15);
for (i=0; i < (int)(oldsp & 15); i++) Show(" ");
}
while (oldsp < ste.wBP) {
if (!(oldsp & 15)) Show("\nss:%04x ", oldsp);
Show("%02x ", *(byte far *)MK_FP(regs.ss, oldsp++));
}
Show("\n");
}
if (frame <= disStack && (disCnt-- >0)) {
Show("\n");
DisAsmAround(MK_FP(ste.wCS, ste.wIP), 8);
}
MyFlush();
} /* if after first to show */
} while (StackTraceNext(&ste) && (cnt-- > 0));
} /* DumpStack */
int BeginReport(LPSTR time) {
int i;
MyOpen();
if (fh == -1) { /* maybe we're out of handles */
_lclose(4); /* trash one at random */
MyOpen(); /* and try again */
}
if (fh == -1) return 0;
for (i=0; i<4; i++) Show("*******************");
Show(STR(FailureReport), (FP)szAppNameVers, (FP)time);
MyFlush();
if (!noSound) {
sound = OpenSound();
pitch = 1000L << 16;
} else sound = 0;
return 1;
} /* BeginReport */
void EndReport(void) {
if (fh != -1) {
if (!whined && _llseek(fh, 0L, 2) > BIG_FILE) {
PostMessage(hWnd, HEAP_BIG_FILE, 0, 0);
whined = 1;
}
MyClose();
}
if (sound) {
StopSound();
CloseSound();
sound = 0;
}
} /* EndReport */
void ShowParamError(int sync) {
if (GetCurrentTask() == lastErr.task)
Show("$param$, %s %s\n",
sync ? (FP)"" : (FP)STR(LastParamErrorWas),
(FP)LogParamErrorStr(lastErr.code, lastErr.adr, lastErr.parm));
lastErr.task = 0;
} /* ShowParamError */
/************************************
Name: void Sherlock(void)
Desc: Handles GP faults in applications by dumping as much system
information as I can think of to a log file.
This is the big routine.
Bugs:
*************************************/
enum {s_prog, s_fault, s_name, s_instr, s_time, s_last};
int Sherlock(void) {
int i, faultlen, party;
LPSTR s[s_last];
if ((!trapZero || regs.intNum != 0) &&
regs.intNum != 6 &&
regs.intNum != 13)
return 0;
if (imTrying>0) {
s[s_prog] = CurModuleName(GetCurrentTask());
SafeDisAsm86(MK_FP(regs.cs, regs.ip), &faultlen);
party = PrepareToParty(ModuleName(regs.cs), s[s_prog]);
imTrying--;
if (party) goto SkipReport;
}
if (++bugCnt > 20) return 0;
if (!BeginReport(s[s_time] = CurTime()))
return 0;
s[s_prog] = CurModuleName(GetCurrentTask());
s[s_fault] = FaultCause();
s[s_name] = GetProcName((FARPROC)MK_FP(regs.cs, regs.ip));
Show(STR(HadAFaultAt),
(FP)s[s_prog],
(FP)s[s_fault],
(FP)s[s_name]);
if (!noSummary) Show("$tag$%s$%s$%s$",
(FP)s[s_prog],
(FP)s[s_fault],
(FP)s[s_name]);
s[s_instr] = Tab2Spc(SafeDisAsm86(MK_FP(regs.cs, regs.ip), &faultlen));
Show("%s$%s\n", (FP)s[s_instr], (FP)s[s_time]);
ShowParamError(0);
MyFlush();
party = PrepareToParty(ModuleName(regs.cs), s[s_prog]);
if ((bugCnt > 3) || ((party>0) && (iFeelLucky & 2))) {
goto SkipReport;
}
if (!noReg) {
Show(STR(CPURegistersRegs));
Show("ax=%04x bx=%04x cx=%04x dx=%04x si=%04x di=%04x\n",
regs.ax, regs.bx, regs.cx, regs.dx, regs.si, regs.di);
Show("ip=%04x sp=%04x bp=%04x ", regs.ip, regs.sp+16, regs.bp);
for (i=0; i<cntFlBit; i++)
Show("%c%c ", flBit[i].name, regs.flags & (1 << flBit[i].bit) ? '+' : '-');
Show("\n");
Show("cs = %04x %s\n", regs.cs, (FP)SegInfo(regs.cs));
Show("ss = %04x %s\n", regs.ss, (FP)SegInfo(regs.ss));
Show("ds = %04x %s\n", regs.ds, (FP)SegInfo(regs.ds));
Show("es = %04x %s\n", regs.es, (FP)SegInfo(regs.es));
MyFlush();
}
if (cpu32 && !noReg32) {
Show(STR(CPU32bitRegisters32bit));
Show("eax = %08lx ebx = %08lx ecx = %08lx edx = %08lx\n",
regs32.eax, regs32.ebx, regs32.ecx, regs32.edx);
Show("esi = %08lx edi = %08lx ebp = %08lx esp = %08lx\n",
regs32.esi, regs32.edi, regs32.ebp, regs32.esp);
Show("fs = %04x %s\n", regs.fs, (FP)SegInfo(regs.fs));
Show("gs = %04x %s\n", regs.gs, (FP)SegInfo(regs.gs));
Show("eflag = %08lx\n", regs32.eflags);
MyFlush();
}
if (!noDisasm) {
Show(STR(InstructionDisasm));
DisAsmAround(MK_FP(regs.cs, regs.ip), disLen);
MyFlush();
}
if (!noInfo)
DumpInfo();
if (!noStack)
DumpStack(disStack, 0x7fff, 0x7fff, 0);
if (!noTasks) {
TASKENTRY te;
MODULEENTRY me;
te.dwSize = sizeof(te);
me.dwSize = sizeof(me);
Show(STR(SystemTasksTasks));
if (TaskFirst(&te)) do {
ModuleFindName(&me, te.szModule);
Show(STR(TaskHandleFlagsInfo),
(FP)te.szModule, te.hTask, me.wcUsage,
(FP)FileInfo(me.szExePath));
Show(STR(Filename), (FP)me.szExePath); /* */
} while (TaskNext(&te));
MyFlush();
}
if (!noModules) {
MODULEENTRY me;
Show(STR(SystemModulesModules));
me.dwSize = sizeof(me);
if (ModuleFirst(&me)) do {
Show(STR(ModuleHandleFlagsInfo),
(FP)me.szModule, me.hModule, me.wcUsage,
(FP)FileInfo(me.szExePath));
Show(STR(File), (FP)me.szExePath); /* */
} while (ModuleNext(&me));
MyFlush();
}
SkipReport:
if (party>0) {
int len;
word far * stack = MK_FP(regs.ss, regs.sp);
Show(STR(ContinuingExecution), (FP)CurTime());
MyFlush();
/* fix up regs */
if (gpRegs & segDS) regs.ds = 0;
if (gpRegs & segES) regs.es = 0;
if (gpRegs & segFS) regs.fs = 0;
if (gpRegs & segGS) regs.gs = 0;
regs.ip += faultlen; /* set at top of func - don't reuse */
if ((int)gpStack < 0) {
for (i=0; i<8; i++) stack[i+gpStack] = stack[i];
} else if (gpStack) {
for (i=7; i>=0; i--) stack[i+gpStack] = stack[i];
}
regs.sp += gpStack << 1;
if (gpRegs & strCX) {
len = regs.cx * memSize;
regs.cx = 0;
} else len = memSize;
if (gpRegs & strSI) { /* doesn't handle 32 bit regs */
regs.si += len;
if (regs.si < (word)len) /* if overflow, set to big value */
regs.si = 0xfff0; /* so global vars in heap don't get */
} /* trashed when we continue */
if (gpRegs & strDI) {
regs.di += len;
if (regs.di < (word)len) regs.di = 0xfff0;
}
}
EndReport();
if (!noClues && /* if we want clues */
!pending && /* no clues waited for */
(!party || !(iFeelLucky & 2))) { /* and we aren't quiet partiers */
PostMessage(hWnd, JUST_THE_FACTS, (WPARAM)GetCurrentTask(), party);
pending++;
}
if (party < 0) TerminateApp(GetCurrentTask(), NO_UAE_BOX);
return party;
} /* Sherlock */
void far *bogus;
int CallMeToo(WORD wID, DWORD dwData) {
NFYLOGPARAMERROR far *lpep;
LPSTR s[s_last];
if (wID == NFY_OUTSTR) {
if (noDebStr)
return FALSE;
MyOpen();
if (fh == -1) return FALSE;
Show(STR(DebugString), dwData);
MyClose();
return TRUE;
}
if (wID == NFY_LOGERROR && noErr)
return FALSE;
lpep = (void far *)dwData; /* Get the data for next log entry */
lastErr.adr = lpep->lpfnErrorAddr;
lastErr.code = lpep->wErrCode;
lastErr.parm = (DWORD)(lpep->lpBadParam);
lastErr.task = GetCurrentTask();
if ((lastErr.code & 0x3000) == 0x1000)
lastErr.parm = (WORD)lastErr.parm;
else if ((lastErr.code & 0x3000) == 0)
lastErr.parm = (BYTE)lastErr.parm;
if (wID == NFY_LOGPARAMERROR && noParam) {
return FALSE;
}
if (bugCnt++ > 60)
return FALSE;
if (!BeginReport(s[s_time] = CurTime())) /* Can't open file */
return FALSE;
switch (wID) {
case NFY_LOGERROR:
#if 0
lep = (void far *)dwData;
cs = ip = 0;
parm = 0;
code = lep->wErrCode;
s[s_fault] = STR(ApplicationError);
#endif
break;
case NFY_LOGPARAMERROR:
s[s_fault] = STR(InvalidParameter);
break;
default:
return FALSE;
}
s[s_prog] = CurModuleName(lastErr.task);
s[s_name] = GetProcName(lastErr.adr);
s[s_instr] = STR(NA); /* not interesting */
Show(STR(HadAFaultAt2),
(FP)s[s_prog],
(FP)s[s_fault], lastErr.code,
(FP)s[s_name]);
if (!noSummary) Show("$tag$%s$%s (%x)$%s$",
(FP)s[s_prog],
(FP)s[s_fault], lastErr.code,
(FP)s[s_name]);
Show(STR(ParamIs), lastErr.parm, (FP)s[s_time]);
ShowParamError(1);
MyFlush();
if (!noInfo && bugCnt < 2)
DumpInfo();
if (!noStack)
DumpStack(0, 0, 0x7fff, 4);
EndReport();
return TRUE;
} /* CallMe */
/* Parse SkipInfo= and ShowInfo= lines into flags array */
void ParseInfo(char *s, int val) {
int i;
strlwr(s);
while (*s) {
for (i=0; i<cntFlag; i++) if (0 == strncmp(s, syms+(i<<2), 3)) {
if (val) SetFlag(i);
else ClrFlag(i);
break;
}
while (*s && *s++ != ' ')
if (s[-1] == ',') break;
while (*s && *s == ' ') s++;
}
} /* ParseInfo */
/************************************
Name: BOOL LoadStringResources(void)
Desc: Load all string resources into GlobalAlloc'd buffer and
initialize aszStrings array with pointers to each string.
Also fixes up string IDs in wf (winflags) array to pointers.
Note that we don't free the memory allocated, we count on
kernel to clean up for us on termination.
Bugs:
*************************************/
BOOL LoadStringResources(void)
{
int n;
HANDLE h;
LPSTR lp;
WORD cbTotal;
WORD cbUsed;
WORD cbStrLen;
//
// Allocate too much memory for strings (maximum possible) at first,
// reallocate to the real size when we're done loading strings.
//
#if (STRING_COUNT * CCH_MAX_STRING_RESOURCE > 65536 - 64)
#error Need to use HUGE pointer for lp and DWORD for cb in LoadStringResources
#endif
cbTotal = STRING_COUNT;
cbTotal *= CCH_MAX_STRING_RESOURCE;
h = GlobalAlloc(GMEM_FIXED, cbTotal);
if ( ! h ) {
return FALSE;
}
lp = GlobalLock(h);
cbUsed = 0;
for ( n = 0; n < STRING_COUNT; n++ ) {
cbStrLen = LoadString(hInst, n, lp, CCH_MAX_STRING_RESOURCE);
if ( ! cbStrLen ) {
return FALSE;
}
aszStrings[n] = lp;
lp += cbStrLen + 1; // LoadString return doesn't count null terminator
cbUsed += cbStrLen + 1;
}
GlobalReAlloc(h, cbUsed, 0);
//
// Fix up winflags array elements from string resource IDs to pointers
//
for ( n = 0; n < wfCnt; n++ ) {
wf[n].name = aszStrings[ (int)(DWORD)wf[n].name ];
}
return TRUE;
}
/************************************
Name: void DumpIni(void)
Desc: Write profile strings to log file
Bugs:
*************************************/
#if 0
void DumpIni() {
int i;
char buf[4];
buf[3] = 0;
MyOpen();
Show("Re-read win.ini\nshowinfo="); // move to resource file if ever used
for (i=0; i<cntFlag; i++) {
if (!flag(i)) {
memcpy(buf, syms+(i<<2), 3);
Show("%s ", (FP)buf);
}
}
Show("\nskipinfo=");
for (i=0; i<cntFlag; i++) {
if (flag(i)) {
memcpy(buf, syms+(i<<2), 3);
Show("%s ", (FP)buf);
}
}
Show("\n");
MyClose();
} /* DumpIni */
#endif
/************************************
Name: int ReadWinIni(void)
Desc: Read profile strings from WIN.INI.
Return 0 if failure.
Bugs:
*************************************/
STATIC int ReadWinIni(void) {
char line[80];
int len;
/* how many instructions should I disassemble by default? */
disLen = GetProfileInt(szAppName, "dislen", 8);
/* should I trap divide by 0 faults? */
trapZero = GetProfileInt(szAppName, "trapzero", 0);
/* should we allow restarting apps? */
iFeelLucky = GetProfileInt(szAppName, "GPContinue", 1);
/* if (!(iFeelLucky & 16)) noSound = 1; */
/* how many stack frames should be disassembled? */
disStack = GetProfileInt(szAppName, "DisStack", 2);
/* where should I write the log file to? */
GetProfileString(szAppName, "logfile", szAppNameShortLog, logFile, sizeof(logFile));
len = strlen(logFile);
if ((len == 0) || // logfile=
(logFile[len-1] == '\\') || // directory only (boo, hiss)
(logFile[len-1] == '/') ||
(logFile[len-1] == ':')) { // drive only
if (len && (logFile[len-1] == ':')) { // drive only, put in root
strcat(logFile, "\\");
}
strcat(logFile, szAppNameShortLog); // append a file name
}
if (!(strchr(logFile, '\\') // if no path specified, put in WinDir
|| strchr(logFile, ':')
|| strchr(logFile, '/'))) {
char logname[80];
int n;
GetWindowsDirectory(logname, sizeof(logname));
n = strlen(logname);
if (n && logname[n-1] != '\\')
strcat(logname, "\\");
strcat(logname, logFile);
strcpy(logFile, logname);
}
/* Set default flag values - see DrWatson.h for default values */
ddFlag = DefFlag;
/* do I really have to print out all this information? */
if (GetProfileString(szAppName, "skipinfo", "", line, sizeof(line)))
ParseInfo(line, 1);
if (GetProfileString(szAppName, "showinfo", "", line, sizeof(line)))
ParseInfo(line, 0);
#if 0
DumpIni();
#endif
return 1;
} /* ReadWinIni */
/************************************
Name: int InitSherlock(void)
Desc: Initialize Sherlock processing. Install GP fault handler.
Return 0 if failure.
Bugs:
*************************************/
STATIC int InitSherlock(void) {
/* do I have 32 bit registers? */
cpu32 = (GetWinFlags() & (WF_CPU386|WF_CPU486)) != 0;
/* see what WIN.INI [drwatson] has to say */
if (!ReadWinIni()) return 0;
NotifyRegister(hTask, (LPFNNOTIFYCALLBACK)CallMe, NF_NORMAL);
/* Now get ToolHelp to do the dirty work */
return InterruptRegister(hTask, GPFault);
} /* InitSherlock */
/************************************
Name: void Moriarty
Desc: Destroy any evidence Sherlock was loaded.
Bugs: Am I freeing all resources I used?
*************************************/
int init;
STATIC void Moriarty(void) {
if (init) {
if (!noTime) PutDate(STR(Stop));
InterruptUnRegister(hTask);
NotifyUnRegister(hTask);
init = 0;
}
} /* Moriary */
/************************************
Name: WINAPI SherlockWndProc(hWnd, wMessage, wParam, lParam)
Desc: Handle sherlock icon, close processing
Bugs: Should pull up dialog boxes for About and GetInfo
*************************************/
LRESULT CALLBACK SherlockWndProc (HWND hWnd, UINT iMessage,
WPARAM wParam, LPARAM lParam) {
char msg[200];
/* int (FAR PASCAL *dfp)(HWND, WORD, WORD, DWORD); */
FARPROC dfp;
switch (iMessage) {
case WM_ENDSESSION:
if (wParam) Moriarty();
break;
case WM_DESTROY: /* Quit Sherlock */
PostQuitMessage (0);
break;
case WM_QUERYOPEN: /* never open a window??? */
PostMessage(hWnd, YOO_HOO, 0, 1);
ReadWinIni();
break;
case WM_WININICHANGE: /* Re-read WIN.INI parameters */
ReadWinIni();
break;
case YOO_HOO:
if (bugCnt) {
wsprintf(msg, STR(Faulty), bugCnt, (FP)logFile);
MessageBox(hWnd, msg, szAppNameVers,
MB_ICONINFORMATION | MB_OK | MB_TASKMODAL);
} else {
MessageBox(hWnd, STR(NoFault), szAppNameVers,
MB_ICONINFORMATION | MB_OK | MB_TASKMODAL);
}
break;
case HEAP_BIG_FILE:
wsprintf(msg, STR(LogFileGettingLarge),
(FP)logFile);
MessageBox(hWnd, msg, szAppNameVers,
MB_ICONEXCLAMATION | MB_OK | MB_TASKMODAL);
break;
case JUST_THE_FACTS:
dfp = MakeProcInstance((FARPROC)SherlockDialog, hInst);
DialogBox(hInst, "SherDiag", hWnd, (DLGPROC)dfp);
FreeProcInstance(dfp);
pending = 0; /* finished all old business */
break;
default:
return DefWindowProc (hWnd,iMessage,wParam,lParam);
}
return 0L;
}
/************************************
Name: WinMain(hInst, hPrevInst, cmdLine, cmdShow)
Desc: Init Sherlock - this is where it all begins
Bugs:
*************************************/
int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow) {
MSG msg; /* Message returned from message loop */
WNDCLASS wndclass; /* Sherlock window class */
char watsonStack[4096];
nCmdShow = nCmdShow;
lpszCmdLine = lpszCmdLine;
newsp = watsonStack + sizeof(watsonStack);
hInst = hInstance;
hTask = GetCurrentTask();
/* Check if Sherlock is already running */
if (!hPrevInstance) {
if (!LoadStringResources()) {
MessageBox(NULL, "Dr. Watson could not load all string resources",
szAppNameVers, MB_ICONEXCLAMATION | MB_OK | MB_SYSTEMMODAL);
return 1;
}
/* Define a new window class */
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
wndclass.lpfnWndProc = SherlockWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon (hInstance, szAppNameShortMacro "Icon");
wndclass.hCursor = LoadCursor (NULL,IDC_ARROW);
wndclass.hbrBackground = GetStockObject (WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass (&wndclass)) {
MessageBox(NULL, STR(ClassMsg), szAppNameVers, MB_ICONEXCLAMATION | MB_OK |
MB_SYSTEMMODAL);
return 1;
}
} else {
/* Instance is already running, issue warning and terminate */
MessageBox (NULL, STR(ErrMsg), szAppNameVers, MB_ICONEXCLAMATION | MB_OK |
MB_SYSTEMMODAL);
return 1;
}
/* Create window and display in iconic form */
hWnd = CreateWindow (szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
ShowWindow (hWnd, SW_SHOWMINNOACTIVE);
UpdateWindow (hWnd);
if (!InitSherlock()) {
MessageBox (/*NULL*/hWnd, STR(Vers), szAppNameVers, MB_ICONEXCLAMATION | MB_OK |
MB_SYSTEMMODAL);
DestroyWindow(hWnd);
return 1;
}
if (!noTime) PutDate(STR(Start));
init = 1;
while (GetMessage (&msg, NULL, 0, 0)) {/* Enter message loop */
TranslateMessage (&msg);
DispatchMessage (&msg);
imTrying = 0;
}
Moriarty(); /* Remove Sherlock GP Handler from GP Handler chain */
return msg.wParam;
} /* WinMain */