Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Exception Handlers and Windows Applications


SP 94: Exception Handlers and Windows Applications

Joe is a systems programmer at a major hardware vendor. He is a graduate of Georgetown University and currently lives and works in South Florida. He can be contacted at 72370,1265.


Every Windows user has at one time or another faced the dreaded UAE (Unrecoverable Application Error) or Application Execution Error. This is certainly frustrating for the user and can result in lost time and perhaps effort because the faulting application is removed from Windows. It can be troublesome for the application developer, too, because the user may not be able to effectively communicate the problem to the developer. Scenarios leading up to such errors can often be difficult, if not impossible, to reproduce.

While there are tools (such as Dr. Watson) that extract information about an exception, there are no tools for debugging a currently trapping application. Nor is there a general reference for writing exception handlers under Windows. Other than a few ToolHelp functions, there is little available to help you debug traps in Windows applications. Consequently, I've written TrapMan, the Windows Trap Manager--a debugging tool for analyzing exceptions in Windows applications.

TrapMan runs in any currently available protected-mode version of Windows. I've tested TrapMan extensively in Windows 3.0 and 3.1 (Standard and Enhanced modes), Win-OS/2 2.0 (Standard-mode Windows 3.0-compatible support under OS/2 2.0), and Win-OS/2 2.1 (Standard- and Enhanced-mode Windows 3.1-compatible support found in OS/2 2.1). TrapMan will not run in real mode--it is a protected-mode-only application. Additionally, if you wish to debug a currently faulting application caught in one of TrapMan's handlers, you must be running a debugger capable of processing unowned INT 3hs in code. I prefer Nu-Mega's Soft-Ice for Windows for debugging DOS-based versions of Windows and the OS/2 kernel debugger for debugging OS/2-based versions (I use both almost on a daily basis). Both debuggers can handle an INT 3h instruction in code that they did not place there.

Before discussing exception handlers, I'll review a number of concepts central to their understanding, namely, the System VM in Windows, DPMI (DOS Protected Mode Interface), protected-mode selectors, and interrupts and exceptions in 286 or greater (286/386/i486, and Pentium) processors. The complete source code and binaries for TrapMan (including DeadMan, a sample application program) are provided electronically; see "Availability," page 3.

The System VM

System VM technically refers to the first VDM (or virtual-DOS machine) started in Windows 3.0 or 3.1 in Enhanced mode. A VM (or virtual machine) is an emulated (virtual) 8086 processor available as a special mode on 386 (and higher) processors for use in Windows and Windows applications. Here, I'll use the term "System VM" somewhat loosely, referring to some address space where all Windows applications reside, which may or may not be equivalent to a virtual machine. (For example, Windows 3.0 Standard mode does not use virtual-8086 mode.) Why is this System VM so important? First, separate page tables are kept for each VM. If all Windows applications were not run in the same VDM, then they would be inaccessible to each other. Under OS/2 2.x, you can run multiple copies of Windows. Each copy is run in a separate VM and cannot access or interfere with any other.

There is another feature of System VM that makes programs such as TrapMan possible. Protected-mode Intel processors such as the 386 can access memory through one of two tables. These tables are the GDT (Global Descriptor Table) and the LDT (Local Descriptor Table), and they are used by operating systems to limit memory access among applications. Simply stated, a protected-mode program has access to memory addressable by the GDT and by one LDT. No other memory is accessible.

In OS/2, separate OS/2 programs are assigned separate LDTs, so one program cannot accidentally or intentionally modify the memory of another. The information in an individual program's LDT simply does not include information about the memory of any other process unless it is explicitly shared by the owning process.

As a second option, Intel specifications permit tasks to share a single LDT, and this is how Windows operates under both OS/2 and DOS. Because of this, TrapMan (and any other Windows program) can access memory in any Windows program or within the Windows kernel itself. This foregoes protection from interapplication corruption, but gives Windows applications great flexibility in how they interact with each other and with Windows itself.

DPMI

The DOS Protected Mode Interface (DPMI) is a set of entry points that permit protected-mode applications ("DPMI clients") to perform various tasks involving selector allocation, interrupt hooking, and the like, without affecting the integrity of the overall operating system.

Such tasks are generally considered privileged in multiprogram systems such as Windows, and applications are not permitted to implement these tasks themselves. However, they can request that a more privileged program (in this case the "DPMI host") make these changes for them.

I'll use a small subset of the available DPMI calls: GetVersion(), GetProcessorExceptionHandler(), SetProcessorExceptionHandler(), AllocateLDTDescriptors(), FreeLDTDescriptor(),GetSegmentBaseAddress(), SetSegmentBaseAddress(), and SetSelectorLimit(). These are only a few of the 50 or so DPMI calls available under the 0.90 specification. Microsoft officially supports only a few DPMI functions (see the Microsoft SDK 3.1 Programmer's Reference, Chapter 20) and the Microsoft Windows Guide to Programming warns "Do not use DPMI services for hooking interrupts or faults." However, all DPMI functions used in TrapMan are available in all DPMI implementations tested. Those more familiar with DPMI may notice that I do not (and should not) call the DPMI functions for placing the processor in protected mode. Windows does this before TrapMan (or any other application) is loaded.

Protected-Mode Selectors

In real mode, a segment register's value can be directly mapped to a physical address. A CS:IP of 197:0 refers directly to address 1970 (197h << 4). In protected mode, however, there is a level of indirection. The same CS:IP cannot be directly mapped to a physical address. A segment register such as CS is loaded with a segment-selector value in protected mode, and this value must be translated to get a physical address. (Paging on 386-family processors differs. With paging enabled, the value calculated is actually a linear address that must be converted to a physical address. For the purposes of this article, linear address=physical address.)

A selector is read as 197 hex = 0000 0001 1001 0111 binary, where 0000 0001 1001 0 (190) is the selector index (all numbers are in hex unless otherwise noted), bit 2 is the table indicator (a 1 value indicates that this is an LDT selector), and bits 1 and 0 give the requested privilege level (11 is ring 3, the least privileged). Inside the LDT entry for 190 is a base address (for example, 0040 0000). It is this base address that must be added to the offset to get the resulting physical address. For debugging traps in protected mode, a basic understanding of selectors is essential. However, much of the difficult work of selector translation is done automatically by the processor.

Protected-Mode Interrupts and Exceptions

Interrupts in protected mode are similar to their real-mode counterparts. However, instead of the interrupt-vector table (IVT) in low memory, interrupts are processed based on a protected-mode interrupt-descriptor table (IDT). For this reason, protected-mode interrupts cannot be watched by changing the interrupt vector table; instead, DOS or DPMI calls must be used to get or set a protected-mode handler.

There are three classes of exceptions: FAULTS, TRAPS, and ABORTS. Basically, a FAULT happens before any changes are made in the system. An example is the general-protection fault, or "GPFault," as it is known to Windows users. Such a fault might be the pseudocode fragment in Example 1 (which is not valid Intel assembler), which attempts to get the address of the current INT 3h handler in the real-mode interrupt-vector table (4 bytes, starting at physical address 0Ch) by placing 0 in a segment register. This procedure is perfectly valid in real mode; however, in protected mode a selector of 0 is not valid, and the actual use of this invalid selector in the third instruction will cause a fault before the third instruction is executed. CS:IP will still point to the faulting instruction. Faults are restartable. It is the restartability of faults that permits TrapMan (and Windows) to terminate faulting applications.

A TRAP, on the other hand, is caught after any changes are made to the system. An example of a trap is the special 1-byte INT 3h (0CCh) instruction used by TrapMan to break to a debugger. The INT 3h handler is called after the INT 3h is executed by the processor, CS:IP now points to the instruction after the INT 3h.

Another type of exception is an ABORT. Since these are generally caused by things such as hardware errors, I'll not discuss them here.

TrapMan was designed for debugging FAULTS. Some common faults found on 80286/80386/80486 Intel processors are listed in Table 1.

TRAPMAN.C

TrapMan is written almost entirely in C (with the exception of some inline assembler) using Microsoft C 6.0a. For reasons I'll discuss later, C is not really the best choice for writing exception handlers. Because it is comfortable for most programmers, however, I used C here to show the basic ideas behind exception handlers and debugging with them.

The file trapman.c (Listing One) is fairly straightforward code. The first thing unusual about TrapMan's source is in the global data area. To set and reset the handlers for various exceptions as the user changes exception handlers, you must always query the current handler for a particular exception (such as trap D, which would be stored in the _Prev13 code pointer). For this version of TrapMan, you can install up to four handlers independently (Traps 0, 6, 12, and 13, also known as DivideByZero, InvalidOpCode, StackFault, and GPFault, respectively). Additionally, you keep flags marking whether or not the Previous handler code is current (that is, whether or not we are currently watching a particular exception). See Figure 1.

Execution of TrapMan begins in the same manner as all Windows applications in WinMain(). To avoid parsing TrapMan arguments, TrapMan accesses the C run-time globals __argc and __argv; see Listing Two, page 71.) If any arguments are passed in on TrapMan's command line (argc >1), then argv[1] is taken as a program name to be debugged and passed to WinExec(). This name should be a fully qualified path to the application, if it is not on the path or in one of the directories searched by WinExec().

Additionally, TrapMan will not permit multiple instances of itself to be run. While this is not important in the C version, the versions given in future modules have handlers that contain extensive amounts of self-modifying code. You can't have multiple instances of an application modifying the same code--it just doesn't work very well! For the same reason, all TrapMan code is preload nondiscardable (although only the handlers and certain support functions actually need to be nondiscardable).

The instance initialization processing (InitInstance) does some important bookkeeping work for TrapMan. First, it gets handles to the main window's Trap and Option menus so that we can modify them later as the user selects and deselects various menu items. It also sets up the default TrapMan configuration. While this configuration is not used with the C handlers described in this article, this section is left in both for completeness and to give an idea of what the user interface of future versions will look like.

All possible exit points to the application must be covered in the window procedure for the main window (MainWndProc) because in all cases you must reset any set trap handlers to their previous values before exiting the application.

For our purposes, I'm assuming that all previous handlers belong to Windows or DPMI, so I won't check them for validity. If private exception handlers become more common, you'll need to verify that the current handlers are your own (that is, they have not been replaced by another app) before resetting them. You'll also need to verify that the code is valid. (To do this, you'll need to keep track of the signature for the previous handler to make sure that it has not been replaced by another application's code.)

The PutInEditControl() routine creates a 10K buffer that it uses to manipulate the contents of the edit control. The routine is sufficient for explanatory purposes, although handlers in your applications may not use an edit control. I felt it essential that users be able to update the TrapMan window's text as required (with a description of events leading up to the trap, for example), either before or after the trap occurred. An edit control seemed the most straightforward manner to do this, but flicker is a problem. No matter where the text cursor is in the buffer, new text from TrapMan is always placed at the bottom of the buffer. This is done so that users can manipulate the buffer while maintaining synchronous messaging from TrapMan or the operating system.

The SaveBuffer() routine simply writes the buffer to the given filename, and it will warn the user before replacing an existing file on disk. The HandleTraps() and HandleOptions() routines process WM_COMMAND messages for the Trap and Options pulldowns, respectively.

TrapMan uses a number of DPMI calls. DPMI uses registers to pass arguments, and all the functions we'll need will use the INT 31h interface. Note that DPMI functions consistently return with the carry flag set to mark failure (as does DOS). All of these functions assume that AX is to be used to return 16-bit values, and DX:AX to return 32-bit values. This is consistent with the Windows API and with most DOS C compilers, and is done so that DPMI calls can be conveniently made from C code. This module contains wrappers for the following functions:

  • DPMIGetVersion returns the version number of the DPMI host. Windows 3.0/3.1 and OS/2 2.0 return 005a (90 decimal); OS/2 2.1 returns 005f (95 decimal), due to some additional features.
  • DPMIGetProcessorExceptionHandlr returns the current exception handler for the exception number passed in as its argument, or 0 for failure.
  • DPMISetProcessorExceptionHandlr returns 0 if okay. It takes two arguments: the exception number and a far pointer to the routine that will handle it.
  • DPMIAllocateLDTDescriptors returns a base selector or 0=error. It takes one argument: the number of descriptors to allocate (usually 1).
  • DPMIFreeLDTDescriptor returns the selector freed if successful, otherwise returns 0. It takes one argument: a selector allocated via DPMIAllocateLDTDescriptors.
  • DPMIGetSegmentBaseAddress returns the base address of the given selector.
  • DPMISetSegmentBaseAddress returns AX=0 if failure. It takes three arguments: the selector whose base is to be modified, the hiword of the new base, and the loword of the new base.
The HANDLER.C file contains the handlers for exceptions that TrapMan watches. Note:

  • Exception handlers are far procedures (they must far return--RETF--back to DPMI).
  • Exception handlers take no parameters (although DPMI does push a 16-bit exception frame onto the stack before calling).
  • Exception handlers must be preloaded and nondiscardable.
  • When called, an exception handler has only cs:ip valid and pointing to the handler's code. All other registers belong either to the faulting application or to DPMI. The handler must change them, so many exception handlers push the client registers first. TrapMan's assembly-language handlers will do the same.
At the INT 3h in our GPFault handler, for example, the DPMI fault-exception frame begins at SS:SP+8. One GPFault had an exception frame that looked something like Figure 2. At ErrorFrame+0, you find the return address to DPMI (3b:0204); at ErrorFrame+4, the error code (a 0 selector); and at ErrorFrame+6, the address of the faulting instruction (1c7f:45d).

Upon disassembling the faulting instruction address, you find a STOSB. Since we know that a stosb instruction takes the value in AL and puts it in ES:DI, we know that a bad ES value can cause the trap. We see that ES is 0--this is the cause of the trap. Finally, at ErrorFrame+ah you find the flags, and at ErrorFrame+ch, a far pointer to the application's stack (1c77:15e8 in this case).

Using TrapMan to Debug a Trap or Fault

Both Windows and Win-OS/2 exception handlers are 16-bit handlers. On entry to a handler, all registers of the faulting application are preserved except CS, IP, SS, and SP. These registers are available in the exception stack frame. The stack frame begins at SS:SP and is laid out as in Table 2.

The far ptr at SS:SP+0Ch is that of the application's stack at fault time. It may be used to trace back a C calling stack or to examine the parameters of the faulting procedure (if a suitable stack frame has been set up). This is done by dumping data using the SS of the faulting app with the current bp or the value of SP of faulting app. The far ptr at SS:SP+6h is the instruction of the app whose attempted execution has generated the fault.

The error code is very similar to a selector in protected mode. The high 13 bits are the selector index (bits 3--15), and bit 2 is the table index. However, instead of an RPL (requested privilege level), bits 0 and 1 have the following meaning: Bit 0 (EXT) is set if the fault was called by an event external to the program; bit 1 (IDT) is set if the selector index refers to a gate descriptor in the IDT.

Closing Notes

The DPMI offsets described here are valid only at entry to the exception handler, as further modification of the stack will change SP. TrapMan's exception handlers are written so that the DPMI stack and registers are valid on the fault INT 3h (if Break On Fault option is checked).

You must be running a protected-mode debugger if you wish to stop on INT 3h during faults. Otherwise, a debugger is not necessary to use TrapMan. Currently, TrapMan does not add handlers for INT 3h, nor are there plans to do so.

Certain portions of the Windows and Win-OS/2 debug kernels do not use OutputDebugString() to write to the debugger. Their output will not be trapped even if OutputDebugString() is hooked. The functionality to add this hook (K328 or _DebugOutput, see Undocumented Windows, p.205) will be included in a future version of TrapMan. Executing random sections of memory frequently causes Trap D or Trap 7 faults.

Example 1: Pseudocode fragment that leads to a GPFault.

mov  ds, 0
mov  bx, 0Ch
mov  OffInt3, word ptr ds:bx     ; GPFault in protected mode!
                                ; Trying to access selector of 0!
mov  SegInt3, word ptr ds:bx+2

Table 1: Some common faults found on 80286, 80386, and 80486 Intel microprocessors.

    Fault                              Description

    Interrupt 0: Divide-by-zero        Trap 0
    Interrupt 6: Invalid opcode        Trap 6
    Interrupt 8: Double
    Interrupt 12: Stack                Trap C or Trap 12
    Interrupt 13: General protection   Trap D or Trap 13

Figure 1: TrapMan in action. Only the default handlers (Traps 6, 12, and 13) are set.

This instance's DS is 12AF
The DPMI Version is 5A
Trapping apps will be terminated
Enabling breaking to debugger on traps
Enabling beeping on traps
Prev Trap 13 handler is 1175FA2
Trap 13 (general protection fault) handler installed
Prev Trap 12 handler is 1175F93
Trap 12 (stack fault) handler installed
Prev Trap 6 handler is 1175FB1
Trap 6 (invalid opcode) handler installed
Trap 13!
E:\DEADMAN\DEADMAN.EXE
DPMI app regs:
   CS:IP = 1307:056C
   SS:SP = 12FF:15AE
   with selector 7470 (flags=0212)
App regs:
   AX=15AC BX=15EC CX=0000 DX=1800
   IP=**** SP=**** BP=15AE SI=15EC DI=1800
   CS=**** SS=**** DS=12FF ES=12FF
Dump of 12FF:15AE:
00___02___04___06__+__08___0A___0C___0E====0123456789ABCDEF
7473  6E69  2167  2020     4144  474E  5245  2121       sting!  DANGER!!
2021  5453  4341  204B     564F  5245  5257  5449       ! STACK OVERWRIT
Trap 6!
E:\DEADMAN\DEADMAN.EXE
DPMI app regs:
   CS:IP = 130F:04F4
   SS:SP = 131F:1596
   with selector 6E58 (flags=0287) App regs:
   AX=159A BX=04F2 CX=131F DX=130F
   IP=**** SP=**** BP=159C SI=15D8 DI=1800
   CS=**** SS=**** DS=131F ES=131F
Dump of 131F:1596:
00___02___04___06__+__08___0A___0C___0E====0123456789ABCDEF
15D8  1800  131F  15AF     0278  130F  15D8  1800       ########x#######
0001  0201  130F  131F     15C9  27BB  04A7  0000       ###########'####

Figure 2: Typical GPFault exception frame.

87:fd0   0204   003b   0000   45d   1c7f   0287   15e8   1c77
Offset   0      2      4      6     8      ah     ch     eh

Table 2: Exception stack frame.

SS of faulting app   0Eh
SP of faulting app   0Ch
Flags                0Ah
CS of faulting app   08h
IP of faulting app   06h
Error Code           04h
Return CS            02h
Return IP            00h

Listing One

/****************************************************************************
    FILE: trapman.c -- a trap manager for Windows 3.x and Win-OS/2
       Copyright (c) 1994, Joseph Hlavaty This product is CAREWARE. Please
       refer to the licensing agreement LICENSE.AGR on the diskette.
    PURPOSE: source file for trapman.exe, contains WinMain, window procedure
             and menu processing routines
****************************************************************************/
#define NOSOUND
#define NOCOMM
#define NODRIVERS
#define NOMINMAX
#define NOLOGERROR
#define NOPROFILER
#define NOLFILEIO
#define NOOPENFILE
#define NOATOM
#define NOLANGUAGE
#define NODBCS
#define NOKEYBOARDINFO
#define NOGDICAPMASKS
#define NOCOLOR
#define NODRAWTEXT
#define NOTEXTMETRIC
#define NOSCALABLEFONT
#define NOMETAFILE
#define NOSYSMETRICS
#define NOSYSTEMPARAMSINFO
#include "windows.h"
#include "trapman.h"
#include "DPMI.h"
#include <string.h>
#include <stdio.h>
#define SYSCOMMAND_MASK   0xFFF0
// this is our global data area
HANDLE hwndTrapMenu ;        // for checking and unchecking our trap options
void (_far *Prev13) () = NULL;
void (_far *Prev12) () = NULL;
void (_far *Prev6) () = NULL;
void (_far *Prev0) () = NULL;
int  WeGot13 = 0 ;
int  WeGot12 = 0 ;
int  WeGot6  = 0 ;
int  WeGot0  = 0 ;
WORD wDPMIVersion = 0xFFFF ;
char *TrapMsgs[15] = {
          "Trap 0 (divide by zero fault) handler installed",     // 0
          "       **** undefined msg ***     ",                  // 1
          "       **** undefined msg ***     ",                  // 2
          "       **** undefined msg ***     ",                  // 3
          "       **** undefined msg ***     ",                  // 4
          "       **** undefined msg ***     ",                  // 5
          "Trap 6 (invalid opcode) handler installed",           // 6
          "       **** undefined msg ***     ",                  // 7
          "       **** undefined msg ***     ",                  // 8
          "       **** undefined msg ***     ",                  // 9

          "       **** undefined msg ***     ",                  // A
          "       **** undefined msg ***     ",                  // B
          "Trap 12 (stack fault) handler installed",             // C
          "Trap 13 (general protection fault) handler installed" // D
       } ;
char szBuffer[255] ;
/****************************************************************************
    PROGRAM: Trapman.c
    PURPOSE: Creates an edit window
****************************************************************************/
HANDLE hInst;
HWND hEditWnd;                /* handle to edit window */
HWND hwnd;                    /* handle to main windows  */
/****************************************************************************
    FUNCTION: WinMain(HANDLE, HANDLE, LPSTR, int)
    PURPOSE: calls initialization function, processes message loop
****************************************************************************/
int PASCAL WinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow)
HANDLE hInstance;
HANDLE hPrevInstance;
LPSTR lpCmdLine;
int nCmdShow;
{
    MSG msg;
    if (!hPrevInstance)
        if (!InitApplication(hInstance))
            return (FALSE);
    if (!InitInstance(hInstance, nCmdShow))
        return (FALSE);
    while (GetMessage(&msg, NULL, NULL, NULL))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (msg.wParam);
}
/****************************************************************************
    FUNCTION: InitApplication(HANDLE)
    PURPOSE: Initializes window data and registers window class
****************************************************************************/
BOOL InitApplication(hInstance)
HANDLE hInstance;
{
    WNDCLASS  wc;
    wc.style = NULL;
    wc.lpfnWndProc = MainWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
//  wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE( TRAPMANICON ));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName =  "TrapManMenu";
    wc.lpszClassName = "TrapManWClass";
    return (RegisterClass(&wc));
}
/****************************************************************************
    FUNCTION:  InitInstance(HANDLE, int)
    PURPOSE:  Saves instance handle and creates main window
****************************************************************************/
BOOL InitInstance(hInstance, nCmdShow)
    HANDLE          hInstance;
    int             nCmdShow;
{
    RECT            Rect;
    int             OurDS ;
    hInst = hInstance;
    hwnd = CreateWindow(
        "TrapManWClass",
        "TrapMan",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        400, 100,
        NULL,
        NULL,
        hInstance,
        NULL
    );
    if (!hwnd)
        return (FALSE);
    GetClientRect(hwnd, (LPRECT) &Rect);
    /* Create a child window */
    hEditWnd = CreateWindow("Edit",
        NULL,
        WS_CHILD | WS_VISIBLE |
        ES_MULTILINE |
        WS_VSCROLL | WS_HSCROLL |
        ES_AUTOHSCROLL | ES_AUTOVSCROLL,
        0,
        0,
        (Rect.right-Rect.left),
        (Rect.bottom-Rect.top),
        hwnd,
        IDC_EDIT,                          /* Child control i.d. */
        hInst,
        NULL);
    if (!hEditWnd)
    {
        DestroyWindow(hwnd);
        return (NULL);
    }
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
    wsprintf((LPSTR) szBuffer,"PLEASE USE THE FULL VERSION OF TRAPMAN");
    PutInEditControl(szBuffer, 1) ;
    wsprintf((LPSTR) szBuffer," (INCLUDED ON DISK) FOR DEBUGGING.  
                                                               This version ");
    PutInEditControl(szBuffer, 1) ;
    wsprintf((LPSTR) szBuffer,"  is meant to be used only for a better");
    PutInEditControl(szBuffer, 1) ;
    wsprintf((LPSTR) szBuffer,"  understanding of the article.");
    PutInEditControl(szBuffer, 1) ;
    wsprintf((LPSTR) szBuffer,"                                       ");
    PutInEditControl(szBuffer, 1) ;
    _asm push ds
    _asm pop OurDS
    wsprintf((LPSTR) szBuffer,"This instance's DS is %0X", OurDS) ;
    PutInEditControl(szBuffer, 1) ;
    wDPMIVersion = DPMIGetVersion() ;
    wsprintf((LPSTR) szBuffer,"The DPMI Version is %0X", wDPMIVersion) ;
    PutInEditControl(szBuffer, 1) ;
// standard mode Win300 returns '90h' instead of 0x5A required by the DPMI ...
    if ((wDPMIVersion > 0x005a) &&
        (wDPMIVersion != 0x90)){ // 1.0+ not supported 0x5A is 0.9
       MessageBox(NULL, "Warning:  Unknown DPMI Host!", "TRAPMAN",
           MB_ICONEXCLAMATION | MB_OK) ;
//       return 0 ;
       }
    #define     TRAPMENU   1 // the SECOND pull down (0 is first)
    hwndTrapMenu = GetSubMenu (GetMenu(hwnd), TRAPMENU);
    return (TRUE);
}
/****************************************************************************
    FUNCTION: MainWndProc(HWND, unsigned, WORD, LONG)
****************************************************************************/
long FAR PASCAL MainWndProc(hWnd, message, wParam, lParam)
HWND hWnd;
unsigned message;
WORD wParam;
LONG lParam;
{
    FARPROC lpProcAbout;
    switch (message) {
        case WM_SYSCOMMAND:
            switch (wParam & SYSCOMMAND_MASK) {
                // make sure we restore old trap handlers...
                case SC_CLOSE:
                    SendMessage(hWnd, WM_COMMAND, IDM_EXIT, 0L) ;
                    break;
                default:
                   return (DefWindowProc(hWnd, message, wParam, lParam));
                }
            break ;
        case WM_COMMAND:
            switch (wParam) {
                case IDM_ABOUT:
                    lpProcAbout = MakeProcInstance(About, hInst);
                    DialogBox(hInst, "AboutBox", hWnd, lpProcAbout);
                    FreeProcInstance(lpProcAbout);
                    break;
                /* file menu commands */
                  case IDM_NEW:
                    if (IDYES == MessageBox(NULL, "Erase current edit buffer?",
                                                       "TrapMan", MB_OKCANCEL))
                       SendMessage(hEditWnd, WM_SETTEXT, 0, (LONG) (LPSTR) "");
                    break ;
                case IDM_SAVE:
                case IDM_SAVEAS:
                    SaveBuffer("(Untitled).trp") ;
                    break ;
                case IDM_OPEN:
                    MessageBox (
                          GetFocus(),
                          "Command not implemented",
                          "TrapMan",
                          MB_ICONASTERISK | MB_OK);
                    break;
               case IDM_EXIT:
                    // reset the previous trap handlers...if any existed
                    if (WeGot13 && Prev13)
                       DPMISetProcessorExceptionHandlr( 13 , (void _far *) 
                                                                     Prev13 ) ;
                    if (WeGot12 && Prev12)
                       DPMISetProcessorExceptionHandlr( 12 , (void _far *) 
                                                                     Prev12 ) ;
                    if (WeGot6 && Prev6)
                       DPMISetProcessorExceptionHandlr( 6 , (void _far *) 
                                                                      Prev6 ) ;
                    if (WeGot0 && Prev0)
                       DPMISetProcessorExceptionHandlr( 0 , (void _far *) 
                                                                      Prev0 ) ;
                    DestroyWindow(hWnd);
                    break;
                /* trap menu commands */
                case IDM_GP:
                    if (!WeGot13)
                    {
                       SetFaultProc(13, (long *) &Prev13,
                       (void _far *) MyGPProc,
                       TrapMsgs[13]) ;
                       CheckMenuItem(hwndTrapMenu,wParam,
                               MF_CHECKED | MF_BYCOMMAND) ;
                       WeGot13 = 1 ;
                    }
                    else
                    {
                       CheckMenuItem(hwndTrapMenu,wParam,
                               MF_UNCHECKED | MF_BYCOMMAND) ;
                       WeGot13 = 0 ;
                       if (Prev13)
                       {
                          PutInEditControl(" *** Resetting previous trap 13 
                                                                 handler", 1) ;
                          DPMISetProcessorExceptionHandlr( 13 , (void _far *) 
                                                                     Prev13 ) ;
                          Prev13 = NULL ;
                       }
                    }
                    break;
                case IDM_STACK:
                    if (!WeGot12)
                    {
                       SetFaultProc(12, (long *) &Prev12,
                       (void _far *) MySPProc,
                       TrapMsgs[12]) ;
                       CheckMenuItem(hwndTrapMenu,wParam,
                               MF_CHECKED | MF_BYCOMMAND) ;
                       WeGot12 = 1 ;
                    }
                    else
                    {
                       CheckMenuItem(hwndTrapMenu,wParam,
                               MF_UNCHECKED | MF_BYCOMMAND) ;
                       WeGot12 = 0 ;
                       if (Prev12)
                       {
                          PutInEditControl(" *** Resetting previous trap 12 
                                                                 handler", 1) ;
                          DPMISetProcessorExceptionHandlr( 12 , (void _far *) 
                                                                     Prev12 ) ;
                          Prev12 = NULL ;
                       }
                    }
                    break;
                case IDM_INVALIDOP:
                    if (!WeGot6)
                    {
                       SetFaultProc(6, (long *) &Prev6,
                       (void _far *) MyInvalidOpProc,
                       TrapMsgs[6]) ;
                       CheckMenuItem(hwndTrapMenu,wParam,
                               MF_CHECKED | MF_BYCOMMAND) ;
                       WeGot6 = 1 ;
                    }
                    else
                    {
                       CheckMenuItem(hwndTrapMenu,wParam,
                               MF_UNCHECKED | MF_BYCOMMAND) ;
                       WeGot6 = 0 ;
                       if (Prev6)
                       {
                          PutInEditControl(" *** Resetting previous trap 6 
                                                                 handler", 1) ;
                          DPMISetProcessorExceptionHandlr( 6 , (void _far *) 
                                                                      Prev6 ) ;
                          Prev6 = NULL ;
                       }
                    }
                    break;
                case IDM_TRAPZERO:
                    if (!WeGot0)
                    {
                       SetFaultProc(0, (long *) &Prev0,
                       (void _far *) MyDivideByZeroProc,
                       TrapMsgs[0]) ;
                       CheckMenuItem(hwndTrapMenu,wParam,
                               MF_CHECKED | MF_BYCOMMAND) ;
                       WeGot0 = 1 ;
                    }
                    else
                    {
                       CheckMenuItem(hwndTrapMenu,wParam,
                               MF_UNCHECKED | MF_BYCOMMAND) ;
                       WeGot0 = 0 ;
                       if (Prev0)
                       {
                          PutInEditControl(" *** Resetting previous trap 0 
                                                                 handler", 1) ;
                          DPMISetProcessorExceptionHandlr( 0 , (void _far *) 
                                                                      Prev0 ) ;
                          Prev0 = NULL ;
                       }
                    }
                    break;
                case IDM_DEFAULT:
                    if (!WeGot13)
                       PostMessage(hWnd, WM_COMMAND, IDM_GP, 0L) ;
                    if (!WeGot12)
                       PostMessage(hWnd, WM_COMMAND, IDM_STACK, 0L) ;
                    if (!WeGot6)
                       PostMessage(hWnd, WM_COMMAND, IDM_INVALIDOP, 0L) ;
                    break;
                case IDC_EDIT:
                    if (HIWORD (lParam) == EN_ERRSPACE)
                    {
                        MessageBox (
                              GetFocus ()
                            , "Out of memory."
                            , "TrapMan"
                            , MB_ICONHAND | MB_OK
                        );
                    }
                    break;
            }
            break;
        case WM_SETFOCUS:
            SetFocus (hEditWnd);
            break;
        case WM_SIZE:
            MoveWindow(hEditWnd, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return (DefWindowProc(hWnd, message, wParam, lParam));
    }
    return (NULL);
}
/****************************************************************************
    FUNCTION: About(HWND, unsigned, WORD, LONG)
    PURPOSE:  Processes messages for "About" dialog box
    MESSAGES:
        WM_INITDIALOG - initialize dialog box
        WM_COMMAND    - Input received
****************************************************************************/
BOOL FAR PASCAL About(hDlg, message, wParam, lParam)
HWND hDlg;
unsigned message;
WORD wParam;
LONG lParam;
{
    switch (message)
    {
        case WM_INITDIALOG:
            return (TRUE);
        case WM_COMMAND:
            if (wParam == IDOK
                || wParam == IDCANCEL)
            {
                EndDialog(hDlg, TRUE);
                return (TRUE);
            }
            break;
    }
    return (FALSE);
}
int SetFaultProc(unsigned char ThisTrap, long *PrevHandler,
                 void _far *MyFaultHandler, char *Msg )
{
  *PrevHandler = (void _far *) DPMIGetProcessorExceptionHandlr( ThisTrap ) ;
  wsprintf((LPSTR) szBuffer,"Prev Trap %d handler is %0lX", ThisTrap, 
                                                                *PrevHandler) ;
  PutInEditControl(szBuffer, 1) ;
  DPMISetProcessorExceptionHandlr(ThisTrap, MyFaultHandler ) ;
  PutInEditControl(Msg, 1) ;
  return TRUE ;
}
// passing in a NULL msg pointer should cause us to free any alloced buffer
BOOL PutInEditControl(char *Msg, int bWithReturn)
{
static HANDLE hBuff = NULL ;
static LPSTR  lpBuff ;
  int    iSizeOfBuff = 10 * 1024 ; // 10K
  if (NULL == hEditWnd)  // edit window is non-existent
     return FALSE ;
  if (NULL == Msg)
  {
     if (hBuff)
     {
        GlobalFree(hBuff) ;
        hBuff = NULL ;
        return 0 ;
     }
  }
  if (NULL == hBuff)
  {
     hBuff = GlobalAlloc(GMEM_MOVEABLE, iSizeOfBuff) ;
     if (!hBuff)
        return 0 ;
     lpBuff = GlobalLock(hBuff) ;
     if (!lpBuff)
        return 0 ;
  }
  SendMessage(hEditWnd, WM_GETTEXT, iSizeOfBuff, (LONG) lpBuff) ;
  lstrcat(lpBuff, Msg) ;
  if (bWithReturn)
  {
     lstrcat(lpBuff, "\015\012") ;
  }
  SendMessage(hEditWnd, WM_SETTEXT, 0, (LONG) lpBuff) ;
  return TRUE ;
}
int KillCurrentDOSProcess()
{
  _asm mov  ah,  4Ch
  _asm mov  al, 0FFh
  _asm int  21h
  if (0)
     return 0 ; // get rid of no return code error message
}
// note this allocates and deletes a save buffer each time... We should really
// query the edit control to see how big it is, instead of assuming 10K is big
// enough. This is fine for as it shouldn't generate that much output!
BOOL SaveBuffer(char *filename)
{
  HANDLE hBuff ;
  PSTR   pBuff ;
  FILE  *flOutput ;
  int    NoOfChars, iSizeOfBuff = 10 * 1024 ;
  hBuff = LocalAlloc(GMEM_MOVEABLE, iSizeOfBuff) ;
  if (!hBuff)
     return 0 ;
  pBuff = LocalLock(hBuff) ;
  if (!pBuff)
     return 0 ;
  SendMessage(hEditWnd, WM_GETTEXT, iSizeOfBuff, (LONG) (LPSTR) pBuff) ;
_asm int 3h
  if (pBuff)
  {
     flOutput = fopen(filename, "rb") ;
     if (flOutput)
     {
         // warn user file exists -- only continue if they permit it!
         //   ow return
     }
     flOutput = fopen(filename, "w") ;
     if (flOutput)
     {
        NoOfChars = strlen(pBuff) ;
        fwrite(pBuff, sizeof(char), NoOfChars, flOutput) ;
        fclose(flOutput) ;
     }
     LocalUnlock(hBuff) ;
     LocalFree(hBuff) ;
  }
  // ow edit control error!
}
BOOL PrintOutTrapRegs( unsigned int BPofFrame )
{
// a NOP in the C version of TrapMan
  if (0)
     return 0 ;
}

Listing Two

#define argc __argc
#define argv __argv
extern int     argc ;
extern char  **argv ;
if (argc > 1) {
   int rc ;
   rc = WinExec(argv[1], SW_SHOW) ;
   if (rc < 0x20) {
      wsprintf( szBuffer, "Cannot load '%s', error code=%d",
                 (LPSTR) argv[1], rc) ;
      MessageBox(NULL, szBuffer, "TrapMan", MB_ICONEXCLAMATION | MB_OK) ;
      }
   }


Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.