Pseudo-random behavior of GCC start-up template when main() returns
Board: NUCLEO-H723ZG Compiler: arm-none-eabi-gcc (Arch Repository) 14.1.0
The GCC start-up assembly template for the H723xx (see here) contains the following code at the end of Reset_Handler:
/* Call static constructors */
bl __libc_init_array
/* Call the application's entry point.*/
bl main
bx lr
Usually, main is assumed not to return. However, if it does, the next instruction BX LR will jump to the address stored in the link register. This address will usually be the return address for the last sub-routine call inside main. So, this instruction will jump back into main to a more or less random location. In my particular case, this location was close to the end of main, and execution continued through to the end of main where it pop-ed a value of zero into the program counter. This then triggered a hard fault, because zero is not a thumb mode address, and the default hard fault handler (an infinite loop) was entered.
Of course, one can debate about the desired behavior if main returns, but I think we can agree the current behavior is not it. One approach would be to replace BX LR with an infinite loop, similar to the GCC start-up templates for the H5 series. See for example this snippet below (taken from here):
/* Call static constructors */
bl __libc_init_array
/* Call the application's entry point.*/
bl main
LoopForever:
b LoopForever
One could also consider throwing in a call to __libc_fini_array() after main and before the infinite loop for symmetry.
I had a cursory look at some different stm32 mcu's startup code assembly, it appears that this is done this way across the startup code of more STM32 families; the nonsensical "bx lr" and the missing __libc_fini_array() call is also in the F4 startup code. The H5 startup code has an infinite loop after return from main() -- but no call to _libc_fini_array().
It would be best to homogenize this across all families, I think. That would take a sweep across a bunch of repositories.
In general, the behavior when returning from main() should be documented. If the choice is made to not touch the current implementation (for fear of breaking stuff, for example), I'd like to see a clear statement somewhere in the documentation that returning from main() triggers undefined behavior. I wouldn't favor that approach, but it's better than having UB and not documenting it.
Hello @0xa000,
Your point seems very relevant. It has been forwarded to our development teams. I will keep you informed.
With regards,
ST Internal Reference: 192331