/*
 * FAULT.C
 *
 * Very small Windows application demonstrating how to use the TOOLHELP
 * library to trap GP Faults and Divide by Zero exceptions.  Trapping these
 * faults allows an application to perform cleanup, save files, and
 * otherwise insure integrity of the user's data.
 *
 * FAULT uses the ToolHelp functions InterruptRegister and
 * InterruptUnRegister to hook itself into the interrupt handler
 * chain.  Before executing an operation that might cause an exception,
 * we use the Catch function to store the current register state.
 * If an exception occurs, ToolHelp will trap it and call our interrupt
 * handler in HANDLER.ASM.  Within the handler, we check if the
 * exception is something we can handle.  If not, that interrupt is
 * passed on to the next handler.  Otherwise it calls Throw, returning
 * control to the function that last called Catch.
 *
 * The handler catches Interrupts 0 (Divide by Zero) and 13 (GP Fault).
 *
 * Copyright(c) Microsoft Corp. 1992 All Rights Reserved
 *
 */

#include <windows.h>
#include <toolhelp.h>
#include "fault.h"


//Global variable block.
GLOBALS     stGlobals;
LPGLOBALS   pGlob=&stGlobals;

/*
 * These global variables hold information that is needed from the
 * interrupt handler.  They are set apart here to make them more visible.
 */
CATCHBUF    cbEx;                       //Stores register state.
LPCATCHBUF  pcbEx=(LPCATCHBUF)&cbEx;    //Convenient pointer
WORD        wException;                 //Indicates which exceptions to trap.



/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.   Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 *
 * Parameters:
 *  Standard
 *
 * Return Value:
 *  Value to return to Windows--termination code.
 *
 */

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
    {
    WNDCLASS        wc;
    MSG             msg;

    pGlob->hInst=hInstance;


    if (!hPrevInstance)
        {
        /*
         * Note that we do not need to unregister classes on a failure
         * since that's part of automatic app cleanup.
         */
        wc.style         = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc   = FaultWndProc;
        wc.cbClsExtra    = 0;
        wc.cbWndExtra    = 0;
        wc.hInstance     = pGlob->hInst;
        wc.hIcon         = LoadIcon(pGlob->hInst, MAKEINTRESOURCE(IDR_ICON));
        wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = COLOR_APPWORKSPACE + 1;
        wc.lpszMenuName  = MAKEINTRESOURCE(IDR_ICON);
        wc.lpszClassName = "Fault";

        if (!RegisterClass(&wc))
            return FALSE;
        }

    pGlob->hWnd=CreateWindow("Fault", "Exception Handler",
                             WS_MINIMIZEBOX | WS_OVERLAPPEDWINDOW,
                             CW_USEDEFAULT, CW_USEDEFAULT, 400, 120,
                             NULL, NULL, hInstance, NULL);

    if (NULL==pGlob->hWnd)
        return FALSE;

    ShowWindow(pGlob->hWnd, nCmdShow);
    UpdateWindow(pGlob->hWnd);

    while (GetMessage(&msg, NULL, 0,0 ))
        {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        }

    return msg.wParam;
    }





/*
 * FaultWndProc
 *
 * Purpose:
 *  Window class procedure.  Standard callback.
 *
 * Parameters:
 *  The standard.
 *
 * Return Value:
 *  The standard.
 *
 */

long FAR PASCAL FaultWndProc(HWND hWnd, UINT iMsg, UINT wParam, LONG lParam)
    {
    HANDLE      hMem;

    switch (iMsg)
        {
        case WM_CREATE:
            /*
             * Install the ExceptionHandler function in HANDLER.ASM
             * as our fault handler and store the proc address in the
             * global variable block.
             */

            pGlob->pfnInt=MakeProcInstance((FARPROC)ExceptionHandler, pGlob->hInst);

            if (NULL!=pGlob->pfnInt)
                {
                //If we fail to register, fail the create and the application.
                if (!InterruptRegister(NULL, pGlob->pfnInt))
                    {
                    MessageBox(hWnd, "InterruptRegister Failed.", "Fatal Error", MB_OK);
                    return -1L;
                    }
                }
            break;


        case WM_DESTROY:
            //Remove our exception handler and free the proc address.
            if (NULL!=pGlob->pfnInt)
                {
                InterruptUnRegister(NULL);
                FreeProcInstance((FARPROC)pGlob->pfnInt);
                }

            PostQuitMessage(0);
            break;


        case WM_COMMAND:
            switch (wParam)
                {
                case IDM_EXDIVIDEBYZERO:
                    if (FPerformCalculation())
                        {
                        //This should NOT happen.
                        MessageBox(hWnd, "IMPOSSIBLE: Missed a Divide by Zero!",
                                   "Fault", MB_OK | MB_ICONHAND);
                        }
                    else
                        {
                        MessageBox(hWnd, "Calculation failed: Divide by zero.",
                                   "Fault", MB_OK | MB_ICONEXCLAMATION);
                        break;
                        }

                    break;


                case IDM_EXGPFAULT:
                    hMem=HAllocateNumbers();

                    if (NULL!=hMem)
                        {
                        //This should NOT happen.
                        MessageBox(hWnd, "IMPOSSIBLE:  Missed the GP Fault!",
                                   "Fault", MB_OK | MB_ICONHAND);

                        GlobalFree(hMem);
                        }
                    else
                        {
                        MessageBox(hWnd, "Allocation failed: GP fault.",
                                   "Fault", MB_OK | MB_ICONEXCLAMATION);
                        break;
                        }

                    break;


                default:
                    break;
                }
            break;


        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }






/*
 * FPerformCalculation
 *
 * Purpose:
 *  Attempts to divide the number 10000 by the numbers 1 through 6.
 *  However, this function was poorly written to use the wrong
 *  terminating condition in a while loop, so the loop executes with
 *  zero as the divisor and faults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded (this should not happen)
 *                  FALSE if the function failed, even on a divide by zero
 *                  exception.
 */

BOOL FAR PASCAL FPerformCalculation(void)
    {
    int         iCatch;
    WORD        i;
    WORD        wValue;

    /*
     * Call Catch and indicate what exceptions to trap.
     *
     * The first time we call Catch here we will get a 0 return value.
     * When we call Throw in our exception handler, Catch returns with
     * the value given in the second parameter to Throw.  Throw must
     * use the same CATCHBUF we fill here in order to return here.
     */

    //Indicate the trap(s) we want.
    wException=EXCEPTION_DIVIDEBYZERO;

    //Save the register state.
    iCatch=Catch(pcbEx);

    //Check if we returned from the exception handler.
    if (0!=iCatch)
        {
        /*
         * Now we can safely exit this procedure, skipping the code
         * that faulted.  We indicate that we now want no exceptions
         * by setting wException to EXCEPTION_NONE.
         *
         ***BE SURE to turn off exception handling that uses Catch
         *  and Throw between messages.  In other words, only use
         *  Catch and Throw within the scope of a function, NOT on
         *  the scope of an application.
         */

        wException=EXCEPTION_NONE;
        return FALSE;
        }


    i=6;
    wValue=10000;

    /*
     * When we check i==1, the condition is TRUE so
     * we continue the loop.  However, the post-decrement
     * on i makes it zero, which will fault.
     */

    while (i--)
        wValue /=i;

    //We should never get here.
    wException=EXCEPTION_NONE;
    return TRUE;
    }






/*
 * HAllocateNumbers
 *
 * Purpose:
 *  Attempts to allocate a 1K block of memory and fill it with the
 *  repeating sequence of the values 0 through 255.  However, due to
 *  another bug in this function, we end up writing past the end of
 *  the segment.  We trap the GP Fault and recover by freeing the memory
 *  and indicating that the function failed.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HANDLE          A global memory handle containing the numbers if
 *                  successful, NULL otherwise (including when we GP fault).
 */

HANDLE FAR PASCAL HAllocateNumbers(void)
    {
    LPSTR       psz;
    WORD        i;
    int         iCatch;
    HANDLE      hMem;

    /*
     * Call Catch and indicate what exceptions to trap.
     *
     * The first time we call Catch here we will get a 0 return value.
     * When we call Throw in our exception handler, Catch returns with
     * the value given in the second parameter to Throw.  Throw must
     * use the same CATCHBUF we fill here in order to return here.
     */

    //Indicate the trap(s) we want.
    wException=EXCEPTION_GPFAULT;

    //Save the register state.
    iCatch=Catch(pcbEx);

    //Check if we returned from the exception handler.
    if (0!=iCatch)
        {
        /*
         * Free any resources this function allocated, perform other
         * cleanup, turn OFF any exception handling, and return a failure.
         */

        if (NULL!=hMem)
            {
            GlobalUnlock(hMem);
            GlobalFree(hMem);
            }

        wException=EXCEPTION_NONE;
        return NULL;
        }


    //Get 1024 bytes of memory.
    hMem=GlobalAlloc(GMEM_MOVEABLE, 1024);
    psz=GlobalLock(hMem);

    /*
     * Write to 1025 bytes of memory, thus accidentally walking over
     * the edge.  Another case where an erroneous terminating condition
     * in a loop can cause such a problem.
     */

    i=0;
    while (i++ <= 1024)     //Should be i++ < 1024, not <=
        *psz++=(char)i;

    //We should never get here to return the handle.
    GlobalUnlock(hMem);
    wException=EXCEPTION_NONE;
    return hMem;
    }
