Oracle® Solaris Studio 12.4: Numerical Computation Guide

Exit Print View

Updated: January 2015
 
 

4.4.3 Using libm Exception Handling Extensions to Locate an Exception

C/C++ programs can use the exception handling extensions to the C99 floating-point environment functions in libm to locate exceptions in several ways. These extensions include functions that can establish handlers and simultaneously enable traps, just as ieee_handler does, but they provide more flexibility. They also support logging of retrospective diagnostic messages regarding floating-point exceptions to a selected file.

4.4.3.1 fex_set_handling(3m)

The fex_set_handling function allows you to select one of several options, or modes, for handling each type of floating-point exception. The syntax of a call to fex_set_handling is:

ret = fex_set_handling(ex, mode, handler);

The ex argument specifies the set of exceptions to which the call applies. It must be a bitwise “or” of the values listed in the first column of Table 4–4. (These values are defined in fenv.h.)

Table 4-4  Exception Codes for fex_set_handling  
Value
Exception
FEX_INEXACT
inexact result
FEX_UNDERFLOW
underflow
FEX_OVERFLOW
overflow
FEX_DIVBYZERO
division by zero
FEX_INV_ZDZ
0/0 invalid operation
FEX_INV_IDI
infinity/infinity invalid operation
FEX_INV_ISI
infinity-infinity invalid operation
FEX_INV_ZMI
0*infinity invalid operation
FEX_INV_SQRT
square root of negative number
FEX_INV_SNAN
operation on signaling NaN
FEX_INV_INT
invalid integer conversion
FEX_INV_CMP
invalid unordered comparison

For convenience, fenv.h also defines the following values: FEX_NONE (no exceptions), FEX_INVALID (all invalid operation exceptions), FEX_COMMON (overflow, division by zero, and all invalid operations), and FEX_ALL (all exceptions).

The mode argument specifies the exception handling mode to be established for the indicated exceptions. There are five possible modes:

  • FEX_NONSTOP mode provides the IEEE 754 default nonstop behavior. This is equivalent to leaving the exception's trap disabled. Note that unlike ieee_handler, fex_set_handling allows you to establish nondefault handling for certain types of invalid operation exceptions and retain IEEE default handling for the rest.

  • FEX_NOHANDLER mode is equivalent to enabling the exception's trap without providing a handler. When an exception occurs, the system transfers control to a previously installed SIGFPE handler, if present, or aborts.

  • FEX_ABORT mode causes the program to call abort(3c) when the exception occurs.

  • FEX_SIGNAL installs the handling function specified by the handler argument for the indicated exceptions. When any of these exceptions occurs, the handler is invoked with the same arguments as if it had been installed by ieee_handler.

  • FEX_CUSTOM installs the handling function specified by handler for the indicated exceptions. Unlike FEX_SIGNAL mode, when an exception occurs, the handler is invoked with a simplified argument list. The arguments consist of an integer whose value is one of the values listed in Table 4–4 and a pointer to a structure that records additional information about the operation that caused the exception. The contents of this structure are described in the next section and in the fex_set_handling(3m) manual page.

Note that the handler parameter is ignored if the specified mode is FEX_NONSTOP, FEX_NOHANDLER, or FEX_ABORT. fex_set_handling returns a nonzero value if the specified mode is established for the indicated exceptions, and returns zero otherwise. In the following examples, the return value is ignored.

The following examples suggest ways to use fex_set_handling to locate certain types of exceptions. To abort on a 0/0 exception, use the following:

fex_set_handling(FEX_INV_ZDZ, FEX_ABORT, NULL);

To install a SIGFPE handler for overflow and division by zero, use the following:

fex_set_handling(FEX_OVERFLOW | FEX_DIVBYZERO, FEX_SIGNAL,
    handler);

In the previous example, the handler function could print the diagnostic information supplied via the sip parameter to a SIGFPE handler, as shown in the previous subsection. By contrast, the following example prints the information about the exception that is supplied to a handler installed in FEX_CUSTOM mode. See the fex_set_handling(3m) manual page for more information.

Example 4-2  Printing Information Supplied to Handler Installed in FEX_CUSTOM Mode
#include <fenv.h>

void handler(int ex, fex_info_t *info)
{
    switch (ex) {
    case FEX_OVERFLOW:
        printf("Overflow in ");
        break;
    case FEX_DIVBYZERO:
        printf("Division by zero in ");
        break;

    default:
        printf("Invalid operation in ");
    }
    switch (info->op) {
    case fex_add:
        printf("floating point add\n");
        break;
    case fex_sub:
        printf("floating point subtract\n");
        break;
    case fex_mul:
        printf("floating point multiply\n");
        break;
    case fex_div:
        printf("floating point divide\n");
        break;
    case fex_sqrt:
        printf("floating point square root\n");
        break;
    case fex_cnvt:
        printf("floating point conversion\n");
        break;
    case fex_cmp:
        printf("floating point compare\n");
        break;
    default:
        printf("unknown operation\n");
    }
    switch (info->op1.type) {
    case fex_int:
        printf("operand 1: %d\n", info->op1.val.i);
        break;
    case fex_llong:
        printf("operand 1: %lld\n", info->op1.val.l);
        break;
    case fex_float:
        printf("operand 1: %g\n", info->op1.val.f);
        break;
    case fex_double:
        printf("operand 1: %g\n", info->op1.val.d);
        break;

    case fex_ldouble:
        printf("operand 1: %Lg\n", info->op1.val.q);
        break;
    }
    switch (info->op2.type) {
    case fex_int:
        printf("operand 2: %d\n", info->op2.val.i);
        break;
    case fex_llong:
        printf("operand 2: %lld\n", info->op2.val.l);
        break;
    case fex_float:
        printf("operand 2: %g\n", info->op2.val.f);
        break;
    case fex_double:
        printf("operand 2: %g\n", info->op2.val.d);
        break;
    case fex_ldouble:
        printf("operand 2: %Lg\n", info->op2.val.q);
        break;
    }
}
...
fex_set_handling(FEX_COMMON, FEX_CUSTOM, handler);

The handler in the preceding example reports the type of exception that occurred, the type of operation that caused it, and the operands. It does not indicate where the exception occurred. To find out where the exception occurred, you can use retrospective diagnostics.

4.4.3.2 Retrospective Diagnostics

Another way to locate an exception using the libm exception handling extensions is to enable logging of retrospective diagnostic messages regarding floating-point exceptions. When you enable logging of retrospective diagnostics, the system records information about certain exceptions. This information includes the type of exception, the address of the instruction that caused it, the manner in which it will be handled, and a stack trace similar to that produced by a debugger. The stack trace recorded with a retrospective diagnostic message contains only instruction addresses and function names; for additional debugging information such as line numbers, source file names, and argument values, you must use a debugger.

The log of retrospective diagnostics does not contain information about every single exception that occurs; if it did, a typical log would be huge, and it would be impossible to isolate unusual exceptions. Instead, the logging mechanism eliminates redundant messages. A message is considered redundant under either of the following two circumstances:

  • The same exception has been previously logged at the same location, i.e., with the same instruction address and stack trace

  • FEX_NONSTOP mode is in effect for the exception and its flag has been previously raised.

In particular, in most programs, only the first occurrence of each type of exception will be logged. When FEX_NONSTOP handling mode is in effect for an exception, clearing its flag via any of the C99 floating-point environment functions allows the next occurrence of that exception to be logged, provided it does not occur at a location at which it was previously logged.

To enable logging, use the fex_set_log function to specify the file to which messages should be delivered. For example, to log messages to the standard error file, use:

fex_set_log(stderr);

The following code example combines logging of retrospective diagnostics with the shared object preloading facility illustrated in the previous section. By creating the following C source file, compiling it to a shared object, preloading the shared object by supplying its path name in the LD_PRELOAD environment variable, and specifying the names of one or more exceptions (separated by commas) in the FTRAP environment variable, you can simultaneously abort the program on the specified exceptions and obtain retrospective diagnostic output showing where each exception occurs.

Example 4-3  Combined Logging of Retrospective Diagnostics With Shared Object Preloading
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fenv.h>

static struct ftrap_string {
    const char  *name;
    int         value;
} ftrap_table[] = {
    { "inexact", FEX_INEXACT },
    { "division", FEX_DIVBYZERO },

    { "underflow", FEX_UNDERFLOW },
    { "overflow", FEX_OVERFLOW },
    { "invalid", FEX_INVALID },
    { NULL, 0 }
};

#pragma init (set_ftrap)
void set_ftrap()
{
    struct ftrap_string  *f;
    char                 *s, *s0;
    int                  ex = 0;

    if ((s = getenv("FTRAP")) == NULL)
        return;

    if ((s0 = strtok(s, ",")) == NULL)
        return;

    do {
        for (f = ftrap_table[0]; f->name != NULL; f++) {
            if (!strcmp(s0, f->name))
                ex |= f->value;
        }
    } while ((s0 = strtok(NULL, ",")) != NULL);

    fex_set_handling(ex, FEX_ABORT, NULL);
    fex_set_log(stderr);
}

Using the preceding code with the example program given at the beginning of this section produces the following results on SPARC-based systems:

env FTRAP=invalid LD_PRELOAD=./init.so a.out
Floating point invalid operation (sqrt) at 0x7fa98398 __sqrt, abort
  0x7fa9839c  __sqrt
  0x00010880  sqrtm1
  0x000108ec  main
Abort

The preceding output shows that the invalid operation exception was raised as a result of a square root operation in the routine sqrtm1.

As noted above, to enable trapping from an initialization routine in a shared object on x86 platforms, you must override the standard __fpstart routine.

Appendix A, Examples gives more examples showing typical log outputs. For general information, see the fex_set_log(3m) man page.