4.7.2. (Software) Floating point division

Floating-point division-by-zero errors in software can be trapped and identified using a combination of intrinsics and C library helper functions.

Trapping division-by-zero errors in code

To trap floating-point division-by-zero errors in your code, use the intrinsic:

__ieee_status(FE_IEEE_MASK_ALL_EXCEPT, FE_IEEE_MASK_DIVBYZERO);

This traps any division-by-zero errors in code, and untraps all other exceptions, as illustrated in Example 4.5.

Example 4.5. Division by zero error

#include <stdio.h>
#include <fenv.h>

int main(void)
{
    float a, b, c;

    // Trap the Invalid Operation exception and untrap all other exceptions:
    __ieee_status(FE_IEEE_MASK_ALL_EXCEPT, FE_IEEE_MASK_DIVBYZERO);

    c = 0;
    a = b / c;
    printf("b / c = %f, ", a); // trap division-by-zero error

    return 0;
}

Identifying division by zero errors in code

The C library helper function _fp_trapveneer is called whenever an exception occurs. On entry into this function, the state of the registers is unchanged from when the exception occurred. Therefore, to find the address of the function in your application code that contains the arithmetic operation which resulted in the exception, simply place a breakpoint on the function _fp_trapveneer and look at LR.

For example, suppose the C code of Example 4.5 is compiled from the command line using the string:

armcc --fpmode ieee_full

When the assembly language code produced by the compiler is disassembled, RealView Debugger produces the output shown in Example 4.6.

Example 4.6. Disassembly of division by zero error

main:
    00008080 E92D4010  PUSH     {r4,lr}
    00008084 E3A01C02  MOV      r1,#0x200
    00008088 E3A00C9F  MOV      r0,#0x9f00
    0000808C EB000F1A  BL       __ieee_status              <0xbcfc>
    00008090 E59F0020  LDR      r0,0x80b8 
    00008094 E3A01000  MOV      r1,#0
    00008098 EB000DEA  BL       _fdiv                      <0xb848>
    0000809C EB000DBD  BL       _f2d                       <0xb798>
    000080A0 E1A02000  MOV      r2,r0
    000080A4 E1A03001  MOV      r3,r1
    000080A8 E28F000C  ADR      r0,{pc}+0x14 ; 0x80bc
    000080AC EB000006  BL       __0printf                  <0x80cc>
    000080B0 E3A00000  MOV      r0,#0
    000080B4 E8BD8010  POP      {r4,pc}
    000080B8 40A00000  <Data>   0x00 0x00 0xA0 '@'
    000080BC 202F2062  <Data>   'b' ' ' '/' ' '
    000080C0 203D2063  <Data>   'c' ' ' '=' ' '
    000080C4 202C6625  <Data>   '%' 'f' ',' ' '
    000080C8 00000000  <Data>   0x00 0x00 0x00 0x00

Placing a breakpoint on _fp_trapveneer and executing the disassembly in the debug monitor produces:

> go
Stopped at 0x0000BF6C due to SW Instruction BreakpointStopped at 0x0000BF6C: TRAPV_S\_fp_trapveneer

Then, inspection of the registers shows:

  r0: 0x40A00000     r1: 0x00000000     r2: 0x00000000     r3: 0x00000000
  r4: 0x0000C1DC     r5: 0x0000C1CC     r6: 0x00000000     r7: 0x00000000
  r8: 0x00000000     r9: 0x00000000    r10: 0x0000C0D4    r11: 0x00000000
 r12: 0x08000004     SP: 0x07FFFFF8     LR: 0x0000809C     PC: 0x0000BF6C
CPSR: nzcvIFtSVC

The address contained in the link register LR is set to 0x809c, the address of the instruction after the instruction BL _fdiv that resulted in the exception.

Examining parameters

To save parameters for post-mortem debugging you must intercept _fp_trapveneer. To intervene in all calls to _fp_trapveneer, use the $Super$$ and $Sub$$ mechanism. For example:

    AREA foo, CODE

IMPORT |$Super$$_fp_trapveneer|
EXPORT |$Sub$$_fp_trapveneer|

    |$Sub$$_fp_trapveneer|

;; Add code to save whatever registers you need here
;; Take care not to corrupt any needed registers

    B |$Super$$_fp_trapveneer|
    END

For more information see:

Copyright © 2002-2007 ARM Limited. All rights reserved.ARM DUI 0205H
Non-Confidential