Skip to content

Instantly share code, notes, and snippets.

@MMI
Created March 20, 2021 15:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MMI/eaae59cb9481f17b63ab0e7fb07ee4fb to your computer and use it in GitHub Desktop.
Save MMI/eaae59cb9481f17b63ab0e7fb07ee4fb to your computer and use it in GitHub Desktop.
Undefined behaviour: ARM gcc cross-compiler may generate illegal instructions in place of known division by zero

Division By Zero Exceptions

Setup

On an embedded project, I recently had to debug a crash where the root cause was a division by zero. The offending code in question reduced to something like function foo() presented here.

To validate that it actually was a division by zero problem, I added the if block that printed "GOTCHA". After this change, I saw that the processor status register (CFSR) had the UNDEFINST bit set instead of the exepected DIVBYZERO bit. What?

Using godbolt we can see the compiler emitting an instruction 0xdeff in main... with no other code (suggesting that the compiler realized that the code will not work and simply stopped -- without warning, I might add).

Play Time

So if we uncomment the if block, we can see that the compiler generates complete code for main(). Instead, it generates the illegal 0xdeff instruction on the printf side of the if block in foo() (having optimized the printf of a constant string to a puts).

And this illegal instruction explains why I was seeing the UNDEFINST bit in the CFSR.

What Does It All Mean

Maybe not very much. Or maybe a bunch. Which is kind of the problem. Other ARM core/compiler combinations (clang, for example) quietly return zero on division by zero (the mechanics may still involve processor traps but the application survives). In my particular application, it caused a crash in device firmware, in code that runs many times per second and where an incorrect calculation wouldn't affect anything. This crash would have been particularly mistifying if we had configured the core to ignore division by zero (which is an option).

// arm-none-eabi-gcc v10.2.1 options: -Os -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb
#include <stdio.h>
#include <inttypes.h>
int foo(int a, int b)
{
uint32_t c;
c = 100;
c *= a;
// if (a + b) {
c /= (a + b);
// } else {
// printf("GOTCHA\n");
// c /= (a + b);
// }
printf("c: %u\n", c);
return 0;
}
int main(void)
{
foo(0, 0);
printf("hello world\n");
}
@mstephenson6
Copy link

This is fun on Apple Silicon just to show the different handling between arm64 and x86_64, compiling with that block un-commented:

mstephenson6@Matts-MacBook-Pro ~ % clang --target=arm64-apple-darwin20.3.0 -o div0-arm64 div0.c 
mstephenson6@Matts-MacBook-Pro ~ % ./div0-arm64 
GOTCHA
c: 0
hello world
mstephenson6@Matts-MacBook-Pro ~ % clang --target=x86_64-apple-darwin20.3.0 -o div0-x86_64 div0.c
mstephenson6@Matts-MacBook-Pro ~ % ./div0-x86_64 
GOTCHA
zsh: floating point exception  ./div0-x86_64

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment