¿Por qué __libc_init_array provoca una excepción?

Después de mucho tiempo tratando de depurar por qué mi código parpadeante simple para mi microcontrolador STM32F446RE no funcionaba, descubrí una línea en el archivo de ensamblaje de arranque que estaba vinculando y que estaba causando una excepción que empujó al microcontrolador a un controlador de excepción de bucle infinito.

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 provoca una excepción y, por lo tanto, salta a Default_Handlerlo que es un bucle infinito.

Puede notar que directamente después de esta llamada a __libc_init_arrayes el punto de entrada a my main. Si comento bl __libc_init_arraypor completo, mi programa realmente funciona bien; salta a principal y permanece allí ejecutando código para hacer parpadear mi LED.

Hice un arm-none-eabi-objdump -D blink.elf > blink.list' to see what __libc_init_arrayestá 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 tengo fluidez en el ensamblaje, por lo que realmente no sé qué está tratando de lograr o qué lo produjo. El único otro archivo fuente que incluyo además de mi main.c y el ensamblado de inicio es system_stm32f4xx.cel que generó cubeMX (y al que SystemInitse dirige la llamada), pero no tiene una función llamada __libc_init_array.

Entonces, ¿de dónde viene esta __libc_init_arrayfunción? ¿Por qué está causando una excepción (pateando mi micro al controlador de excepciones)? y ¿cómo evito esto (además de obviamente escribir mi propio ensamblaje de inicio y no incluir archivos extraños generados automáticamente)?

EDITAR:

Aquí está el archivo MAKE que uso para compilar 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.cuso system_stm32f4xx.cque tiene la función system_init.

La biblioteca __libc_init_array que está utilizando está codificada con instrucciones ARM (32b), en lugar de instrucciones Thumb (16b). ¿Qué estás usando para compilar tu código?
Es parte de las funciones de inicialización. Está ahí para inicializar objetos C/C++ antes de que se inicie el programa principal. De su bloqueo, probablemente haya algo en su código o en las bibliotecas o en la forma en que se compila que está causando problemas.
Si elimina el resto de su fuente, ¿la ejecución supera esta etapa? ¿Cuánto código de ejemplo has escrito?
Lo más probable es que el problema no esté en el código fuente en sí, sino en el paso de vinculación durante la generación. Probablemente esté utilizando las bibliotecas compiladas para ARM clásico de 32 bits (no CortexM), que no utiliza el conjunto de instrucciones de pulgar. ¿Qué línea de comando y/o herramientas está utilizando para la compilación?
He agregado mi archivo MAKE a la publicación principal para que pueda ver cómo lo estoy creando y vinculando.
Intente agregar -mthumb y -mcpu=cortex-m4 a su variable LFLAGS.
@pgvoorhees eso funcionó!
@pgvoorhees, ¿puede explicar por qué necesito esas banderas cuando simplemente vinculo?

Respuestas (2)

La solución es agregar -mcpu=cortex-m4 y -mthumb a su variable LFLAGS.

No estoy muy bien versado en las partes internas de GCC; por favor que alguien me corrija si me equivoco.

La razón por la que se deben agregar las banderas se debe a la forma en que se llama a su enlazador. Con razón, está permitiendo que gcc llame al enlazador en el proceso de compilación. El problema es que ARM GCC tiene valores predeterminados asociados, a menos que lo anule en la línea de comando. Los valores predeterminados se pueden encontrar escribiendo

arm-none-eabi-gcc -dumpspecs

La linea interesante es esta

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

Eso significa que, a menos que especifique lo contrario, estos indicadores predeterminados se utilizan para la compilación. La opción "interfuncionamiento sin pulgar" es lo que está acabando con el programa. Esta bandera evita que GCC genere instrucciones que le indiquen al procesador que cambie las arquitecturas de instrucciones.

Dar las banderas -mcpu y -mthumb a ldflags le da a GCC pistas sobre cómo compilar correctamente su programa.

Tuve el mismo problema, pero la respuesta resultó ser algo diferente y pensé en compartir lo que funcionó para mí por el bien de la próxima persona cuya búsqueda los lleve aquí. En mi caso, las banderas -mthumb y -mcpu ya estaban allí, pero estaba obteniendo un código ARM de 32 bits para las cosas de libc. Lo que me resolvió fue completar la ruta LIBDIR= en el Makefile generado por STM32cubeMX. Lo configuré en -L/usr/lib/arm-none-eabi/newlib/thumb/ Esto provocó que se incluyera la versión en miniatura de las cosas de libc en lugar de la versión ARM de 32 bits. No he buscado más en la causa raíz, pero me quedan varias preguntas como: ¿de dónde vino el código de 32 bits? ¿Fue de las bibliotecas del sistema (mi sistema host es Mint Linux); ¿Por qué el enlazador no dijo nada? ¿Por qué STM32cubeMX dejó en blanco la ruta LIBDIR? (Sin esperar ninguna respuesta.

Esto es mucho más una declaración de nuevas preguntas que una respuesta a la pregunta formulada.