STM32 – Applications Hardfault After Jumping from the Bootloader

armbootloadercmicrocontrollerstm32

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.

Bootloader stack trace

The next step resulting in the hardfault.

enter image description here

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

it does jump to address 0x8005004

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):

void blCall(void)
{
    typedef void (*jump_app)(void);

    uint32_t *_vectable = (__IO uint32_t*)0x08005000;  // point _vectable to the start of the application at 0x08005000

    jump_app app_jump = (jump_app) *(_vectable + 1);   // get the address of the application's reset handler by loading the 2nd entry in the table

    SCB->VTOR = _vectable;   // point VTOR to the start of the application's vector table

    __set_MSP(*_vectable);   // setup the initial stack pointer using the RAM address contained at the start of the vector table

    app_jump();   // call the application's reset handler
}

Note that I'm doing _vectable + 1, because _vectable is a pointer to a uint32_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:

void blCall(void)
{
    typedef void (*jump_app)(void);

    uint32_t *_vectable = (__IO uint32_t*)0x08005000;  // point _vectable to the start of the application at 0x08005000

    jump_app app_jump = (jump_app) _vectable[1];   // get the address of the application's reset handler by loading the 2nd entry in the table

    SCB->VTOR = _vectable;   // point VTOR to the start of the application's vector table

    __set_MSP(_vectable[0]);   // setup the initial stack pointer using the RAM address contained at the start of the vector table

    app_jump();   // call the application's reset handler
}

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.