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
.thumb
.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
Reset_Handler:
ldr sp, =_estack /* set stack pointer */
/* Copy the data segment initializers from flash to SRAM */
movs r1, #0
b LoopCopyDataInit
CopyDataInit:
ldr r3, =_sidata
ldr r3, [r3, r1]
str r3, [r0, r1]
adds r1, r1, #4
LoopCopyDataInit:
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. */
FillZerobss:
movs r3, #0
str r3, [r2], #4
LoopFillZerobss:
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
Default_Handler:
Infinite_Loop:
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)?
EDIT:
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 \
$(header)
INC_PARAMS= $(INC:%=-I%)
# 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
LSCRIPT = $(ARC)/STM32F446RETx_FLASH.ld
# 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
clean:
rm $(OBJ) $(HEX) $(ELF) $(binfile)
# Delete all intermediate object files
tidy:
rm $(OBJ)
In addition to blink.c
i use system_stm32f4xx.c
which has the system_init function.