Después de mucho tiempo tratando de depurar por qué no funcionaba mi simple código de parpadeo para mi microcontrolador STM32F446RE, descubrí una línea en el archivo de ensamblaje de inicio que estaba enlazando, lo que estaba causando una excepción que empujó al microcontrolador a un bucle infinito manejador de excepciones.
Aquí están las líneas de montaje de importancia:
.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
La línea que causó la excepción fue:
/* Call static constructors */
bl __libc_init_array
Lo que hace que el microcontrolador salte a una función que eventualmente causa una excepción y, por lo tanto, salta a Default_Handler
, que es un bucle infinito.
Puede notar que directamente después de esta llamada a __libc_init_array
es el punto de entrada a mi main
. Si comento bl __libc_init_array
por completo, mi programa realmente funciona bien; salta a main y permanece allí ejecutando el código para parpadear mi LED.
Hice un arm-none-eabi-objdump -D blink.elf > blink.list' to see what __libc_init_array
está haciendo. Aquí está la función:
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}
Pero de ninguna manera soy fluido en el montaje, por lo que realmente no sé qué es lo que está tratando de lograr o qué lo produjo. El único otro archivo fuente que incluyo, además de mi main.c y el ensamblaje de inicio, es system_stm32f4xx.c
, generado por cubeMX (y donde va la llamada SystemInit
), pero no tiene una función llamada __libc_init_array
.
Entonces, ¿de dónde viene esta función __libc_init_array
? ¿Por qué está causando una excepción (pateando mi micro al controlador de excepciones)? y ¿cómo puedo evitar esto (además de escribir, obviamente, mi propio ensamblaje de inicio y no incluir archivos generados automáticamente)?
EDITAR:
Aquí está el makefile que uso para construir el código:
#-{ 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)
Además de blink.c
, uso system_stm32f4xx.c
que tiene la función system_init.