Context is: STM32H753 bare-metal software compiled with arm-none-eabi-gcc.
The reset handler is implemented in C and located in Flash memory:
void reset_handler_c(void)
{
asm_func();
}
The asm function is implemented in a .s file and located in RAM:
.global asm_func
asm_func:
ldr sp,=xxx
bl entry_point
bx lr
(As it is, it does not make a lot of sense but it is obviously a simplified example just to reproduce the issue)
The asm generated is the following:
Problem is: the BLX instruction can take only a register as parameter and a hardfault is generated at execution. Extract from STM32H7 programming manual:
Now, if I call a C function instead, the BLX is replaced by a BL, which is correct:
Any idea why gcc is generating this weird BLX instruction ?
EDIT: the compilation options are -mcpu=cortex-m7 -std=c99 -mfpu=fpv5-d16 -mfloat-abi=hard -mthumb -O1 ...
If you call a function that's in another section or object, the function address is not known ahead of time. A relocation is generated and fixed up by the linker at link time. To patch in the correct function call for the relocation, the linker needs to know whether the function you call is an ARM or a Thumb function. It knows this by inspecting the least-significant bit of the address of the symbol you called. If it's set, it generates code to call a Thumb function. If it's clear, it generates code to call an ARM function. This is what went wrong in your case: the LSB of the address is clear, hence a BLX
instruction to call an ARM function was generated.
The least significant bit of the address needs to be set by the assembler for thumb functions for this to work. However, there's a bit of a problem here: setting the least significant bit is the right thing for function symbols, but wrong for all other symbols. Say for example you're placing a look-up table in the text section and want to access it through a symbol. If the assembler was to set the LSB in the look-up table's symbol, it would introduce off-by-one errors when you tried to access the table. For this reason, the assembler only sets the LSB when you declare the symbol to be a function-type symbol. For this to happen, you need to issue an appropriate .type
directive in the translation unit where the symbol is defined:
.type asm_func, %function
With the symbol type declared correctly, the assembler will set the LSB correctly and the linker will generate the correct type of function call.
It's a good habit to do this for every symbol referring to a function, regardless of architecture. This'll fix a number of diffuse problems you might encounter otherwise.
Also make sure your assembly file is indeed assembled for thumb mode by issuing a
.thumb
directive as the first thing in the source code. I assume you have already done so. Changing the target will not change what mode code is emitted, but it may cause the assembler to refuse assembling code in ARM mode, which is at least a visible failure at build time instead of a silent failure at runtime.