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 covers the following topics:
Overview of exception handling (Section 11.1)
Raising an exception from a user program (Section 11.2)
Writing a structured exception handler (Section 11.3)
Writing a termination handler (Section 11.4)
11.1 Exception-Handling Overview
On Tru64 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 manual defines special structures and mechanisms that enable the processing of exceptional events on Tru64 UNIX systems in a more precise and organized way. Among the activities that the standard defines are the following:
The manner in which exception handlers are established
The way in which exceptions are raised
How the exception system searches for and invokes a handler
How a handler returns to the exception system
The manner in which the exception system traverses the stack and maintains procedure context
The run-time exception dispatcher that supports the structured exception-handling capabilities of the Tru64 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 Tru64 UNIX components
that support the exception-handling mechanism defined in the
Calling Standard for Alpha Systems
manual.
11.1.1 C Compiler Syntax
Syntax provided by the Tru64 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.
11.1.2 libexc Library Routines
The exception support library,
/usr/ccs/lib/cmplrs/cc/libexc.a
, provides routines with the following capabilities:
The ability to raise user-defined exceptions or convert UNIX signals to exceptions. These routines include:
exc_raise_status_exception
exc_raise_signal_exception
exc_raise_exception
exc_exception_dispatcher
exc_dispatch_exception
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.
The ability to perform virtual and actual unwinding of levels of procedure activations from the stack and continuing execution in a handler or other user code. These routines include:
unwind
exc_virtual_unwind
RtlVirtualUnwind
exc_resume
exc_longjmp
exc_continue
exc_unwind
RtlUnwindRfp
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 ability to access procedure-specific information and map any address within a routine to the corresponding procedure information. This information includes enough data to cause an unwind or determine whether a routine handles an exception. These routines include:
exc_add_pc_range_table
exc_remove_pc_range_table
exc_lookup_function_table_address
exc_lookup_function_entry
find_rpd
exc_add_gp_range
exc_remove_gp_range
exc_lookup_gp
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 more information on any routine provided in
/usr/ccs/lib/cmplrs/cc/libexc.a
, see the routine's reference page.
11.1.3 Header Files that Support Exception Handling
Various header files define the structures that support the exception-handling
system and the manipulation of procedure context.
Table 11-1
describes these files.
Table 11-1: Header Files that Support Exception Handling
File | Description |
excpt.h |
Defines the exception code structure and
defines a number of Tru64 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 more information. |
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 more information. |
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 more information. |
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
manual.
See
pdsc (4)
for more information. |
11.2 Raising an Exception from a User Program
A user program typically raises an exception in either of two ways:
A program can explicitly initiate an application-specific
exception by calling the
exc_raise_exception
or
exc_raise_status_exception
function.
These functions allow the calling
procedure to specify information that describes the exception.
A program can install a special signal handler,
exc_raise_signal_exception
, that converts a POSIX signal to an exception.
The
exc_raise_signal_exception
function invokes the exception
dispatcher to search the run-time stack for any exception handlers that have
been established in the current or previous stack frames.
In this case, the
code reported to the handler has EXC_SIGNAL in its facility field and the
signal value in its code field.
(See
excpt
(4)
and the
excpt.h
header file for a dissection of the code data structure.)
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 handlerexc_raise_signal_exception
moves this code toExceptionRecord.ExceptionInfo[0]
before invoking the exception dispatcher.
The examples in
Section 11.3
show how to explicitly
raise an exception and convert a signal to an exception.
11.3 Writing a Structured Exception Handler
The structured exception-handling
capabilities provided by the Tru64 UNIX C compiler allow you to deal with
the possibility that a certain exception condition may occur in a certain
code sequence.
These capabilities are always enabled (the
cc
command's
-ms
option is not required).
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 it 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:
< 0
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.
0
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.
> 0
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 more information.
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, 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 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.
Example 11-1: Handling a SIGSEGV Signal as a Structured Exception
#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.
Example 11-2: Handling an IEEE Floating-Point SIGFPE as a Structured Exception
#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:"); ieee_set_fp_control(float_traps); /* 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 0 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) { *((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: %d\n", quot); exit(1); }
The following is a sample run of this program:
%
% cc 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 0x09 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.
Example 11-3: Multiple Structured Exception Handlers
#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 Compaq-supplied widget\n", code, widgetname); 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 Compaq-supplied widget
%
a.out
Enter widget name:
bar
Exception 0x10ffe009: bar is not a widget
11.4 Writing a Termination Handler
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 cleanup 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;
Note
The
longjmp()
routine for Tru64 UNIX does not use an unwind operation. Therefore, in the presence of frame-based exception handling, do not use alongjmp()
from a try-body or termination-handler. Rather, use anexc_longjmp()
, which is implemented through an unwind operation.
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).
Using 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:
If the try body terminated normally, execution continues with
the statement following the complete
try...finally
block.
If the try body terminated abnormally with an explicit jump
out of the body, the jump is completed.
However, if the jump exits the body
of one or more containing
try...finally
statements, their
termination handlers are invoked before control is finally transferred to
the target of the jump.
If the try body terminated abnormally due to an unwind, a
jump to an exception handler, or an
exc_longjmp
call, control
is returned to the C run-time exception handler, which will continue invoking
termination handlers as required before jumping to the target of the unwind.
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
shows the order in which termination
handlers and exception handlers execute when an exception causes the termination
of the innermost try body.
Example 11-4: Abnormal Termination of a Try Block by an Exception
#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.