An exception is a special condition that occurs during the currently executing thread and requires the execution of code that acknowledges the condition and performs some appropriate actions. This code is known as an exception handler.
A termination handler consists of code that executes when the flow of control leaves a specific body of code. Termination handlers are useful for cleaning up the context established by the exiting body of code, performing such tasks as freeing memory buffers or releasing locks.
This chapter contains the following discussions:
On Digital UNIX systems, hardware traps exceptions, as described in the Alpha Architecture Reference Manual, and delivers them to the operating system kernel. The kernel converts certain hardware exceptions, such as bad memory accesses and arithmetic traps, to signals. A process can enable the delivery of any signal and establish a signal handler to deal with the consequences of the signal processwide.
The Calling Standard for Alpha Systems defines special structures and mechanisms that enable the processing of exceptional events on Digital UNIX systems in a more precise and organized way. Among the activities that the standard defines are the following:
The run-time exception dispatcher that supports the structured exception handling capabilities of the Digital UNIX C compiler is an example of the type of frame-based exception handler described in the standard. (See Section 11.3 for a discussion of structured exception handling.)
The following sections briefly describe the Digital UNIX components that support the exception handling mechanism defined in the Calling Standard for Alpha Systems.
Syntax provided by the Digital UNIX C compiler allows you to protect regions of code against user- or system-defined exception conditions. This mechanism, known as structured exception handling, allows you to define exception handlers and termination handlers and to indicate the regions of code that they protect.
The c_excpt.h header file defines the symbols and functions that user exception processing code can use to obtain the current exception code and other information describing the exception.
Routines in the exception support library, /usr/ccs/lib/cmplrs/cc/libexc.a, provide the following capabilities:
These exception management routines also provide the mechanism to dispatch exceptions to the appropriate handlers. In the case of C-language structured exception handling, described in Section 11.3, the C-specific handler invokes a routine containing user-supplied code to determine what action to take. The user-supplied code can either handle the exception or return for some other procedure activation to handle it.
Some of the unwind routines also support invoking handlers as they unwind so that the language or user can clean up items at particular procedure activations.
The C-language structured exception handler calls routines in the last two categories to allow user code to fix up an exception and resume execution, and to locate and dispatch to a user-defined exception handler. Section 11.3 describes this process. For detailed information on any routine provided in /usr/ccs/lib/cmplrs/cc/libexc.a, see the routine's reference page.
Various header files define the structures that support the exception handling system and the manipulation of procedure context. Table 11-1 describes these files.
File | Description |
excpt.h | Defines the exception code structure and defines a number of Digital UNIX exception codes; also defines the system exception and context records and associated flags and symbolic constants, the run-time procedure type, and prototypes for the functions provided in libexc.a. See excpt(4) for additional details. |
c_excpt.h | Defines symbols used by C-language structured exception handlers and termination handlers; also defines the exception information structure and functions that return the exception code, other exception information, and information concerning the state in which a termination handler is called. See c_excpt(4) for additional details. |
machine/fpu.h | Defines prototypes for the ieee_set_fp_control and ieee_get_fp_control routines, which enable the delivery of IEEE floating-point exceptions and retrieve information that records their occurrence; also defines structures and constants that support these routines. See ieee(3) for additional details. |
pdsc.h | Defines structures, such as the run-time procedure descriptor and code range descriptor, that provide run-time contexts for the procedure types and flow control mechanisms described in the Calling Standard for Alpha Systems. See pdsc(4) for additional details. |
A user program typically raises an exception in either of two ways:
Note
The exact exception code for arithmetic and software-generated exceptions, defined in the signal.h header file, is passed to a signal handler in the code argument. The special signal handler exc_raise_signal_exception moves this code to ExceptionRecord.ExceptionInfo[0] before invoking the exception dispatcher.
Examples in Section 11.3 illustrate how to explicitly raise an exception and convert a signal to an exception.
The structured exception handling capabilities provided by the Digital UNIX C compiler allow you to deal with the possibility that a certain exception condition may occur in a certain code sequence. The syntax establishing a structured exception handler is as follows:
try
{
try-body
}
except
(
exception-filter
)
{
exception-handler
}
The try-body is a statement or block of statements that the exception handler protects. If an exception occurs while the try body is executing, the C-specific run-time handler evaluates the exception-filter to determine whether to transfer control to the associated exception-handler, continue searching for a handler in outer-level try body, or continue normal execution from the point at which the exception occurred.
The exception-filter is an expression associated with the exception handler that guards a given try body. It can be a simple expression or can invoke a function that evaluates the exception. An exception filter must evaluate to one of the following integral values in order for the exception dispatcher to complete its servicing of the exception:
The exception dispatcher dismisses the exception and resumes the thread of execution that was originally disrupted by the exception. If the exception is noncontinuable, the dispatcher raises a STATUS_NONCONTINUABLE_EXCEPTION exception.
The exception dispatcher continues to search for a handler, first in any try...except blocks in which the current handler might be nested and then in the try...except blocks defined in the procedure frame preceding the current frame on the run-time stack. If a filter chooses not to handle an exception, it typically returns this value.
The exception dispatcher transfers control to the exception handler, and execution continues in the frame on the run-time stack in which the handler is found. This process, known as "handling the exception," unwinds all procedure frames below the current frame and causes any termination handlers established within those frames to execute.
Two intrinsic functions are allowed within the exception filter to access information about the exception being filtered:
long exception_code
();
Exception_info_ptr exception_info
();
The exception_code function returns the exception code. The exception_info function returns a pointer to an EXCEPTION_POINTERS structure. Using this pointer, you can access the machine state (for instance, the system exception and context records) at the time of the exception. See excpt(4) and c_excpt(4) for additional details.
You can use the exception_code function within an exception filter or exception handler. However, you can use the exception_info function only within an exception filter. If you need to use the information returned by the exception_info function within the exception handler, you should invoke the function within the filter and store the information locally. If you need to refer to exception structures outside of the filter, you must copy them as well because their storage is valid only during the execution of the filter.
When an exception occurs, the exception dispatcher virtually unwinds the run-time stack until it reaches a frame for which a handler has been established. The dispatcher initially searches for an exception handler in the stack frame that was current when the exception occurred.
If the handler is not in this stack frame, the dispatcher virtually unwinds the stack (in its own context), leaving the current stack frame and any intervening stack frames intact until it reaches a frame that has established an exception handler. It then executes the exception filter associated with that handler.
During this phase of exception dispatching, the dispatcher has only virtually unwound the run-time stack; all call frames that may have existed on the stack at the time of the exception are still there. If it cannot find an exception handler or if all handlers reraise the exception, the exception dispatcher invokes the system last-chance handler. (See exc_set_last_chance_handler(3) for instructions on how to set up a last-chance handler.)
By treating the exception filter as if it were a Pascal-style nested procedure, exception handling code evaluates the filter expression within the scope of the procedure that includes the try...except block. This allows the filter expression to access the local variables of the procedure containing the filter, even though the stack has not actually been unwound to the stack frame of the procedure that contains the filter.
Prior to executing an exception handler (for instance, if an exception filter returns EXCEPTION_EXECUTE_HANDLER), the exception dispatcher performs a real unwind of the run-time stack, executing any termination handlers established for try...finally blocks that terminated as a result of the transfer of control to the exception handler. Only then does the dispatcher call the exception handler.
The exception-handler is a compound statement that deals with the exception condition. It executes within the scope of the procedure that includes the try...except construct and can access its local variables. A handler can respond to an exception in several different ways, depending on the nature of the exception. For instance, it can log an error or correct the circumstances that led to the exception being raised.
Either an exception filter or exception handler can take steps to modify or augment the exception information it has obtained and ask the C-language exception dispatcher to deliver the new information to exception code established in some outer try body or prior call frame. This activity is more straightforward from within the exception filter, which operates with the frames of the latest executing procedures - and the exception context - still intact on the run-time stack. The filter simply completes its processing by returning a 0 to the dispatcher to request the dispatcher to continue its search for the next handler.
For an exception handler to trigger a previously established handler, it must raise another exception, from its own context, that the previously-established handler is equipped to handle.
Example 11-1 shows a simple exception handler established to handle a segmentation violation signal (SIGSEGV) that has been converted to an exception by the exc_raise_signal_exception signal handler.
#include <signal.h> #include <excpt.h> #include <machine/fpu.h> #include <errno.h>main () { Exception_info_ptr except_info; PCONTEXT context_record; system_exrec_type *exception_record; long code; sigset_t newmask, oldmask; struct sigaction act, oldact; char *x=0; /* Set up things so that SIGSEGV signals are delivered. Set exc_raise_signal_exception as the SIGSEGV signal handler in sigaction. */ act.sa_handler = exc_raise_signal_exception; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (sigaction(SIGSEGV, &act, &oldact) < 0) perror("sigaction:"); /* If a segmentation violation occurs within the following try block, the run-time exception dispatcher calls the exception filter associated with the except statement to determine whether to call the exception handler to handle the SIGSEGV signal exception. */ try { *x=55; } * The exception filter tests the exception code against SIGSEGV. If it tests true, the filter returns 1 to the dispatcher, which then executes the handler; if it tests false, the filter returns -1 to the dispatcher, which continues its search for a handler in the previous run-time stack frames. Eventually the last-chance handler executes. Note: Normally the printf in the filter would be replaced with a call to a routine that logged the unexpected signal. */ except(exception_code() == EXC_VALUE(EXC_SIGNAL,SIGSEGV) ? 1 : (printf("unexpected signal exception code 0x%lx\n", exception_code()), 0)) { printf("segmentation violation reported: handler\n"); exit(0); } printf("okay\n"); exit(1); }
The following is a sample run of this program:
%
cc segfault_ex.c -lexc
%
a.out
segmentation violation reported in handler
Example 11-2 is similar to Example 11-1 insofar as it also demonstrates a way of handling a signal exception, in this case, a SIGFPE. This example further shows how an IEEE floating-point exception, floating divide-by-zero, must be enabled by a call to ieee_set_fp_control( ), and how the handler obtains more detailed information on the exception by reading the system exception record.
#include <signal.h> #include <excpt.h> #include <machine/fpu.h> #include <errno.h>main () { Exception_info_ptr except_info; PCONTEXT context_record; system_exrec_type exception_record; long code; sigset_t newmask, oldmask; struct sigaction act, oldact; unsigned long float_traps=IEEE_TRAP_ENABLE_DZE, trap_mask; int fpsigstate; double temperature=75.2, divisor=0.0, quot, return_val; /* Set up things so that IEEE DZO traps are reported and that SIGFPE signals are delivered. Set exc_raise_signal_exception as the SIGFPE signal handler. */ act.sa_handler = exc_raise_signal_exception; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (sigaction(SIGFPE, &act, &oldact) < 0) perror("sigaction:"); if (ieee_set_fp_control(float_traps) < 0) { printf("set_fp_control problem"); exit(1); } /* If a floating divide-by-zero FPE occurs within the following try block, the run-time exception dispatcher calls the exception filter associated with the except statement to determine whether the SIGFPE signal exception is to be handled by the exception handler. */ try { printf("quot = IEEE %.2f / %.2f\n",temperature,divisor); quot = temperature / divisor; } /* The exception filter saves the exception code and tests it against SIGFPE. If it tests true, the filter obtains the exception information, copies the exception record structure, and returns 1 to the dispatcher which then executes the hand- ler. If the filter's test of the code is false, the filter returns -1 to the handler, which continues its search for a handler in previous run-time frames. Eventually the last-chance handler executes. Note: Normally the filter printf is replaced with a call to a routine that logged the unexpected signal. */ except((code=exception_code()) == EXC_VALUE(EXC_SIGNAL,SIGFPE) ? (except_info = exception_info(), exception_record = *(except_info->ExceptionRecord), 1) : (printf("unexpected signal exception code 0x%lx\n", exception_code()), 0)) /* The exception handler follows and prints out the signal code, which has the following format:
0x 8 0ffe 0003 | | | | hex SIGFPE EXC_OSF facility EXC_SIGNAL */ { printf("Arithmetic error\n"); printf("exception_code() returns 0x%lx\n", code); printf("EXC_VALUE macro in excpt.h generates 0x%lx\n", EXC_VALUE(EXC_SIGNAL, SIGFPE)); printf("Signal code in the exception record is 0x%lx\n", exception_record.ExceptionCode); /* To find out what type of SIGFPE this is, look at the first optional parameter in the exception record. Verify that it is FPE_FLTDIV_FAULT). */ printf("No. of parameters is %u\n", exception_record.NumberParameters); printf("SIGFPE type is 0x%lx\n", exception_record.ExceptionInformation[0]); /* Set return value to IEEE_PLUS_INFINITY and return. */ if (exception_record.ExceptionInformation[0] == FPE_FLTDIV_FAULT) { *((unsigned long *) &return_val=IEEE_PLUS_INFINITY; printf("Returning 0x%f to caller\n", return_val); return(0); } /* If this is a different kind of SIGFPE, return gracelessly. */ else return(-1); } /* We get here only if no exception occurred in the try block. */ printf("okay"); exit(1); }
The following is a sample run of this program:
%
cc -ieee_with_no_inexact sigfpe_ex.c -lexc
%
a.out
quot = IEEE 75.20 / 0.00 Arithmetic error exception_code() returns 0x80ffe0003 The EXC_VALUE macro in excpt.h generates 0x80ffe0003 The signal code in the exception record is 0x80ffe0003 No. of parameters is 1 SIGFPE type is 0x10 Returning 0xINF to caller
A procedure (or group of interrelated procedures) can contain any number of try...except constructs, and can nest these constructs. If an exception occurs within the try...except block, the system invokes the exception handler associated with that block.
Example 11-3 demonstrates the behavior of multiple try...except blocks by defining two private exception codes and raising either of these two exceptions within the innermost try block.
#include <excpt.h> #include <strings.h> #include <stdio.h> #define EXC_NOTWIDGET EXC_VALUE(EXC_C_USER, 1) #define EXC_NOTDECWIDGET EXC_VALUE(EXC_C_USER, 2) void getwidgetbyname(); /* main() sets up an exception handler to field the EXC_NOTWIDGET exception and then calls getwidgetbyname(). */ main(argc, argv) int argc; char *argv[]; { char *widget[20]; long code; try { if (argc > 1) strcpy(widget, argv[1]); else { printf("Enter widget name: "); gets(widget); } getwidgetbyname(widget); } except((code=exception_code()) == EXC_NOTWIDGET) { printf("Exception 0x%lx: %s is not a widget\n", code, widget); exit(0); } } /* getwidgetbyname() sets up an exception handler to field the EXC_NOTDECWIDGET exception. Depending upon the data it is passed, its try body calls exc_raise_status_exception() to generate either of the user-defined exceptions. */ void getwidgetbyname(char* widgetname[20]) { long code; try { if (strcmp(widgetname, "foo") == 0) exc_raise_status_exception(EXC_NOTDECWIDGET); if (strcmp(widgetname, "bar") == 0) exc_raise_status_exception(EXC_NOTWIDGET); } /* The exception filter tests the exception code against EXC_NOTDECWIDGET. If it tests true, the filter returns 1 to the dispatcher; if it tests false, the filter returns -1 to the dispatcher, which continues its search for a handler in the previous run-time stack frames. When the generated exception is EXC_NOTWIDGET, the dispatcher finds its handler in main()'s frame. */ except((code=exception_code()) == EXC_NOTDECWIDGET) { printf("Exception 0x%lx: %s is not a DEC-supplied widget\n", code, widget); exit(0); } printf("widget name okay\n"); }
The following is a sample run of this program:
%
cc raise_ex.c -lexc
%
a.out
Enter widget name:
foo
Exception 0x20ffe009: foo is not a DEC-supplied widget
%
a.out
Enter widget name:
bar
Exception 0x10ffe009: bar is not a widget
The cc compiler allows you to ensure that a specified block of termination code is executed whenever control is passed from a guarded body of code. The termination code is executed regardless of how the flow of control leaves the guarded code. For example, a termination handler can guarantee that clean-up tasks are performed even if an exception or some other error occurs while the guarded body of code is executing.
The syntax for a termination handler is as follows:
try
{
try-body
}
finally
{
termination-handler
}
The try-body is the code, expressed as a compound statement, that the termination handler protects. The try body can be a block of statements or a set of nested blocks. It can include the following statement, which causes an immediate exit from the block and execution of its termination handler:
leave;
The termination-handler is a compound statement that executes when the flow of control leaves the guarded try body, regardless of whether the try body terminated normally or abnormally. The guarded body is considered to have terminated normally when the last statement in the block is executed (that is, when the body's closing "}" is reached). Use of the leave statement also causes a normal termination. The guarded body terminates abnormally when the flow of control leaves it by any other means, for example, due to an exception or due to a control statement such as return, goto, break, or continue.
A termination handler can call the following intrinsic function to determine whether the guarded body terminated normally or abnormally:
int abnormal_termination( );
The abnormal_termination function returns 0 if the try body completed sequentially; otherwise, it returns 1.
The termination handler itself may terminate either sequentially or by a transfer of control out of the handler. If it terminates sequentially (by reaching the closing "}"), subsequent control flow depends on how the try body terminated:
Like exception filters, termination handlers are treated as Pascal-style nested procedures and are executed without the removal of frames from the run-time stack. A termination handler can thus access the local variables of the procedure in which it is declared.
Note that there is a performance cost in the servicing of abnormal terminations, inasmuch as abnormal terminations (and exceptions) are considered to be outside the normal flow of control for most programs. Keep in mind that explicit jumps out of a try body are considered abnormal termination. Normal termination is the simple case and costs less at run time.
In some instances, you can avoid this cost by replacing a jump out of a try body with a leave statement (which transfers control to the end of the innermost try body) and testing a status variable after completion of the entire try...finally block.
A termination handler itself may terminate nonsequentially (for instance, to abort an unwind) by means of a transfer of control (for instance, a goto, break, continue, return, exc_longjmp, or the occurrence of an exception). If this transfer of control exits another try...finally block, its termination handler will execute.
Example 11-4 illustrates the order in which termination handlers and exception handlers execute when an exception causes the termination of the innermost try body.
#include <signal.h> #include <excpt.h> #include <errno.h>#define EXC_FOO EXC_VALUE(EXC_C_USER, 1)
signed foo_except_filter() { printf("2. The exception causes the exception filter to be evaluated.\n"); return(1); }
main () { try { try { printf("1. The main body executes.\n"); exc_raise_status_exception(EXC_FOO); } finally { printf("3. The termination handler executes because control will leave the try...finally block to \n"); } } except(foo_except_filter()) { printf("4. execute the exception handler.\n"); } }
The following is a sample run of this program:
%
cc segfault_ex.c -lexc
%
a.out
1. The main body executes. 2. The exception causes the exception filter to be evaluated. 3. The termination handler executes because control will leave the try...finally block to 4. execute the exception handler.