Warum verursacht __libc_init_array eine Ausnahme?

Nachdem ich lange versucht hatte zu debuggen, warum mein einfacher Blinkcode für meinen STM32F446RE-Mikrocontroller nicht funktionierte, entdeckte ich eine Zeile in der Bootup-Assembly-Datei, die ich damit verknüpfte, was eine Ausnahme verursachte, die den Mikrocontroller in einen Endlosschleifen-Ausnahmehandler drückte.

Hier sind die Montagelinien von Bedeutung:

  .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

Die Zeile, die die Ausnahme verursacht hat, war:

/* Call static constructors */
      bl __libc_init_array

Dies bewirkt, dass der Mikrocontroller zu einer Funktion springt, die schließlich eine Ausnahme verursacht, und somit zu Default_Handlereiner Endlosschleife springt.

Sie werden vielleicht bemerken, dass direkt nach diesem Aufruf __libc_init_arrayder Einstiegspunkt zu my ist main. Wenn ich vollständig auskommentiere, bl __libc_init_arrayfunktioniert mein Programm tatsächlich einwandfrei. es springt in die Hauptleitung und bleibt dort und führt den Code aus, um meine LED zu blinken.

Ich habe etwas arm-none-eabi-objdump -D blink.elf > blink.list' to see what __libc_init_arraygetan. Hier ist die Funktion:

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}

Aber ich bin keineswegs fließend im Zusammenbauen, also weiß ich nicht wirklich, was es zu erreichen versucht oder was es hervorgebracht hat. Die einzige andere Quelldatei, die ich neben meiner main.c und der Startup-Assembly einfüge, ist system_stm32f4xx.cdie, die von cubeMX generiert wurde (und wohin der SystemInitAufruf geht), aber sie hat keine Funktion namens __libc_init_array.

Woher __libc_init_arraykommt diese Funktion? Warum verursacht es eine Ausnahme (das Kicken meines Mikros zum Ausnahmebehandler)? und wie verhindere ich dies (abgesehen davon, dass ich offensichtlich meine eigene Startup-Assembly schreibe und keine überflüssigen automatisch generierten Dateien einschließe)?

BEARBEITEN:

Hier ist das Makefile, das ich zum Erstellen des Codes verwende:

#-{ 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)

Zusätzlich zu blink.ci verwende ich system_stm32f4xx.cdie Funktion system_init.

Die von Ihnen verwendete __libc_init_array-Bibliothek ist mit ARM-Anweisungen (32b) und nicht mit Thumb-Anweisungen (16b) codiert. Womit kompilierst du deinen Code?
Es ist Teil der Initialisierungsfunktionen. Es ist dazu da, C/C++-Objekte zu initialisieren, bevor das Hauptprogramm startet. Dass es abstürzt, liegt wahrscheinlich an Ihrem Code oder den Bibliotheken oder an der Art und Weise, wie es kompiliert wird, was Probleme verursacht.
Wenn Sie den Rest Ihrer Quelle entfernen, kommt die Ausführung über diese Phase hinaus? Wie viel Beispielcode haben Sie geschrieben?
Höchstwahrscheinlich liegt das Problem nicht in Ihrem Quellcode selbst, sondern im Verknüpfungsschritt während der Generierung. Sie verwenden wahrscheinlich die Bibliotheken, die für das klassische 32-Bit-ARM (nicht CortexM) kompiliert wurden, das keinen Thumb-Befehlssatz verwendet. Welche Befehlszeile und/oder Tools verwenden Sie für die Kompilierung?
Ich habe mein Makefile zum Hauptbeitrag hinzugefügt, damit Sie sehen können, wie ich es aufbaue und verlinke
Versuchen Sie, -mthumb und -mcpu=cortex-m4 zu Ihrer LFLAGS-Variablen hinzuzufügen.
@pgvoorhees das hat funktioniert!
@pgvoorhees kannst du erklären, warum ich diese Flags brauche, wenn ich einfach verlinke?

Antworten (2)

Die Lösung besteht darin, -mcpu=cortex-m4 und -mthumb zu Ihrer LFLAGS-Variablen hinzuzufügen.

Ich kenne mich mit GCC-Interna nicht besonders gut aus; bitte korrigiert mich jemand wenn ich falsch liege.

Der Grund, warum die Flags hinzugefügt werden müssen, liegt in der Art und Weise, wie Ihr Linker aufgerufen wird. Sie lassen zu Recht gcc den Linker während des Kompilierungsprozesses aufrufen. Das Problem ist, dass mit ARM GCC Standardwerte verknüpft sind, es sei denn, Sie überschreiben sie in der Befehlszeile. Die Standardwerte können durch Eingabe gefunden werden

arm-none-eabi-gcc -dumpspecs

Die interessante Zeile ist diese

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

Das heißt, wenn Sie nichts anderes angeben, werden diese Standard-Flags für die Kompilierung verwendet. Die Option "no-thumb-interwork" tötet das Programm. Dieses Flag verhindert, dass GCC Anweisungen generiert, die den Prozessor anweisen, die Anweisungsarchitekturen zu ändern.

Das Übergeben der Flags -mcpu und -mthumb an ldflags gibt GCC Hinweise, wie Sie Ihr Programm korrekt kompilieren.

Ich hatte das gleiche Problem, aber die Antwort stellte sich als etwas anderes heraus, und ich dachte, ich würde teilen, was für mich funktioniert hat, um der nächsten Person zuliebe, deren Suche sie hierher führt. In meinem Fall waren die Flags -mthumb und -mcpu bereits vorhanden, aber ich bekam 32-Bit-ARM-Code für das libc-Zeug. Was es für mich gelöst hat, war das Ausfüllen des LIBDIR=-Pfads im von STM32cubeMX generierten Makefile. Ich habe es auf -L/usr/lib/arm-none-eabi/newlib/thumb/ gesetzt. Dies führte dazu, dass die Thumb-Version des libc-Zeugs anstelle der 32-Bit-ARM-Version enthalten war. Ich habe die eigentliche Ursache nicht weiter untersucht, aber ich habe einige Fragen, wie zum Beispiel: Woher kommt der 32-Bit-Code? War es aus den Systembibliotheken (mein Hostsystem ist Mint Linux); Warum hat der Linker nichts gesagt? Warum hat STM32cubeMX den LIBDIR-Pfad leer gelassen? (Erwarte keine Antworten.

Dies ist weit mehr eine Formulierung neuer Fragen als eine Antwort auf die gestellte Frage