[ Next ] [ Previous ]     Chapter 7
   [ Contents ]  [ Chapter6: DLLs ]  [ Chapter 8: Interfacing with OS/2 Devices ]

Exception Handling

OS/2 provides an opportunity for a program to interrupt system errors and handle them in their own manner. These system "errors" are known as exceptions and are not really errors, but more abnormal conditions. Some types of exceptions are guard-page exceptions, divide-by-zero exceptions, illegal instruction, and access violation (or protection violation). Most everyone has seen the black protection violation screen, which only lets the user end the program. Wouldn't it be nice to intercept that exception and either fix the problem ahead of time or at least provide an error message that was somewhat intelligible to the user? Exception handlers are the answer.
There are two kinds of exceptions generated by the operating system, asynchronous exceptions and synchronous exceptions. Asynchronous exceptions are caused by events external to a thread. Synchronous exceptions are caused by events internal to a thread. Some common synchronous exceptions include guard-page exceptions, divide-by-zero exceptions, and access violations. All the asynchronous exceptions generate one of two exception types, XCPT_SIGNAL or XCPT_ASYNC_PROCESS_TERMINATE.  Asynchronous exceptions, except for the XCPT_ASYNC_PROCESS_TERMINATE exception, are also known as signal exceptions. Signal exceptions are available only to non-Presentation Manager processes.

When a synchronous exception occurs, the operating system sends an exception just to the thread causing the exception. If the operating system terminates the application, a XCPT_ASYNC_PROCESS_TERMINATE is sent to all the other threads in the process.

When an asynchronous exception occurs, the operating system sends an exception just to the main thread.  

How to Register an Exception Handler

Exception handlers are registered on a per-thread basis using the function

/*  Registers an exception handler for the current thread. */
 #define INCL_DOSEXCEPTIONS
 #include <os2.h>

/* A pointer to the exception registration record that describes the exception handler to be registered. */
 PEXCEPTIONREGISTRATIONRECORD    pERegRec;
 APIRET                          ulrc;      /*  Return Code. */

 ulrc = DosSetExceptionHandler(pERegRec);

Exception handlers can be "nested" as a chain of exception-handling functions. The operating system will call the last handler in the chain: after that function has completed, it may call the next-to-last handler, and so on. An exception handler will do its work and then rerun a value to the operating system that indicates whether to continue with the next exception handler registered in the chain or to dismiss the exception.

General use  for exception handlers are  to handle memory faults, and Guard Page  example  will show the exception handler working with a memory fault.
 Memory exceptions can occur when an application attempts to access a guard page, attempts to use memory that has been allocated but not committed
 (a sparse memory object), or when an application attempts to write to memory that has read-only access. Without an application-registered exception
 handler, some of these exceptions might cause the application to terminate. If the application registers its own exception handler, it can correct the cause
 of the memory fault and continue to run.
Gotcha!

On the other hand, massive usage of memory exceptions for memory allocation in application program has  one, but very unpleasant drawback. Debugger also use memory exception handlers for catching up memory violation errors and programmer should think enough before using memory exceptions technique for memory allocation.
C/C++ compiler has memory exception handling in its memory heap model implementation and malloc's functions family as well as in new/delete core, so application programmer generally don't need to use memory exceptions.

The EXCEPTIONREGISTRATIONRECORD data structure forms a linked list of exception handlers. The first element in the structure is a pointer to either the next exception handler or an end-of-list marker, and is filled in by the operating system. The second is a pointer to the exception-handling function currently being registered and should be filled in by the developer. When registering an exception handler, this structure must be local to the procedure that contains DosSetExceptionHandler, as opposed to a global structure.
Gotcha!

Before exiting your program, make sure you call the function DosUnsetExeptionHandler. If you do not, you will probably see a stack overflow error.

What an Exception Handler Looks Like

An exception handler should use the  following prototype

APIRET APIENTRY  myHandler(PEXCEPTIONREPORTRECORD pERepRec,
                            PEXCEPTIONREGISTRATIONRECORD pERegRec,
                            PCONTEXTRECORD pCtxRec,
                            PVOID p)

The EXCEPTIONREPORTRECORD structure is a data structure that describes the exception and includes the exception type and other exception information.

/*
 This structure contains machine-independent information about an exception or unwind. No system exception will ever have more parameters than the value of EXCEPTION_MAXIMUM_PARAMETERS. User exceptions are not bound to this limit.
*/
 typedef STRUCT _EXCEPTIONREPORTRECORD {
   ULONG                               ExceptionNum;    /* Exception number. */
   ULONG                               fHandlerFlags;   /* Handler flags. */
   STRUCT _EXCEPTIONREPORTRECORD      *NestedExceptionReportRecord;  /* Nested exception report record structure. */
   PVOID                               ExceptionAddress; /* Address of the exception. */
   ULONG                               cParameters;      /* Size of exception specific information. */
   ULONG                               ExceptionInfo[EXCEPTION_MAXIMUM_PARAMETERS]; /* Exception specific information. */
 } EXCEPTIONREPORTRECORD;

 typedef EXCEPTIONREPORTRECORD *PEXCEPTIONREPORTRECORD;

The EXCEPTIONREGISTRATIONRECORD structure  is described in the last section. "How to Register an Exception Handler."

/* These structures are linked together to form a chain of exception handlers that are dispatched upon receipt of an exception. Exception handlers should not  be registered directly from a high level language such as "C". This is the responsibility of the language runtime routine.
*/
 typedef struct _EXCEPTIONREGISTRATIONRECORD {
   STRUCT _EXCEPTIONREGISTRATIONRECORD    *prev_structure;   /* Nested exception registration record structure. */
   _ERR                                   *ExceptionHandler; /* Pointer to the ERR function. */
 } EXCEPTIONREGISTRATIONRECORD;

 typedef EXCEPTIONREGISTRATIONRECORD *PEXCEPTIONREGISTRATIONRECORD;

The CONTEXTRECORD structure (as it is described in  \TOOLKIT\H\BSEXCPT.H)

struct _CONTEXT
{
  /* The flags values within this flag control the contents of a ContextRecord.
   * If the ContextRecord is used as an input parameter, then  for each portion
   * of the ContextRecord controlled by a flag whose value is set, it is assumed that that
   * portion of the ContextRecord contains valid context. If the ContextRecord
   * is being used to modify a thread's context, then only that
   * portion of the thread's context will be modified.
   * If the ContextRecord is used as an Input/Output parameter to capture the context
   * of a thread, then only those portions of the thread's context corresponding
   * to set flags will be returned.
   */

   ULONG ContextFlags;
  /* This section is specified/returned if the ContextFlags
   * contains the flag CONTEXT_FLOATING_POINT.
   */
   ULONG   ctx_env[7];
   FPREG   ctx_stack[8];

  /* This section is specified/returned if the ContextFlags
   * contains the flag CONTEXT_SEGMENTS.
   */
   ULONG ctx_SegGs;
   ULONG ctx_SegFs;
   ULONG ctx_SegEs;
   ULONG ctx_SegDs;

  /* This section is specified/returned if the ContextFlags
   * contains the flag CONTEXT_INTEGER.
   */
   ULONG ctx_RegEdi;
   ULONG ctx_RegEsi;
   ULONG ctx_RegEax;
   ULONG ctx_RegEbx;
   ULONG ctx_RegEcx;
   ULONG ctx_RegEdx;

  /* This section is specified/returned if the ContextFlags
   * contains the flag CONTEXT_CONTROL.
   */
   ULONG ctx_RegEbp;
   ULONG ctx_RegEip;
   ULONG ctx_SegCs;
   ULONG ctx_EFlags;
   ULONG ctx_RegEsp;
   ULONG ctx_SegSs;
};
typedef struct _CONTEXT CONTEXTRECORD;
typedef struct _CONTEXT *PCONTEXTRECORD;

is an input/output parameter that contains register contents at the time of the exception. If the exception handler will return XCPT_CONTINUE_EXECUTION, the structure can be modified. If it is modified without XCPT_CONTINUE_EXECUTION being specified, very bad things will happen.
The last parameter, the DISPATCHERCONTEXT structure, is undocumented because it should never be modified.
The 486 chip uses the address at FS:0 so point to the address of the first exception registration record. Many compilers implement exception handlers by modifying this value directly, rather than using the OS/2 API, in order to improve performance.

Signal Exceptions

Signal exceptions are special types of exceptions generated by only three events: when the user press Ctrl+C, when the user presses Ctrl+Break, and when another process terminates the application with the DosKillProcess  function.

In a order to receive the Ctrl+C and the Ctrl+Break exceptions, the thread must call DosSetSignalExeptionFocus. The kill process signal is sent whether this function is used or not.

Dos and Don'ts for Exception Handlers

An error in the exception handler may generate a recursive exception condition. This creates a situation that is very difficult to debug. Life will get much easier for the developer if the exception handler is unset when a fatal error condition occurs.

DosExitList and Exception Handlers

When all threads in a process receive the process termination exception, a process will execute the functions specified by DosExitList. The functions DosCreateThread and DosExecPgm should not be used in exit list routine.

A Guard Page Example

The following example illustrates guard-page handling. Guard pages provide an extra level of protection for two things, data and thread stacks. A guard page is like a traffic cop with a large brick wall as a stop sign. When someone hits that brick wall, he or she is going to have some reaction, in this case, a guard-page exception. This gives the programmer a chance to clean up the problem. When a page of memory is committed, it also can be marked as a guard page. If the application writes to the edge of the guard page, top or bottom, a guard-page exception is generated. The default behavior is designed for dynamic stack growth, and stacks grow downward. Because of this, the operating system will look to see if the next lower page is free, and if so, commit it. However, an exception handler gives the programmer some flexibility. If the application so chooses, it can commit the next higher page in the exception handler, and then return control back to the function that generated the guard-page exception. This memory management scheme the method used by most compilers to control thread stack growth.
GP.C
GP.MAK
GP.DEF
When an exception occurs, information about the exception is placed in the EXCEPTIONREPORTRCORD structure, and a pointer to these structures is passed to the exception handler.

 typedef STRUCT _EXCEPTIONREPORTRECORD {
   ULONG                               ExceptionNum;    /* Exception number. */
   ULONG                               fHandlerFlags;   /* Handler flags. */
   STRUCT _EXCEPTIONREPORTRECORD      *NestedExceptionReportRecord;  /* Nested exception report record structure. */
   PVOID                               ExceptionAddress; /* Address of the exception. */
   ULONG                               cParameters;      /* Size of exception specific information. */
   ULONG                               ExceptionInfo[EXCEPTION_MAXIMUM_PARAMETERS]; /* Exception specific information. */
 } EXCEPTIONREPORTRECORD;

ExceptionNum is the field that tells the type of exception that has occurred. In our case, we're looking for a XCPT_GUARD_PAGE_VIOLATION. If the exception is not a guard page, we pass it on through to the system exception handler by returning XCPT_CONTINUE_SEARCH. If a guard-page exception occurs, we check to see if we have enough memory to commit one more page. If the memory is available, we commit another page and set it as a guard page. The last thing we do is return XCPT_CONTINUE_EXECUTION, which tells the system to bypass the other exception handler and continue executing the program. The errant function statement will execute correctly, and the program functions as if no problems had occurred.  

Summary

Exception handlers are a flexible way to give the developer control over system errors. Exception handlers have a lot of restrictions because the process can be dying when the exception handler is executed. However, with the right amount of prudence, an exception handler provides a powerful tool for error control.

[ Next ] [ Previous ]     Chapter 7
   [ Contents ]  [ Chapter 6: DLLs ]  [ Chapter 8: Interfacing with OS/2 Devices ]