[ Next ] [ Previous ] | Chapter 7 |
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.
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.
|
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. |
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 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.
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.
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.
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.
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 |