Oracle® Solaris Studio 12.4: Numerical Computation Guide

Exit Print View

Updated: January 2015
 
 

4.4.2 Using a Signal Handler to Locate an Exception

The previous section presented several methods for enabling trapping at the outset of a program in order to locate the first occurrence of an exception. In contrast, you can isolate any particular occurrence of an exception by enabling trapping within the program itself. If you enable trapping but do not install a SIGFPE handler, the program will abort on the next occurrence of the trapped exception. Alternatively, if you install a SIGFPE handler, the next occurrence of the trapped exception will cause the system to transfer control to the handler, which can then print diagnostic information, such as the address of the instruction where the exception occurred, and either abort or resume execution. In order to resume execution with any prospect for a meaningful outcome, the handler might need to supply a result for the exceptional operation as described in the next section.

You can use ieee_handler to simultaneously enable trapping on any of the five IEEE floating-point exceptions and either request that the program abort when the specified exception occurs or establish a SIGFPE handler. You can also install a SIGFPE handler using one of the lower-level functions sigfpe(3), signal(3c), or sigaction(2); however, these functions do not enable trapping as ieee_handler does. Remember that a floating-point exception triggers a SIGFPE signal only when its trap is enabled.

4.4.2.1 ieee_handler (3m)

The syntax of a call to ieee_handler is:

i = ieee_handler(action, exception, handler)

The two input parameters action and exception are strings. The third input parameter, handler, is of type sigfpe_handler_type, which is defined in floatingpoint.h.

The three input parameters can take the following values:

Input Parameter
C or C++ Type
Possible Value
action
char *
get, set, clear
exception
char *
invalid, division, overflow,
underflow, inexact,
all, common
handler
sigfpe_handler_type
user-defined routine
SIGFPE_DEFAULT
SIGFPE_IGNORE
SIGFPE_ABORT

When the requested action is "set", ieee_handler establishes the handling function specified by handler for the exceptions named by exception. The handling function can be SIGFPE_DEFAULT or SIGFPE_IGNORE, both of which select the default IEEE behavior, SIGFPE_ABORT, which causes the program to abort on the occurrence of any of the named exceptions, or the address of a user-supplied subroutine, which causes that subroutine to be invoked (with the parameters described in the sigaction(2) manual page for a signal handler installed with the SA_SIGINFO flag set) when any of the named exceptions occurs. If the handler is SIGFPE_DEFAULT or SIGFPE_IGNORE, ieee_handler also disables trapping on the specified exceptions; for any other handler, ieee_handler enables trapping.

On x86 platforms, the floating-point hardware traps whenever an exception's trap is enabled and its corresponding flag is raised. Therefore, to avoid spurious traps, a program should clear the flag for each specified exception before calling ieee_handler to enable trapping.

When the requested action is "clear", ieee_handler revokes whatever handling function is currently installed for the specified exception and disables its trap. This is the same as "set"ting SIGFPE_DEFAULT. The third parameter is ignored when action is "clear".

For both the "set" and "clear" actions, ieee_handler returns 0 if the requested action is available and a nonzero value otherwise.

When the requested action is "get", ieee_handler returns the address of the handler currently installed for the specified exception or SIGFPE_DEFAULT, if no handler is installed.

The following examples show a few code fragments illustrating the use of ieee_handler. This C code causes the program to abort on division by zero:

#include <sunmath.h> 
/* uncomment the following line on x86 systems */
    /* ieee_flags("clear", "exception", "division", NULL); */
    if (ieee_handler("set", "division", SIGFPE_ABORT) != 0)
        printf("ieee trapping not supported here \n");

The following is the equivalent Fortran code:

#include <floatingpoint.h> 
c uncomment the following line on x86 systems
c     ieee_flags('clear', 'exception', 'division', %val(0))
      i = ieee_handler('set', 'division', SIGFPE_ABORT) 
      if(i.ne.0) print *,'ieee trapping not supported here'

This C fragment restores IEEE default exception handling for all exceptions:

#include <sunmath.h> 
    if (ieee_handler("clear", "all", 0) != 0) 
        printf("could not clear exception handlers\n");

The following is the same action in Fortran:

      i = ieee_handler('clear', 'all', 0) 
      if (i.ne.0) print *, 'could not clear exception handlers'

4.4.2.2 Reporting an Exception From a Signal Handler

When a SIGFPE handler installed via ieee_handler is invoked, the operating system provides additional information indicating the type of exception that occurred, the address of the instruction that caused it, and the contents of the machine's integer and floating-point registers. The handler can examine this information and print a message identifying the exception and the location at which it occurred.

To access the information supplied by the system, declare the handler as follows. The remainder of this chapter presents sample code in C; see Appendix A, Examples for examples of SIGFPE handlers in Fortran.

#include <siginfo.h>
#include <ucontext.h>
 
void handler(int sig, siginfo_t *sip, ucontext_t *uap)
{
    ...
}

When the handler is invoked, the sig parameter contains the number of the signal that was sent. Signal numbers are defined in sys/signal.h; the SIGFPE signal number is 8.

The sip parameter points to a structure that records additional information about the signal. For a SIGFPE signal, the relevant members of this structure are sip‐>si_code and sip->si_addr (see /usr/include/sys/siginfo.h). The significance of these members depends on the system and on what event triggered the SIGFPE signal.

The sip->si_code member is one of the SIGFPE signal types listed in Table 4–3. The tokens shown are defined in sys/machsig.h.

Table 4-3  Types for Arithmetic Exceptions
SIGFPE Type
IEEE Type
FPE_INTDIV
n/a
FPE_INTOVF
n/a
FPE_FLTRES
inexact
FPE_FLTDIV
division
FPE_FLTUND
underflow
FPE_FLTINV
invalid
FPE_FLTOVF
overflow

As the previous table shows, each type of IEEE floating-point exception has a corresponding SIGFPE signal type. Integer division by zero (FPE_INTDIV) and integer overflow (FPE_INTOVF) are also included among the SIGFPE types, but because they are not IEEE floating-point exceptions you cannot install handlers for them via ieee_handler. You can install handlers for these SIGFPE types via sigfpe(3); note, though, that integer overflow is ignored by default on all SPARC and x86 platforms. Special instructions can cause the delivery of a SIGFPE signal of type FPE_INTOVF, but Sun compilers do not generate these instructions.

For a SIGFPE signal corresponding to an IEEE floating-point exception, the sip‐>si_code member indicates which exception occurred. On x86-based systems, it actually indicates the highest priority unmasked exception whose flag is raised. This is normally the same as the exception that last occurred. The sip->si_addr member holds the address of the instruction that caused the exception on SPARC-based systems, and on x86-based systems it holds the address of the instruction at which the trap was taken, usually the next floating-point instruction following the one that caused the exception.

Finally, the uap parameter points to a structure that records the state of the system at the time the trap was taken. The contents of this structure are system-dependent; see /usr/include/sys/siginfo.h for definitions of some of its members.

Using the information provided by the operating system, we can write a SIGFPE handler that reports the type of exception that occurred and the address of the instruction that caused it. Example 4–1 shows such a handler.

Example 4-1  SIGFPE Handler
#include <stdio.h>
#include <sys/ieeefp.h>
#include <sunmath.h>
#include <siginfo.h>
#include <ucontext.h>

void handler(int sig, siginfo_t *sip, ucontext_t *uap)
{
    unsigned    code, addr;

    code = sip->si_code;
    addr = (unsigned) sip->si_addr;
    fprintf(stderr, "fp exception %x at address %x\n", code,
        addr);
}

int main()
{
    double  x;

    /* trap on common floating point exceptions */
    if (ieee_handler("set", "common", handler) != 0)
        printf("Did not set exception handler\n");
    /* cause an underflow exception (will not be reported) */
    x = min_normal();
    printf("min_normal = %g\n", x);
    x = x / 13.0;
    printf("min_normal / 13.0 = %g\n", x);

    /* cause an overflow exception (will be reported) */
    x = max_normal();
    printf("max_normal = %g\n", x);
    x = x * x;
    printf("max_normal * max_normal = %g\n", x);
    ieee_retrospective(stderr);
    return 0;
}

On SPARC systems, the output from this program resembles the following:

min_normal = 2.22507e-308
min_normal / 13.0 = 1.7116e-309
max_normal = 1.79769e+308
fp exception 4 at address 10d0c
max_normal * max_normal = 1.79769e+308
Note: IEEE floating-point exception flags raised:
    Inexact;  Underflow; 
IEEE floating-point exception traps enabled:
    overflow; division by zero; invalid operation; 
See the Numerical Computation Guide, ieee_flags(3M), ieee_handler(3M)

On x86 platforms, the operating system saves a copy of the accrued exception flags and then clears them before invoking a SIGFPE handler. Unless the handler takes steps to preserve them, the accrued flags are lost once the handler returns. Thus, the output from the preceding program does not indicate that an underflow exception was raised, when compiled with –xarch=386:

min_normal = 2.22507e-308
min_normal / 13.0 = 1.7116e-309
max_normal = 1.79769e+308
fp exception 4 at address 8048fe6
max_normal * max_normal = 1.79769e+308
 Note: IEEE floating-point exception traps enabled: 
    overflow;  division by zero;  invalid operation; 
 See the Numerical Computation Guide, ieee_handler(3M)

But by default, or when compiled with –xarch=sse2, tjos test program loops because the PC never gets past the loop instruction. For Oracle Solaris Studio 12.4 , it would suffice to add a line of code to increment the PC:

uap → UC_mcontext.gregs[REG_PC= +=5;

The above code is only covered for –xarch=sse2 and only if the SSE2 instruction happens to be 5 bytes long. A completely general SSE2 solution involves decoding the optimized code to find the beginning of the next instruction. Use fex_set_handling instead.

In most cases, the instruction that causes the exception does not deliver the IEEE default result when trapping is enabled: in the preceding outputs, the value reported for max_normal * max_normal is not the default result for an operation that overflows (i.e., a correctly signed infinity). In general, a SIGFPE handler must supply a result for an operation that causes a trapped exception in order to continue the computation with meaningful values. See Handling Exceptions for one way to do this.