Go to main content
Oracle® Developer Studio 12.5: Numerical Computation Guide

Exit Print View

Updated: June 2016
 
 

4.4 Locating an Exception

One way to locate where an exception occurs is to test the exception flags at various points throughout a program. However, isolating an exception precisely by this approach can require many tests and carry a significant overhead.

An easier way to determine where an exception occurs is to enable its trap. When an exception whose trap is enabled occurs, the operating system notifies the program by sending a SIGFPE signal. See the signal(5) manual page. Thus, by enabling trapping for an exception, you can determine where the exception occurs either by running under a debugger and stopping on receipt of a SIGFPE signal or by establishing a SIGFPE handler that prints the address of the instruction where the exception occurred. Note that trapping must be enabled for an exception to generate a SIGFPE signal. When trapping is disabled and an exception occurs, the corresponding flag is set and execution continues with the default result specified in Table 31, but no signal is delivered.

4.4.1 Using the Debugger to Locate an Exception

This section gives examples showing how to use dbx to investigate the cause of a floating-point exception and locate the instruction that raised it. Recall that in order to use the source-level debugging features of dbx, programs should be compiled with the –g flag. Refer to the Oracle Developer Studio 12.5: Debugging a Program with dbx for more information.

Consider the following C program:

#include <stdio.h>
#include <math.h>

double sqrtm1(double x)
{
   return sqrt(x) - 1.0;
}

int main(void)
{
   double x, y;

   x = -4.2;
   y = sqrtm1(x);
   printf("%g  %g\n", x, y);
   return 0;
}

Compiling and running this program produces:

-4.2  NaN

The appearance of a NaN in the output suggests that an invalid operation exception might have occurred. To determine whether this is the case, you can recompile with the –ftrap option to enable trapping on invalid operations and use dbx to run the program and stop when a SIGFPE signal is delivered. Alternatively, you can use dbx without recompiling the program by linking with a startup routine that enables the invalid operation trap or by manually enabling the trap.

4.4.1.1 Using dbx to Locate the Instruction Causing an Exception

The simplest way to locate the code that causes a floating-point exception is to recompile with the –g and –ftrap flags and then use dbx to track down the location where the exception occurs. First, recompile the program as follows:

example% cc -g -ftrap=invalid ex.c -lm

Compiling with –g allows you to use the source-level debugging features of dbx. Specifying –ftrap=invalid causes the program to run with trapping enabled for invalid operation exceptions. Next, invoke dbx, issue the catch fpe command to stop when a SIGFPE is issued, and run the program. On SPARC-based systems, the result resembles this:

example% dbx a.out
Reading a.out
Reading ld.so.1
Reading libm.so.2
Reading libc.so.1
(dbx) catch fpe
(dbx) run
Running: a.out 
(process id 2773)
signal FPE (invalid floating point operation) in __sqrt at 0x7fa9839c
0x7fa9839c: __sqrt+0x005c:      srlx     %o1, 63, %l5
Current function is sqrtm1
    5   return sqrt(x) - 1.0;
(dbx) print x
x = -4.2
(dbx)

The output shows that the exception occurred in the sqrtm1 function as a result of attempting to take the square root of a negative number.

You can also use dbx to identify the cause of an exception in code that has not been compiled with –g, such as a library routine. In this case, dbx will not be able to give the source file and line number, but it can show the instruction that raised the exception. Again, the first step is to recompile the main program with –ftrap:

example% cc -ftrap=invalid ex.c -lm

Now invoke dbx, use the catch fpe command, and run the program. When an invalid operation exception occurs, dbx stops at an instruction following the one that caused the exception. To find the instruction that caused the exception, disassemble several instructions and look for the last floating-point instruction prior to the instruction at which dbx has stopped. On SPARC-based systems, the result might resemble the following transcript.

example% dbx a.out
Reading a.out
Reading ld.so.1
Reading libm.so.2
Reading libc.so.1
(dbx) catch fpe
(dbx) run
Running: a.out 
(process id 2931)
signal FPE (invalid floating point operation) in __sqrt at 0x7fa9839c
0x7fa9839c: __sqrt+0x005c:      srlx     %o1, 63, %l5
(dbx) dis __sqrt+0x50/4
dbx: warning: unknown language, 'c' assumed
0x7fa98390: __sqrt+0x0050:      neg      %o4, %o1
0x7fa98394: __sqrt+0x0054:      srlx     %o2, 63, %l6
0x7fa98398: __sqrt+0x0058:      fsqrtd   %f0, %f2
0x7fa9839c: __sqrt+0x005c:      srlx     %o1, 63, %l5
(dbx) print $f0f1 
$f0f1 = -4.2
(dbx) print $f2f3
$f2f3 = -NaN.0
(dbx) 

The output shows that the exception was caused by an fsqrtd instruction. Examining the source register shows that the exception was a result of attempting to take the square root of a negative number.

On x86-based systems, because instructions do not have a fixed length, finding the correct address from which to disassemble the code might involve some trial and error. In this example, the exception occurs close to the beginning of a function, so we can disassemble from there. Note that this output assumes the program has been compiled with the –xlibmil flag. The following output might be a typical result.

example% dbx a.out
Reading a.out
Reading ld.so.1
Reading libc.so.1
(dbx) catch fpe
(dbx) run
Running: a.out 
(process id 18566)
signal FPE (invalid floating point operation) in sqrtm1 at 0x80509ab
0x080509ab: sqrtm1+0x001b:      fstpl    0xffffffe0(%ebp)
(dbx) dis sqrtm1+0x16/5
dbx: warning: unknown language, 'c' assumed
0x080509a6: sqrtm1+0x0016:      fsqrt    
0x080509a8: sqrtm1+0x0018:      addl     $0x00000008,%esp
0x080509ab: sqrtm1+0x001b:      fstpl    0xffffffe0(%ebp)
0x080509ae: sqrtm1+0x001e:      fwait    
0x080509af: sqrtm1+0x001f:      movsd    0xffffffe0(%ebp),%xmm0
(dbx) print $st0
$st0 = -4.20000000000000017763568394002504647e+00
(dbx) 

The output reveals that the exception was caused by a fsqrt instruction. Examination of the floating-point registers reveals that the exception was a result of attempting to take the square root of a negative number.

4.4.1.2 Enabling Traps Without Recompilation

In the preceding examples, trapping on invalid operation exceptions was enabled by recompiling the main subprogram with the –ftrap flag. In some cases, recompiling the main program might not be possible, so you might need to resort to other means to enable trapping. There are several ways to do this.

When you are using dbx, you can enable traps manually by directly modifying the floating-point status register. This can be somewhat tricky because the operating system does not enable the floating-point unit until the first time it is used within a program, at which point the floating-point state is initialized with all traps disabled. Thus, you cannot manually enable trapping until after the program has executed at least one floating-point instruction. In our example, the floating-point unit has already been accessed by the time the sqrtm1 function is called, so we can set a breakpoint on entry to that function, enable trapping on invalid operation exceptions, instruct dbx to stop on the receipt of a SIGFPE signal, and continue execution. On SPARC-based systems, the steps are as follows. Note the use of the assign command to modify the %fsr to enable trapping on invalid operation exceptions:

example% dbx a.out
Reading a.out
... etc.
(dbx) stop in sqrtm1
dbx: warning: 'sqrtm1' has no debugger info -- will trigger on first instruction
(2) stop in sqrtm1
(dbx) run
Running: a.out 
(process id 23086)
stopped in sqrtm1 at 0x106d8
0x000106d8: sqrtm1       :      save    %sp, -0x70, %sp
(dbx) assign $fsr=0x08000000
dbx: warning: unknown language, 'c' assumed
(dbx) catch fpe
(dbx) cont
signal FPE (invalid floating point operation) in __sqrt at 0xff36b3c4
0xff36b3c4: __sqrt+0x003c:      be      __sqrt+0x98
(dbx) 

On x86-based systems, the same process might look like this:

example% dbx a.out
Reading a.out
... etc.
(dbx) stop in sqrtm1 
dbx: warning: 'sqrtm1' has no debugger info -- will trigger on first instruction
(2) stop in sqrtm1
(dbx) run    
Running: a.out 
(process id 25055)
stopped in sqrtm1 at 0x80506b0
0x080506b0: sqrtm1     :        pushl  %ebp
(dbx) assign $fctrl=0x137e
dbx: warning: unknown language, 'c' assumed
(dbx) catch fpe
(dbx) cont 
signal FPE (invalid floating point operation) in sqrtm1 at 0x8050696
0x08050696: sqrtm1+0x0016:      fstpl  -16(%ebp)
(dbx)

In the example above, the assign command unmasks (that is, enables trapping on) the invalid operation exception in the floating-point control word. If a program uses SSE2 instructions, you must unmask exceptions in the MXCSR register to enable trapping on exceptions raised by those instructions.

You can also enable trapping without recompiling the main program or using dbx by establishing an initialization routine that enables traps. This might be useful, for example, if you want to abort the program when an exception occurs without running under a debugger. There are two ways to establish such a routine.

If the object files and libraries that comprise the program are available, you can enable trapping by relinking the program with an appropriate initialization routine. First, create a C source file similar to the following:

#include <ieeefp.h>
 
#pragma init (trapinvalid)
 
void trapinvalid()
{
     /* FP_X_INV et al are defined in ieeefp.h */
     fpsetmask(FP_X_INV);
}

Compile this file to create an object file and link the original program with this object file:

example% cc -c init.c
example% cc ex.o init.o -lm
example% a.out
Arithmetic Exception

If relinking is not possible but the program has been dynamically linked, you can enable trapping by using the shared object preloading facility of the runtime linker. To do this on SPARC-based systems, create the same C source file as above, but compile as follows:

example% cc -Kpic -G -ztext init.c -o init.so -lc

To enable trapping, add the path name of the init.so object to the list of preloaded shared objects specified by the environment variable LD_PRELOAD:

example% env LD_PRELOAD=./init.so a.out
Arithmetic Exception

See the Oracle Solaris 11.3 Linkers and Libraries Guide for more information about creating and preloading shared objects.

In principle, you can change the way any floating-point control modes are initialized by preloading a shared object as described above. However, initialization routines in shared objects, whether preloaded or explicitly linked, are executed by the runtime linker before it passes control to the startup code that is part of the main executable. The startup code then establishes any nondefault modes selected via the –ftrap, –fround, –fns (SPARC), or –fprecision (x86) compiler flags; executes any initialization routines that are part of the main executable, including those that are statically linked; and finally passes control to the main program. Therefore, on SPARC, remember the following:

  • Any floating-point control modes established by initialization routines in shared objects, such as the traps enabled in the example above, will remain in effect throughout the execution of the program unless they are overridden.

  • Any nondefault modes selected via the compiler flags will override modes established by initialization routines in shared objects (but default modes selected via compiler flags will not override previously established modes).

  • Any modes established either by initialization routines that are part of the main executable or by the main program itself will override both.

On x86-based systems, the situation is slightly more complicated. In general, the startup code automatically supplied by the compiler resets all floating-point modes to the default by calling the __fpstart routine (found in the standard C library, libc) before establishing any nondefault modes selected by the –fround, –ftrap, or –fprecision flags and passing control to the main program. As a consequence, in order to enable trapping or change any other default floating-point mode on x86-based systems by preloading a shared object with an initialization routine, you must override the __fpstart routine so that it does not reset the default floating-point modes. The substitute __fpstart routine should still perform the rest of the initialization functions that the standard routine does, however. The following code shows one way to do this. This code assumes that the host platform is running the Oracle Solaris 10 OS or later releases.

#include <ieeefp.h>
#include <sys/sysi86.h>
 
#pragma init (trapinvalid)
 
void trapinvalid()
{
     /* FP_X_INV et al are defined in ieeefp.h */
     fpsetmask(FP_X_INV);
}
 
extern int  __fltrounds(), __flt_rounds;
extern int  _fp_hw, _sse_hw;
 
void __fpstart()
{
    /* perform the same floating point initializations as
       the standard __fpstart() function but leave all
       floating point modes as is */
    __flt_rounds = __fltrounds();
    (void) sysi86(SI86FPHW, &_fp_hw);
 
    /* set the following variable to 0 instead if the host
       platform does not support SSE2 instructions */
    _sse_hw = 1;
}

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 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 33. The tokens shown are defined in sys/machsig.h.

Table 33  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 1 shows such a handler.

Example 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, the test program loops because the PC never gets past the loop instruction. For Oracle Developer Studio12.5 , 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 five 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.

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 34. (These values are defined in fenv.h.)

Table 34  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 34 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 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 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.

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