4
\$\begingroup\$

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

\$\endgroup\$
4
  • \$\begingroup\$ It should just work. Unless you have interrupts enabled and active in the bootloader, in which case they execute in main program context without initializing after you change the vector table. \$\endgroup\$
    – Justme
    Jul 17, 2022 at 20:19
  • \$\begingroup\$ @Justme This is why I move the jump to application into the startup.s. The bootloader should only init the RAM and Flash from what I understand. In its current state. I had the bootloader de-init any interrupts and clear any flags with the same result. What is shown here is me systematically trying to eliminate any cause of the hardfault over about 2 weeks. \$\endgroup\$
    – Binder
    Jul 17, 2022 at 20:39
  • \$\begingroup\$ Do you disable all interrupts before jumping to 0x08005004 ? \$\endgroup\$
    – Abel Tom
    Jul 18, 2022 at 8:51
  • \$\begingroup\$ Here is an example how to implement the jump https://electronics.stackexchange.com/a/602278/30280. And this adds some information about setting SCB->VTOR https://stackoverflow.com/a/58236379/1401213. \$\endgroup\$
    – kkrambo
    Jul 18, 2022 at 15:26

2 Answers 2

4
\$\begingroup\$

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.

\$\endgroup\$
3
  • \$\begingroup\$ I clearly need to do some more research on how a ARM based CPU boots. I know exactly where my thinking process failed. I just assumed the reset vector would be at address 0x08005004 and contain a goto instruction somewhere down the line in the disassembly to the application entry point. This is typically how it works on 8-bit MCUs, from what I have looked at. The PC would start at address 0 with a goto that jumps over the vector table to the application. This also explains why, if I place a break point at 0x8000000 that it would never trigger. \$\endgroup\$
    – Binder
    Jul 18, 2022 at 22:38
  • \$\begingroup\$ Yeah - that's not how it works. The vector table really is just an array of pointers to the actual handler functions. So When you assign SCB-VTOR, you're telling the core where that array is, so that when an interrupt occurs it first does a lookup into that array, and then jumps to the address it finds there at the appropriate index. \$\endgroup\$
    – brhans
    Jul 18, 2022 at 22:44
  • \$\begingroup\$ Similarly at startup, the default value for VTOR is usually at the start of your micro's internal flash - so that's where you have to put your vector table. The core 'knows' to load its initial stack pointer from the 1st index and then loads the address of the reset handler from the 2nd index and jumps there all by itself - no goto required. \$\endgroup\$
    – brhans
    Jul 18, 2022 at 22:49
2
\$\begingroup\$

The vectors for main app start at 0x08005000, not from where the 0x08005000 points to.

In other words, address 0x08005000 has a pointer which points to stack, and 0x08005004 has a pointer which points to where to start executing.

In yet other words, you can't set vector start address and MSP address to same value.

\$\endgroup\$
1
  • \$\begingroup\$ Tried doing this with the same result. Also flipped them. Could you perhaps add a code snippet? SCB->VTOR = _jump; __set_MSP(_jump+4); \$\endgroup\$
    – Binder
    Jul 17, 2022 at 21:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.