After a long time trying to debug why my simple blinky code for my STM32F446RE microcontroller was not working i discovered a line in the bootup assembly file that i was linking to it which was causing an exception which pushed the microcontroller into an infinite loop exception handler.

here is the assembly lines of importance:

  .syntax unified
  .cpu cortex-m4
  .fpu softvfp

.global  g_pfnVectors
.global  Default_Handler

/* start address for the initialization values of the .data section. 
defined in linker script */
.word  _sidata
/* start address for the .data section. defined in linker script */  
.word  _sdata
/* end address for the .data section. defined in linker script */
.word  _edata
/* start address for the .bss section. defined in linker script */
.word  _sbss
/* end address for the .bss section. defined in linker script */
.word  _ebss
/* stack used for SystemInit_ExtMemCtl; always internal RAM used */

 * @brief  This is the code that gets called when the processor first
 *          starts execution following a reset event. Only the absolutely
 *          necessary set is performed, after which the application
 *          supplied main() routine is called. 
 * @param  None
 * @retval : None

    .section  .text.Reset_Handler
  .weak  Reset_Handler
  .type  Reset_Handler, %function
  ldr   sp, =_estack      /* set stack pointer */

/* Copy the data segment initializers from flash to SRAM */  
  movs  r1, #0
  b  LoopCopyDataInit

  ldr  r3, =_sidata
  ldr  r3, [r3, r1]
  str  r3, [r0, r1]
  adds  r1, r1, #4

  ldr  r0, =_sdata
  ldr  r3, =_edata
  adds  r2, r0, r1
  cmp  r2, r3
  bcc  CopyDataInit
  ldr  r2, =_sbss
  b  LoopFillZerobss
/* Zero fill the bss segment. */  
  movs  r3, #0
  str  r3, [r2], #4

  ldr  r3, = _ebss
  cmp  r2, r3
  bcc  FillZerobss

/* Call the clock system intitialization function.*/
  bl  SystemInit   
/* Call static constructors */
      bl __libc_init_array
/* Call the application's entry point.*/
  bl  main
  bx  lr    
.size  Reset_Handler, .-Reset_Handler

 * @brief  This is the code that gets called when the processor receives an 
 *         unexpected interrupt.  This simply enters an infinite loop, preserving
 *         the system state for examination by a debugger.
 * @param  None     
 * @retval None       
    .section  .text.Default_Handler,"ax",%progbits
  b  Infinite_Loop
  .size  Default_Handler, .-Default_Handler

The line that caused the exception was:

/* Call static constructors */
      bl __libc_init_array

Which causes the microcontroller to jump to a function which eventually causes an exception and thus jump to Default_Handler which is an infinite loop.

You may notice that directly after this call to __libc_init_array is the entry point to my main. If i comment out bl __libc_init_array entirely my program actually works fine; it jumps into main and stays there running code to blink my LED.

I did a arm-none-eabi-objdump -D blink.elf > blink.list' to see what __libc_init_array is doing. Here is the function:

08000608 <__libc_init_array>:
 8000608:   e92d4070    push    {r4, r5, r6, lr}
 800060c:   e59f6064    ldr r6, [pc, #100]  ; 8000678 <__libc_init_array+0x70>
 8000610:   e59f5064    ldr r5, [pc, #100]  ; 800067c <__libc_init_array+0x74>
 8000614:   e0656006    rsb r6, r5, r6
 8000618:   e1b06146    asrs    r6, r6, #2
 800061c:   13a04000    movne   r4, #0
 8000620:   0a000005    beq 800063c <__libc_init_array+0x34>
 8000624:   e2844001    add r4, r4, #1
 8000628:   e4953004    ldr r3, [r5], #4
 800062c:   e1a0e00f    mov lr, pc
 8000630:   e12fff13    bx  r3
 8000634:   e1560004    cmp r6, r4
 8000638:   1afffff9    bne 8000624 <__libc_init_array+0x1c>
 800063c:   e59f603c    ldr r6, [pc, #60]   ; 8000680 <__libc_init_array+0x78>
 8000640:   e59f503c    ldr r5, [pc, #60]   ; 8000684 <__libc_init_array+0x7c>
 8000644:   e0656006    rsb r6, r5, r6
 8000648:   eb000104    bl  8000a60 <_init>
 800064c:   e1b06146    asrs    r6, r6, #2
 8000650:   13a04000    movne   r4, #0
 8000654:   0a000005    beq 8000670 <__libc_init_array+0x68>
 8000658:   e2844001    add r4, r4, #1
 800065c:   e4953004    ldr r3, [r5], #4
 8000660:   e1a0e00f    mov lr, pc
 8000664:   e12fff13    bx  r3
 8000668:   e1560004    cmp r6, r4
 800066c:   1afffff9    bne 8000658 <__libc_init_array+0x50>
 8000670:   e8bd4070    pop {r4, r5, r6, lr}
 8000674:   e12fff1e    bx  lr
 8000678:   08000aac    stmdaeq r0, {r2, r3, r5, r7, r9, fp}
 800067c:   08000aac    stmdaeq r0, {r2, r3, r5, r7, r9, fp}
 8000680:   08000ab4    stmdaeq r0, {r2, r4, r5, r7, r9, fp}
 8000684:   08000aac    stmdaeq r0, {r2, r3, r5, r7, r9, fp}

But i am by no means fluent in assembly so I don't really know what it is trying to accomplish or what produced it. The only other source file i include besides my main.c and the startup assembly is system_stm32f4xx.c which was generated by cubeMX (and where the SystemInit call goes to) but it has no function called __libc_init_array.

So where is this __libc_init_array function coming from? Why is it causing an exception (kicking my micro to the exception handler)? and how do i prevent this (besides obviously writing my own startup assembly and not including extraneous auto-generated files)?


Here is the makefile i use to build the code:

#-{ Project Relative Paths }----------------------------------------------------

# executables, intermediate objects, and libraries.
BIN= ./binary
# program source files
SRC= ./source
# on-architecture specific header files
BHD= ./header
# architecture/device specific files
ARC= ./architecture
# pre-compiled libraries that the project will link against 
LIB= ./library

#-{ Compiler Definitions }------------------------------------------------------

#include directories
INC= $(ARC)/CMSIS/inc \
     $(ARC)/HAL/inc \
     $(ARC)/inc \


# Compiler
CC = arm-none-eabi-gcc

# Device specific flags [1]
# -mcpu=cortex-m4 sets target CPU
# -mthumb tells gcc to compile to thumb2 instructions
DFLAGS = -mcpu=cortex-m4 -mthumb

# Compiler flags
CFLAGS = $(DFLAGS) -g -c -Wall -Wextra

#-{ Linker Definitions }------------------------------------------------------

# Linker
LD = arm-none-eabi-gcc #same as compilter but uses different flags

# Path to linker script #linking script

# Linker flags
LFLAGS = -T $(LSCRIPT) --specs=nosys.specs

# Object copy (for converting formats)
OBJCOPY = arm-none-eabi-objcopy
OFLAGS = -O ihex
OFLAGSbin = -O binary

#-{ Programming/Debugging Definitions }-----------------------------------------

# Debugger
DBG = arm-none-eabi-gdb

# OpenOCD
OCD = openocd

# Debug/programming interface configuration file
INTRF = /usr/local/share/openocd/scripts/interface/stlink-v2-1.cfg
# Target device configurations file
OCDTARGET = /usr/local/share/openocd/scripts/target/stm32f4x.cfg
OCDBOARD = /usr/local/share/openocd/scripts/board/st_nucleo_f4.cfg

#-{ Build Rules }---------------------------------------------------------------

# Final binaries
HEX = $(BIN)/blink.hex
ELF =  $(BIN)/blink.elf
binfile = $(BIN)/blink.bin

# All intermediate object files
OBJ = $(BIN)/blink.o $(BIN)/boot.o $(BIN)/init.o

#-- These rules for the final binaries will usually not require modification

# Convert the ELF into intel hex format
$(HEX): $(ELF)
    $(OBJCOPY) $(OFLAGS) $(ELF) $(HEX)

#convert the ELF into binary format
$(binfile): $(ELF)
    $(OBJCOPY) $(OFLAGSbin) $(ELF) $(binfile)

# Link all intermediate objects into a single executable
$(ELF): $(OBJ)
    $(LD) $(LFLAGS) $(OBJ) -o $(ELF)

#-- These rules will vary depending on the program being built

# Compile the main file
$(BIN)/blink.o: $(SRC)/blink.c $(ARC)/CMSIS/inc/stm32f4xx.h
    $(CC) $(CFLAGS) $(INC_PARAMS) $(SRC)/blink.c -o $(BIN)/blink.o

# Compile the reset handler
$(BIN)/boot.o: $(ARC)/CMSIS/src/startup_stm32f446xx.S
    $(CC) $(CFLAGS) $(INC_PARAMS) $(ARC)/CMSIS/src/startup_stm32f446xx.S -o $(BIN)/boot.o

#compile the post-reset, pre-main, system handler
$(BIN)/init.o: $(ARC)/CMSIS/src/system_stm32f4xx.c
    $(CC) $(CFLAGS) $(INC_PARAMS) $(ARC)/CMSIS/src/system_stm32f4xx.c -o $(BIN)/init.o

#-{ Utility Rules }-------------------------------------------------------------

# OpenOCD command to program a board
program: $(HEX)
    @sudo -E $(OCD) -f $(INTRF) -f $(OCDTARGET) -c "program $(ELF) verify reset exit"

# OpenOCD command to load a program and launch GDB
debug: $(ELF)
    @(sudo -E $(OCD) -f $(INTRF) -f $(OCDTARGET) &); \
    $(DBG) $(ELF) -ex "target remote localhost:3333; load"; \
    sudo kill $(OCD)

# Build the entire program
all: $(HEX) $(binfile)

# Delete all of the generated files
    rm $(OBJ) $(HEX) $(ELF) $(binfile)

# Delete all intermediate object files
    rm $(OBJ)

In addition to blink.c i use system_stm32f4xx.c which has the system_init function.

  • 2
    \$\begingroup\$ The __libc_init_array library you're using is encoded with ARM (32b) instructions, rather than Thumb (16b) instructions. What are you using to compile your code? \$\endgroup\$ – pgvoorhees Dec 12 '17 at 16:05
  • \$\begingroup\$ It's part of the initialisation functions. It's there to initialise and C/C++ objects before the main program starts. Of its crashing there is probably something in your code or the libraries or the way it is compiled that is causing it problems. \$\endgroup\$ – Tom Carpenter Dec 12 '17 at 16:07
  • \$\begingroup\$ If you remove the rest of your source, does the execution make it past this stage? How much example code have you written? \$\endgroup\$ – gregb212 Dec 12 '17 at 16:18
  • 1
    \$\begingroup\$ Most likely the problem isn't in your source code itself, but in the linking step during generation. You're probably using the libraries compiled for 32 bit classic ARM (non-CortexM), which is not using thumb instruction set. What command line and/or tools are your using for the compilation? \$\endgroup\$ – dim lost faith in SE Dec 12 '17 at 16:32
  • 1
    \$\begingroup\$ Try adding -mthumb and -mcpu=cortex-m4 to your LFLAGS variable. \$\endgroup\$ – pgvoorhees Dec 12 '17 at 17:59

Solution is to add -mcpu=cortex-m4 and -mthumb to your LFLAGS variable.

I'm not super-well-versed on GCC internals; please, someone correct me if I'm mistaken.

The reason the flags need to be added is caused by the way your linker is being called. You are rightly letting gcc call the linker in the process of compilation. The issue is that ARM GCC has defaults associated with it, unless you override on the command line. The defaults can be found by typing

arm-none-eabi-gcc -dumpspecs

The interesting line is this one

marm mlittle-endian mfloat-abi=soft mno-thumb-interwork fno-leading-underscore

That means, unless you specify otherwise, these default flags are used for compilation. The "no-thumb-interwork" option is what is killing the program. This flag prevents GCC from generating instructions that tell the processor to change instruction architectures.

Giving the -mcpu and -mthumb flags to ldflags gives GCC hints about how to correctly compile your program.


I had this same problem, but the answer turned out to be something different and I thought I'd share what worked for me for the sake of the next person whose search leads them here. In my case, the -mthumb and -mcpu flags were already there, yet I was getting 32-bit ARM code for the libc stuff. What solved it for me was filling in the LIBDIR= path in the STM32cubeMX-generated Makefile. I set it to -L/usr/lib/arm-none-eabi/newlib/thumb/ This caused the thumb version of the libc stuff to be included rather than the 32-bit ARM version. I haven't looked any further into the root cause, but am left with several questions such as: where did the 32-bit code come from? Was it from the system libraries (my host system is Mint Linux); why didn't the linker say something? Why did STM32cubeMX leave the LIBDIR path blank? (Not expecting any answers....)

  • \$\begingroup\$ This is far more a statement of new questions than it is an answer for the question asked \$\endgroup\$ – Chris Stratton Apr 20 '19 at 0:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.