written by Shawn Nock on 2016-09-15

In the last post, I described a comprehensive blink example for the nrf51 zero footprint runtime system. Here's the rub, though: it doesn't work.

Here's a backtrace of the failure:

#0  0x000004a2 in HardFault_Handler ()
#1  <signal handler called>
#2  __floatsisf () at ../../../src/libgcc/config/arm/ieee754-sf.S:302
#3  0x00000220 in util.ms_to_ticks (milliseconds=1000) at /home/nock/devel/Nrf51_Led_Demo_Ada/src/util.adb:19
#4  0x000003d8 in util.delay_ms (milliseconds=1000) at /home/nock/devel/Nrf51_Led_Demo_Ada/src/util.adb:73
#5  0x00000144 in main () at /home/nock/devel/Nrf51_Led_Demo_Ada/src/main.adb:15

So we end up in the HardFault_Handler, this means we've asked the microcontroller to do something it cannot do. We can disassemble the program at the point it stopped to see what's happening:

(gdb) up
#1  <signal handler called>
(gdb) up
#2  __floatsisf () at ../../../src/libgcc/config/arm/ieee754-sf.S:302
302     ../../../src/libgcc/config/arm/ieee754-sf.S: No such file or directory.
(gdb) disassemble 
Dump of assembler code for function __floatsisf:
=> 0x000014b0 <+0>:     ands.w  r3, r0, #2147483648     ; 0x80000000
   0x000014b4 <+4>:     it      mi
   0x000014b6 <+6>:     negmi   r0, r0
   0x000014b8 <+8>:     movs.w  r12, r0
   0x000014bc <+12>:    it      eq
   0x000014be <+14>:    bxeq    lr
   0x000014c0 <+16>:    orr.w   r3, r3, #1258291200     ; 0x4b000000
   0x000014c4 <+20>:    mov     r1, r0
   0x000014c6 <+22>:    mov.w   r0, #0
   0x000014ca <+26>:    b.n     0x1506 <__floatdisf+42>

This didn't immediately speak to me. ARM has a few addressing modes and I am not an expert. What really clarified the problem for me was opening the binary with Rardare2:

[nock@black Nrf51_Led_Demo_Ada]$ r2 obj/main
Warning: Cannot initialize dynamic strings
 -- Mind that the 'g' in radare is silent
[0x00000460]> aa
[aav: using from to 0x0 0x26288v)th sym. and entry0 (aa)
Using vmin 0x1a50 and vmax 0x20000828
aav: using from to 0x0 0x26288
Using vmin 0x1a50 and vmax 0x20000828
[x] Analyze value pointers (aav)
[0x00000460]> s 0x14b0
[0x000014b0]> pdf
            ;-- __floatsisf:
/ (fcn) sym.__aeabi_i2f 108
|           ; CALL XREF from 0x0000021c (sym.util__ms_to_ticks)
|           ; CALL XREF from 0x00000264 (sym.util__ms_to_ticks)
|           ; CALL XREF from 0x0000023c (sym.util__ms_to_ticks)
|           0x000014b0      10f00043       ands r3, r0, 0x80000000
|       ,=< 0x000014b4      48bf           it mi
|       `-> 0x000014b6      4042           rsbs r0, r0, 0

Columns in the output are: Address, numeric instruction, instruction nemonic (assembly lang). With this view something immediately jumps out at me; the failing instruction (at address 0x000014b0) is 32-bits long. The nrf51822 is a Cortex-m0 part that implements the arm-v6m instruction set. arm-v6m is a Thumb-only instruction set meaning that it only understands 16-bit long Thumb instructions (rather than 32-bit long ARM instructions). 1

So we've asked the processor to run an invalid instruction! At this point I attempted to look for errors in the compiler flags (which live in the nrf51-zfp RTS I ported), but the flags are correct. In fact, there are no ARM instructions in the code generated from my project files, it's only in the software floating-point emulation code.

A little research showed that this code lives in libgcc.a and it's built for all the supported platforms when the compiler is compiled. The heart of the problem is that GNAT 2016 doesn't ship with an arm-v6m compatible libgcc.a., even though it will happily generate arm-v6m code. Multilib support in gcc was non-intuitive to me; eventually I was able to generate the following patch (gist link):

--- gcc-4.9-gpl-2016-src/gcc/config/arm/t-arm-elf    2016-04-25 10:34:19.000000000 -0400
+++ gcc-4.9-gpl-2016-src/gcc/config/arm/t-arm-elf.new    2016-07-04 16:27:29.056019131 -0400
@@ -21,6 +21,15 @@

+MULTILIB_OPTIONS    += mcpu=cortex-m0
+MULTILIB_EXCEPTIONS  += marm/mcpu=cortex-m0
+MULTILIB_EXCEPTIONS  += mcpu=cortex-m0
+MULTILIB_EXCEPTIONS += mcpu=cortex-m0/mfloat-abi=hard
+MULTILIB_EXCEPTIONS += marm/mcpu=cortex-m0/mfloat*
+MULTILIB_EXCEPTIONS += mthumb/mcpu=cortex-m0/mfloat-abi=hard
 #MULTILIB_OPTIONS     += mcpu=fa526/mcpu=fa626/mcpu=fa606te/mcpu=fa626te/mcpu=fmp626/mcpu=fa726te
 #MULTILIB_DIRNAMES    += fa526 fa626 fa606te fa626te fmp626 fa726te
#MULTILIB_EXCEPTIONS += *mthumb*/*mcpu=fa526 *mthumb*/*mcpu=fa626

When building GNAT 2016 from source, this patch will produce a libgcc.a that is compatible with arm-v6m. I wasn't satisfied with the GNAT I built myself (some issues getting gprbuild, et. al. working), but happily if you copy the generated libgcc.a to the $PROJECT_DIR/obj directory; the linker finds it before the borked GNAT one and you can use stock GNAT 2016 without further issues.

I submitted my patch to Adacore via the #ada channel on Freenode. It sounds like it'll be included in GNAT 2017. Until then, I am distributing a compatible libgcc.a with my projects.

  1. The arm-v6m does understand a handful of 32-bit instructions, but they are all branches (where as the long instruction above is a logical AND).]