I am attempting to write a custom bootloader for a STM32F103C8Tx (64k Flash, 20k RAM variant) on a bluepill board.
When the jump from the bootloader occurs the application immediately hardfaults on the next step from the entry point of the application.
I can confirm that SCB->VTOR
is set to 0x08005000
. If I single step the bootloader it does jump to address 0x8005004
.
Here is the stack trace at the jump.
The next step resulting in the hardfault.
Note the call to address 0xfffffffe
witch (I believe) is out of scope of the memory map. I have no idea how to debug this.
Edit: I found the exact instruction where the hardfault occurs in the GDB output. 0x08005014
08005000: 00 50 str r0, [r0, r0]
08005002: 00 20 movs r0, #0
08005004: 35 54 strb r5, [r6, r0]
08005006: 00 08 lsrs r0, r0, #32
08005008: c9 53 strh r1, [r1, r7]
0800500a: 00 08 lsrs r0, r0, #32
0800500c: cf 53 strh r7, [r1, r7]
0800500e: 00 08 lsrs r0, r0, #32
08005010: d7 53 strh r7, [r2, r7]
08005012: 00 08 lsrs r0, r0, #32
08005014: dd 53 strh r5, [r3, r7]
Something I have noticed is that when I run the application from debug
or Terminate and Relaunch
, the application runs fine with no problem while the bootloader is in flash.
If I reset the MCU or the bootloader is not in flash, I get the hardfault.
Additional context
Both BOOT pins are set to 0 to set it to boot from the main flash memory.
The bootloader is placed at 0x8000000
and the application is placed at 0x8005000
.
Bootloader linker script:
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 19K
}
Application linker script:
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
FLASH (rx) : ORIGIN = 0x8005000, LENGTH = 36K
}
The application vector table is set as follows:
#define VECT_TAB_OFFSET 0x00005000U
Note: I have written the bootloader in C++ and not C, so there is some name mangling from the map file.
I have modified the startup_xxx.s
file to immediately call the bootloader jump. This is to avoid de-initializing any peripherals or interrupts that may interfere with the application.
LoopFillZerobss:
cmp r2, r4
bcc FillZerobss
bl _Z6blCallv
/* Call the clock system intitialization function.*/
Call the jump to the application
void blCall(void){
typedef void (*jump_app)(void);
uint32_t _jump = (uint32_t) (__IO uint32_t*)0x08005000;
jump_app app_jump = (jump_app) (_jump + 4);
SCB->VTOR = _jump;
__set_MSP(_jump);
app_jump();
}
I also tried this with the same result as per @Justme's suggestion.
void blCall(void){
typedef void (*jump_app)(void);
uint32_t _jump = (uint32_t) (__IO uint32_t*)0x08005000;
jump_app app_jump = (jump_app) (_jump + 4);
SCB->VTOR = _jump;
__set_MSP(_jump + 4);
app_jump();
}
Switched from C++ to C with no change in the result.
The application is nothing special. It is basically a default generated CubeIDE main with the external HSE set and some GPIO pins for LEDs. The application runs fine if I remove the vector table offset.
IDE: STM32CubeIDE V1.9.0
Debugger: Segger J-Link EDU
Best Answer
This is the source of your problem - you're trying to jump to 0x08005004. That address does not contain any code - it contains the address of your reset handler (0x08005435).
Same with all the other data following on at 0x08005008, 0x0800500c, etc - those are the addresses of the interrupts handlers at 0x080053c9, 0x080053cf, etc.
You'll notice that these are all odd numbers (with the LSB set) to tell the core that it must execute the instructions there in THUMB mode (with 16-bit wide instructions) rather than ARM mode (with 32-bit wide instructions) - this is normal - your reset handler is actually at 0x08005434.
So you need to declare your
_jump
as a pointer (not an integer) and dereference it in order to jump to the location it points to.I'd expect something like this to work (and I've renamed your
_jump
to_vectable
):Note that I'm doing
_vectable + 1
, because_vectable
is a pointer to auint32_t
, which has a size of 4 already, and then I'm dereferencing the result so that I load the value at 0x08005004 (0x08005435), rather than 0x08005004 itself.Similarly when setting the MSP, I dereference
_vectable
to load the value at 0x08005000 (0x20005000).You could also treat
_vectable
as an array and do something like:This code should have exactly the same effect.
Depending on your compiler, you might be able to save a tiny amount of stack space by adding a little to your
jump_app
typedef:typedef void (*jump_app)(void) __attribute__((noreturn));
The attribute on the end tells the compiler that the call to that function will never return, so there's no need for it to push the return location onto the stack.