/* * 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 #include #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; }