| ARM Technical Support Knowledge Articles | |
Applies to: RealView Development Suite (RVDS)
It is often important to be able to eliminate any possible (accidental) division by zero errors in code, especially for embedded systems, which may not be able to recover easily. The following shows how to catch division by zero errors in:
and also shows how to identify which line of your source code caused the error.
Integer division by zero throws a signal by default, so you must either:
Integer division is implemented by calling the C library functions __aeabi_idiv and __aeabi_uidiv. These check for divison by zero. If integer division by zero is detected, a branch to __aeabi_idiv0 is made. So to trap the division by zero, all you need to do is place a breakpoint on __aeabi_idiv0.
N.B. As a result of changes from the ABI for the ARM Architecture, __rt_div0 is now called __aeabi_idiv0, __rt_sdiv is now called __aeabi_idiv and __rt_udiv is now called __aeabi_uidiv.
These aeabi functions such as __aeabi_idiv0 are documented in the Run-time ABI for the ARM Architecture.
This can be found at: http://www.arm.com/products/DevTools/ABI.html
N.B. These names could change in a future release.
On entry into __aeabi_idiv0, the Link Register (LR,R14) will contain the address of the instruction _after_ the call to the__aeabi_uidiv division routine in your application code. Therefore, to identify the offending line in your source code, you can simply look up the line of C code in the debugger at that address given by LR.
If you need to examine parameters, etc, and save them for post-mortem debugging, you can trap __aeabi_idiv0. You can intervene in all calls to __aeabi_idiv0 by using the $Super$$ and $Sub$$ mechanism. This is documented in chapter 4 of the Linker and Utilities Guide of your tools (e.g. ADS or RVDS).
$Super$$__aeabi_idiv0 identifies the original unpatched function __aeabi_idiv0(). Use this to call the original function directly.
$Sub$$__aeabi_idiv0 - identifies the new function that will be called instead of the original function __aeabi_idiv0(). Use this to add processing before or after the original function.
Example:
extern void $Super$$__aeabi_idiv0(void);
/* this function will be called instead of the original __aeabi_idiv0() */
void $Sub$$__aeabi_idiv0()
{
// insert code to process a divide by zero
...
// call the original __aeabi_idiv0 function
$Super$$__aeabi_idiv0();
}
An alternative place to intercept divide by zero is in __rt_raise(). As documented in the Compilers and Libraries Guide, the interface to __rt_raise looks like this:
void __rt_raise(int signal, int type)
In the case of __aeabi_idiv0(), it simply calls __rt_raise(2, 2). Thus, in your implementation of __rt_raise, you could check for (signal == 2) && (type == 2) to determine if a divide by zero has occurred.
Floating point division (using the software floating point libraries)
To find the address of the function in your application code which contains the arithmetic operation which resulted in the exception, simply place a breakpoint on the function "_fp_trapveneer", then look at LR.
This is a function within the C library which is called if an exception occurs. On entry into this function, the registers are in the state they were when the exception occurred. So you can then simply view the registers when the breakpoint is hit.
Example:
---- main.c ----
#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);
return 0;
}
---- end main.c ----
Built/executed with:
armsd main.axf
armsd: break @_fp_trapveneer
armsd: go
Breakpoint #1 at PC = 0x0000c77c (_fp_trapveneer + 0)
_fp_trapveneer
+0000 0x0000c77c: 0xe92d5000 .P-. : * stmfd r13!,{r12,r14}
armsd: reg
r0 = 0x00000000 r1 = 0x00000000 r2 = 0x00000000 r3 = 0x00000000
r4 = 0x0000cb14 r5 = 0x00000000 r6 = 0x00000000 r7 = 0x00000000
r8 = 0x00000000 r9 = 0x00000000 r10 = 0x00000000 r11 = 0x00000000
r12 = 0x04000004 r13 = 0x07ffffe8 r14 = 0x000080c8
pc = 0x0000c77c cpsr = %nzcvqIFt_SVC spsr = %nzcvqift_Reserved_00
where r14 = LR = 0x80c8 = the address of the instruction after the "bl _f2d".
If you need to examine parameters, etc, and save them for post-mortem debugging, you can intercept _fp_trapveneer like this:
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
Article last edited on: 2008-09-09 15:47:47
Did you find this article helpful? Yes No
How can we improve this article?