When starting my STM32 makefile project for the first time I encountered a very early hard fault in the startup code. It happened exactly when calling libc function at
/* Call static constructors */
The whole startup code came straight from ST, so I did not suspect it to be faulty. Here is what I have found out to fix the problem.
I use GNU ARM embedded toolchain, so I also did not suspect it to be at fault. At this stage I suspected: startup code, linker script, compilation flags. STM32L151 MCU hit the fault with IBUSERR bit set in CFSR, which means that it was not an ordinary bus fault (wrong memory address being accessed), so it most likely was not the linker script (it also came straight from ST).
A hard fault can also be triggered when the ARM CPU tries to execute invalid instructions (like a wrong jump or trying to treat program data as code). I eventually traced the problem to linker flags. I use gcc -c to compile .c files to .o object files and plain gcc to link them all into a single executable. The problem happened because gcc called at compile time did have the correct -mcpu=cortex-m3 -mthumb flags. The flags make the output binary contain only valid Cortex M3 instructions, but gcc at link time was called with a different set of flags, which did not contain the mcpu and mthumb options.
After adding -mcpu=cortex-m3 -mthumb flags to LDFLAGS variable of my boilermake build system the final executable worked fine when executed by the MCU.
It turned out that by default gcc will link an ARM instructions version of standard library (or probably a version for different Cortex-M than required) and not its Thumb version. Obviously Cortex-M4 code will not work on smaller chips like Cortex-M0+.
Supported versions of the library can be listed by executing arm-none-eabi-gcc --print-multi-lib and the default settings by arm-none-eabi-gcc -dumpspecs .