TOC PREV NEXT INDEX DOC LIST MASTER INDEX



Exception Handling

This chapter covers the following topics, but it mainly centers around describing the stack trace features in the Apex subsystem stack_trace.ss:


A Runtime View of Exception Handling

This discussion should give you an overview of how exception handling works inside the Apex Ada runtime.

Sources of Exceptions

There are several sources for exceptions in Ada:

Explicit
for example, raise Program_Error; statement
Implicit
for example, Constraint_Error due to an array bounds check.
Machine Exception
for example, Constraint_Error due to arithmetic overflow.

Explicit

There are two mechanisms for explicit raises:

Implicit

The same two mechanisms are used as above, depending on the architecture. On some architectures, only traps are used, on others, only calls to RAISE, still others use a mixture of the two.

Machine Exception

A machine exception occurs (for example, SIGFPE on Unix) and the OS transfers control to a machine exception handler that's been installed by the Ada runtime system. The machine exception handler decodes the machine exception, translating it, if applicable, to an Ada exception. To translate the machine exception into an Ada exception, the runtime modifies the PC and two of the parameter registers in the context record passed into the handler so that when the handler returns, the OS will transfer control to the RAISE code in the runtime rather than the code which caused the machine exception.

Explicit and Implicit exception handlers

Explicit exception handlers are those declared in the source code. For example,

Implicit handlers are not visible in the source code. They are created by the compiler usually for the purpose of doing some sort of clean-up work that's needed in the event of an exception. For example, the compiler inserts an implicit handler in a block where a controlled object is declared. Before an exception can be propagated out of the routine, the controlled object must first be finalized. The implicit handler takes care of this finalization.

Effecting transfer of control to a handler

From the point where the exception is first raised to the point where execution resumes in the correct exception handler, several actions occur:

1 . The current machine context is saved into a "virtual register set" (defined by $APEX_HOME/base/ada/rts_interfaces.ss/<view>/apex_stack_trace_types.1.ada's Context_Rec type).

2 . The call stack is unwound a frame at a time looking for an exception handler.

Unwinding is done using machine frame specific information to restore the "virtual registers" to the values they had when they were in that frame. Not every register is unwound. Only the registers which the machine's calling conventions require to be preserved across subprogram calls are unwound.

Searching for a handler is done by taking the current virtual register set's PC and looking it up in a table that was generated by the compiler and linker.

For each code region that has an exception handler, the table contains an address range which is the first and last PC of that region. If the PC is contained in one of these regions, there is an associated entry point for the exception handler. If the PC is not in a region with an exception handler, the frame is unwound.

3 . Once the handler is found, the exception occurrence record is copied to a point just beyond the stack pointer (that is, below the stack pointer in the case of machines where the stack grows toward smaller addresses) for that found frame and the virtual register set is restored into the real register set. Control is then transferred to the handler, passing in the exception id and pointer to the exception occurrence as implicit parameters to be used by the compiler generated code.

Once control is transferred to the exception handler, the behavior is as described in the Ada RM95 11.4.


Determining the Source of a Raised Exception at Run Time

It will be easier to understand this section if you have already read the preceding section. This section contains practical applications of the concepts covered in the preceding section.

Getting a Call Stack from the Source of the Exception

To a degree, the exception unwinding process is configurable.

When an exception handler is entered, by default the stack is cut back to a location just beyond where the exception occurrence object lies. In applications where stack space is at a premium and the depth of the call sequence executed in the exception handler is non-trivial, this is beneficial.

Figure 2 Exception Unwinding - Stack Cutback

Unfortunately, cutting the stack back upon entry destroys information about where the exception originally occurred, making it impossible to get a stack trace from that location.

Apex provides an alternative stack handling method whereby the stack pointer will not be cut back upon entrance to the exception handler, leaving the stack intact. This makes it possible for you to obtain a stack trace (in the form of a list of PC's) from where the exception was originally raised. Using this alternative, the stack still will be cut back upon exiting the handler.

Figure 3 Exception Unwinding - Stack Not Cutback

This functionality is made available to you via the stack_trace.ss subsystem in the Apex release. The units Apex_Exception_Stack_Trace_Enable, Apex_Stack_Trace, and Apex_Stack_Trace_Foundation, are the units of most interest in this subsystem.

Note: stack_trace.ss is currently supported for the following platforms:

There are a number of options you have to obtain a call stack, from high-level to low-level, provided via the stack_trace.ss subsystem:

Getting More Descriptive Output when an Unhandled Exception is Encountered

There are two mechanisms provided in Apex to get more information than the default "MAIN PROGRAM ABANDONED ..." message from a program that has died due to an unhandled exception.

1 . Core dumps

By calling Apex_Stack_Trace.Unhandled_Exception_Core_Dump_Enable at program start-up, the program will core dump when an unhandled exception occurs. The core dump will be made in such a way that when you start the debugger up on the core image, it will put you exactly at the spot where the exception was first raised.

2 . Trace dumps

By calling Apex_Exception_Stack_Trace_Enable, the runtime will output a human- and machine-readable file containing a dump of the registers at the current location as well as list of PC's. This file is far smaller than a core dump image, is in ASCII format (as opposed to binary), and can conveniently be sent by e-mail, so it has the advantage that an end-user of an application can easily forward the trace dump information back to the developer for analysis.

The following is an example of a trace_dump file.

This sample trace_dump file contains one trace done on a Sun SPARC. By "trace", we mean the set of 3 things:

a . The "TL:" line contains the name of the trace or the "trace_label".

b . The next four lines contain register names and values. The register names are specific to each processor. On the Sun SPARC, the global and output registers are printed in this trace dump.

c . The remaining lines contain the PC (program counter) values for each call frame.

For Embedded applications, the trace_dump file can be supplied as input to the debugger as invoked by either the apex_browse script, or the Browse tab of the File > Debug dialog box.

A single trace_dump file may contain many traces, each with its own trace label, register values, and list of PC values.

Both mechanisms can be enabled simultaneously, if desired. In that mode, first a trace dump file will be produced followed by the core file.

See Post-mortem Unhandled Exception Call Stack Analysis for tools that can be used with these files.

Dealing with Exception Chains

In many applications, it's a useful technique to change which exception is getting raised as it propagates up the dynamic call chain. Here's a very simple example of such a technique:

While this is a useful technique, it has the problem that if IO_Error is unhandled, then it's hard to determine which original exception (Device_Off_Line or Device_Not_Ready) caused the failure.

Apex gives you the ability to trace these earlier exceptions. The function Apex_Stack_Trace.Occurrence_Next points to the next most recent exception in the chain. This can be called repeatedly until the function returns a null - indicating that the previous one was the original exception in the chain. Once you have these exception occurrences, you can use Apex_Stack_Trace.Trace_Dump to get an dump file, or Apex_Stack_Trace_Foundation.Initialize to begin the process of manually walking up the stack.

Note that it is possible to obtain the earlier exception if and only if the exception handler was not exited before raising the new exception. In the following example, it is not possible to obtain the exception occurrence for either possibility of the earlier exception since the handler has been exited before the new raise:

Post-mortem Unhandled Exception Call Stack Analysis

If your program has died with an unhandled exception, you'll likely want to be able to find out where the exception originated in your application.

If you are running the program under the debugger, this is quite an easy thing to do. In the event of an unhandled exception, the debugger will take you right to the source code position where the exception occurred.

If you are not running the program under the debugger, there are two options:

If a core file was dumped (because Apex_Stack_Trace.Unhandled_Exception_Core_Dump_Enable was called), it can be read into the debugger, and you can use the debugger to examine the call stack and/or objects in your program.

If a trace file was dumped (because Apex_Exception_Stack_Trace_Enable was called), it can be read in by a special tool stacktrace, which is similar to the debugger. This tool currently has no graphical user interface, but can be used in command mode. It is invoked as:

Upon reading in the file, the debugger commands shown in Table 2 can be used. Debugger commands which would execute the program (for example, r, g, s, etc.), set breakpoints, or show memory cannot be used.

Table 2 Debugger Commands

vi
Puts you in "vi-style window" mode
ls
List the stack traces that are available from the trace dump file (there can be more than one in a file)
select stack
Selects the stack trace from the list available from the ls command.
reg
Prints the contents of the general purpose registers at the time of the exception. Note that not all of the registers in the display are valid, and are there just in case they might be able to give you a hint at what was going on in your program. Registers which probably don't contain the correct info are: the first two parameter registers the floating point registers the special registers
cs
Print the call stack for the selected stack. Note that parameters to the calls will not be displayed.
cd
Move the source position down one frame (to the caller) in the call stack
cu
Move the source position up one frame (to the callee) in the call stack
I
Switches to disassembly mode (works only in vi mode)

... as well as all of the other source and disassembly viewing commands. This tool does not support reading memory, or any form of the p command. See Using the Apex Debugger for details on most the commands shown above. The two debugging commands, select stack and ls, specific to the stacktrace tool are discussed in this section.

ls

List stacks

Syntax
Description

The ls command displays a numbered list of trace_labels. This command can only be used after running the stacktrace tool. The output from this command is used by the select stack command to change call stacks.

select stack

Select new stack.

Syntax
Arguments
Description

The select stack command is used to change call stacks. This command can only be used after running the stacktrace tool. Since there may be more than one stack dump in the same trace_dump file and/or more than one stack in the trace_dump file has the same trace_label, this command enables users to change stacks.


Detailed Usage of the stacktrace.ss Subprograms

Dealing with Unhandled Exceptions

Getting a Core Dump

To get a core file dumped when an exception occurs, you simply import the stack_trace.ss view to your view, add a with Apex_Stack_Trace; to your main unit, then add a call to this procedure at the beginning of your main unit.

Getting a Trace Dump

First the simple methods:

The easiest method for getting more information out of an unhandled exception is to use the provided unhandled exception trace dumping code.

There are two ways to start this code:

The first method requires no modification of the view, and so it is perhaps easier to maintain. However, it has the disadvantage that unhandled exceptions occurring during elaboration of packages prior to the main unit will not be traced.

The second method tells the compiler to emit a call to __START_EXCEPTION_TRACEBACK before any unit in your application has been elaborated, which is a label attached to the routine Apex_Stack_Trace_Example.Exception_Stack_Trace_Enable. Using this mechanism allows you to trace unhandled exceptions which are raised during elaboration of library units.

Note that Apex_Exception_Stack_Trace_Enable is a simple routine which just calls Apex_Stack_Trace_Example.Exception_Stack_Trace_Enable just to provide a shorter name which is a bit easier to use.

If an unhandled exception occurs after either of these two mechanisms have been used, a file called "trace_dump" will be created in the current directory. You can examine this ASCII text file yourself, or use it as input to the stack trace tool (see Post-mortem Unhandled Exception Call Stack Analysis).

Now the more complex methods:

Apex_Stack_Trace_Example.Exception_Stack_Trace_Enable makes use of this procedure in Apex_Stack_Trace_Foundation:

This procedure allows you to have a user defined routine attached to each of the these events: an exception is raised, an exception is reraised, an exception is discovered to be unhandled.

In the current release, System.Address is used instead of an access-to-procedure to maintain compatibility between Ada83 and Ada95 applications. The procedure specification for the three types of callout events is as follows:

  • Unhandled:

    The "Occurred_In_Main_Task" boolean is set to True if the unhandled exception occurred during execution of the main task, and is set to False otherwise.

    Note that any installed callout routine should have a pragma Suppress(All_Checks); to avoid an exception occurring in a precarious state.

    The callout routine installed by Apex_Stack_Trace_Example.Exception_Stack_Trace_Enable makes a call to Apex_Stack_Trace.Trace_Dump, passing it the exception occurrence. It leaves the other parameters to their defaults:

    To get the output to go to standard output, set Filename to stdout. To get the output to go to standard error, set the Filename to stderr. Otherwise, the output will go to the filename specified. If that file does not already exist, it will be created. If the Append parameter is set to True, the file will have the trace appended onto the end.

    A trace label is used by Stack_Trace for identifying particular trace dumps in the stacktrace tool. The string should not contain any embedded blanks or special characters. (see Post-mortem Unhandled Exception Call Stack Analysis). The trace label in the output will be the exception name appended with the specified Trace_Label string.

    Unwinding the Call Stack a Frame at a Time

    In addition to using the higher level Trace_Dump routine, you can use the lower-level code on which Trace_Dump is based in the package Apex_Stack_Trace_Foundation.

    Apex_Stack_Trace_Foundation contains routines which have been designed to derive a stack trace on the fly. That is, a "frame object" is obtained and it is viewed as the current frame. The interesting features of the current frame are processed in some way (for example, displayed or written to a file). Then the previous frame is obtained and it overwrites the current frame. The routines operate using a global data structure which is the task's call stack.

    To initialize a frame object with the machine context (stack pointer, frame pointer, PC, non-volatile registers, etc.) at the time the exception occurred, call Apex_Stack_Trace_Foundation's Initialize procedure:

    or

    This routine initializes User_Frame with the machine context saved when the exception was first raised. Having a variation that takes an Exception_Occurrence rather than an Exception_Occurrence_Access makes the code easier to write and read. For example, it allows you to write this:

    instead of:

    Note: For Ada83, there is no "choice_parameter_specification" [see Ada RM95 11.2(3)) with which to access the exception occurrence. Therefore we provide another routine for Ada83 (and Ada95 for compatibility) in the package Stack_Trace_Foundation for Ada83 to access the current, innermost exception occurrence:

    Note: For Ada83, the Apex_Occurrence_Types.Exception_Occurrence type is opaque. For Ada95, it is a subtype of Ada.Exceptions.Exception_Occurrence.

    Apex_Stack_Trace_Types.Frame is a machine-specific type which contains a "virtual register set" that represents the state of registers in a given call frame.

    A frame object can be declared in three ways:

    or

    or (Ada95 only)

    The latter two are preferable, though arguably messier, since they don't require any dynamic memory allocation (or deallocation).

    To unwind User_Frame a frame at a time, Apex_Stack_Trace_Foundation.Previous_Frame is called iteratively. Previous_Frame overwrites User_Frame with the context of the previous call frame, that is, its caller's frame.

    Unwind_Complete is set to True once the end of the call stack is reached. Calling Previous_Frame again after Unwind_Complete has been set to True will result in unpredictable behavior.

    From any frame, there are three pieces of data that are universally accessible: the program counter (pc), the stack pointer (sp), and the frame pointer (fp). The following functions from Apex_Stack_Trace_Foundation are used:

    Other components in the frame are accessible by directly referencing them. However, the components

    In other words, if possible, avoid using frame object's components directly.

    To get to the next exception occurrence in the chain (see Dealing with Exception Chains), pass the current exception occurrence into Apex_Stack_Trace_Foundation.Occurence_Next, and the next exception in the chain is returned (in LIFO order). This routine can be called repeatedly until it returns a Null access value (signifying the end of the chain). The body of Apex_Stack_Trace ($APEX_HOME/base/ada/stack_trace.ss/apex_stack_trace.2.ada) contains an example usage of this routine.

    Unwinding the Call Stack from Arbitrary Places in an Application

    The same technique for stack unwinding from an exception occurrence can be used from an arbitrary point in an application, with the only difference being that when you call the Trace_Dump or Initialize routine, you don't supply an exception occurrence object. The stack trace begins in such a way that the top frame is that of the subprogram calling the above mentioned routines.

    Both of these routines are overloaded so as to handle this case.

    One limitation is that if you use the lower-level subprograms in Stack_Trace_Foundation, you must call Initialize from the same routine that you do the stack unwinding. For example, this is correct:

    This is not correct:

    where My_Initialize_Wrapper is a procedure like:

    The problem with the latter example is that when My_Initialize_Wrapper returns, its stack frame is gone, and Previous_Frame will not be able to unwind the frame correctly, making Previous_Frame's behavior unpredictable (for example, cause the program to crash).

    Another limitation is that these routines cannot be used from the context of a runtime callout routine, such as one installed for task switches. The reason for this is that no "top-of-stack" sentinel has been created for these callouts, so the Previous_Frame routine will not be able to tell when unwinding is complete.

    Unwinding the Call Stack from a Synchronous Interrupt (Signal) Handler

    Note: This section does not apply to Apex for Tornado platforms.

    In an application, it is sometimes desirable to be able to obtain a call stack when a synchronous machine exception occurs (such as a SIGSEGV), log it, and then continue on, rather than let the program die.

    The Apex_Stack_Trace and Apex_Stack_Trace_Foundation packages allow you to do just that. When calling Apex_Stack_Trace.Trace_Dump or Apex_Stack_Trace_Foundation.Initialize, instead of passing an exception occurrence, or nothing, you pass the access value for the interrupt context record passed into the machine exception handler - on Unix this is the sigcontext record.

    The routine Previous_Frame assumes that the current frame has been completely and consistently set up. If a subprogram is interrupted while it is setting up the frame, that is, in the subprogram prologue, then the frame may be in an inconsistent state. This should not happen if the signal is synchronous. However, an asynchronous signal can be delivered to a process at any instruction, including a subprogram prologue. On Unix, synchronous signals include SIGBUS, SIGSEGV, SIGFPU, and SIGTRAP. Asynchronous signals on Unix include SIGINT, SIGALRM, SIGUSR1, and SIGPROF.

    If you use the Apex_Stack_Trace_Foundation.Initialize routine on Apex for Rational Exec, do not instantiate the ISR with the generic provided by V_Interrupts as this will confuse the stack unwinding code; just attach an uninstantiated handler.


    Customization

    User Available Runtime Exception Handling Hooks

    As was described in the section Dealing with Unhandled Exceptions, a callout installation procedure is available in the package Apex_Stack_Trace_Foundation.

    Customizing the String Returned by Ada.Exceptions.Exception_Information

    For Ada95, there is a callout that allows you to change what is returned by the routine Ada.Exceptions.Exception_Information. It can be found in Apex_Occurrence_Utils:

    To use this routine, call it during initialization of your program, passing it the 'Address of a function with the following specification:

    where <name> is an arbitrary function name

    If you call Exception_Information_Callout_Install, the function you install will be responsible for returning the exception information string when a call is made to Ada.Exceptions.Exception_Information. This replaces the default implementation contained in the body of Ada.Exceptions.

    Customizing the Code Called at Program Start-up

    When the Apex policy switch TRACING contains the value "Exception_Traceback", the compiler will emit a call to the routine with the external name "__START_EXCEPTION_TRACEBACK". By default, this label is attached to the routine Apex_Stack_Trace_Example.Exception_Stack_Trace_Enable. However, you can write your own version of this routine, giving it the same external name, "with" it from your main unit, and it will be called instead. Note that you must remove the "with" of Apex_Exception_Stack_Trace_Enable from your application, to avoid getting multiply-defined symbol errors when you perform the link.

    Both the Apex_Stack_Trace_Foundation.Exception_Callout_Install and Apex_Occurrence_Utils.Exception_Information_Callout_Install subprograms are utilized by the code in Apex_Stack_Trace_Example.Exception_Traceback_Enable. The source code for all of the packages in the stack_trace.ss view is shipped with the product and it is intended that you can use this as an example should you wish to customize the implementation or develop other uses for the stack trace code.

    Customizing the Trace Dump Format

    You can customize the format of the trace dump code by implementing your own versions of the code in the Stack_Trace package. However, you may lose the ability to have your file read in by the stacktrace tool. The format of the trace dump is human-readable and compatible with the stacktrace tool.


  • Rational Software Corporation 
    http://www.rational.com
    support@rational.com
    techpubs@rational.com
    Copyright © 1993-2002, Rational Software Corporation. All rights reserved.
    TOC PREV NEXT INDEX DOC LIST MASTER INDEX TECHNOTES APEX TIPS