cancel
Showing results for 
Search instead for 
Did you mean: 

F0 Bootloader jump to App -> HardFault_Handler - what can cause this?

SKled.1
Senior II

I have looked at a bunch of examples throughout here and elsewhere about custom bootloaders.

I noticed the Cortex M0 does not have the VTOR register and found that what people do is, at start, copy the vector table contents from the app flash beginning to the beginning of SRAM and then remap SRAM to address 0.

I did that. But as soon as I, debugging within the bootloader project, step over the call on the application function pointer, the target is in the hardfault handler.

The application address that the pointer variable holds looks correct, except it has exactly 1 byte more. I have read that odd addresses mean it is a Thumb2 instruction and the LSB goes elsewhere and is not really part of the address - so that would be expected.

The CRC of the app code in the app flash region also matches.

I have also exported that memory contents from CubeIDE (Eclipse) Memory Browser and compared it to the compiled binary in a hex editor - they match.

My linker scripts look like this:

Ther is one .ld file that get included in both the bootloader's and the app's main .ld file that CubeIDE initially provided, where I threw out the original MEMORY region and replaced it with include... the file below.

As you can see I have an extra 128 byte region there, for a header, which is generated by a custom program that parses the app binary to generate CRC and some meta info.

This app header is concatenated with the app binary to form one block that can be flashed at 0x8008000. The actual app thus starts 0x80 bytes later.

In the C/C++ programs, I use a header that declares these linker script variables as extern unsigned, and provide matching convenience macros that take their adresses, to get the actual values.

The values I see then in the debugger where they are used, seem to be all correct.

The SP register also does get set to end of RAM when stepping over the __set_MSP(..) line.

The jump does not happen from an ISR, it is in a global function, albeit one in a .cpp file, which is called by the main.c generated by CubeIDE.

Now I would not be surprise if the default Reset_Handler in the stm32*.S file did something suboptimal w.r.t. my goal here - but I can't see that it even gets there, it goes directly from stepping over appJumpAddr(); to the hardfault handler. (see code further below)

Edit: the main() befor calling my function, does this, as generated by CubeIDE:

LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_SYSCFG);

So that thing is on.

Any ideas what to check, to see what's wrong?

MEMORY
{
/* vector table fits in 48 32bitpointers i.e. 192 bytes (0xC0)*/
  APPVTABLERAM (xrw) : ORIGIN = 0x20000000,   LENGTH = 192
  RAM    (xrw)       : ORIGIN = 0x200000C0,   LENGTH = 32K-192
  
  /* bootloader binary */
  BOOTFLASH      (rx)    : ORIGIN = 0x8000000,   LENGTH = 32K
  
  /* Header binary, created by external custom program to calculate CRC etc */
  HEADERFLASH    (rx)    : ORIGIN = 0x8008000,   LENGTH = 128
  
  /* Start of actual application binary */
  APPFLASH       (rx)    : ORIGIN = 0x8008080,   LENGTH = 256K-32K-128
}
__mm_headerflash_start__ = ORIGIN(HEADERFLASH);
__mm_headerflash_size__ = LENGTH(HEADERFLASH);
__mm_bootflash_start__ = ORIGIN(BOOTFLASH);
__mm_bootflash_size__ = LENGTH(BOOTFLASH);
__mm_appflash_start__ = ORIGIN(APPFLASH);
__mm_appflash_size__ = LENGTH(APPFLASH);
__mm_app_vtab_ram_start__ = ORIGIN(APPVTABLERAM);
__mm_app_vtab_ram_size__ = LENGTH(APPVTABLERAM);

void BootLoaderMain_Run()
{
    const unsigned Nptrs = MM_app_vtab_ram_size / sizeof(uint32_t*);
    volatile uint32_t* movedIrqVectorTab = (volatile uint32_t*) MM_app_vtab_ram_start;
    const volatile uint32_t* appIrqVectorTab = (const volatile uint32_t*) MM_appflash_start;
    for (unsigned i=0;  i<Nptrs;    ++i) // copy the vector table into RAM at the prepared location
    {   
        movedIrqVectorTab[i] = appIrqVectorTab[i];
    }
 
    if (check_app_crc())
    {        
        NVIC_DisableIRQ(I2C1_IRQn); // the only peripheral IRQ that's on in the bootloader, so far
        //__disable_irq();          // Also tried more brutal, no visible difference
 
        __DSB();
        __ISB();
		
        //__attribute__((noreturn))
        void (*appJumpAddr)() = (void (*)()) (*((uint32_t *)(MM_appflash_start + 4)));
 
        __set_MSP( *((uint32_t *)MM_appflash_start) );
 
        LL_SYSCFG_SetRemapMemory( LL_SYSCFG_REMAP_SRAM );
	
        __DSB();
        __ISB();
 
        appJumpAddr(); // <<< BOOM! Hard fault
    }
    else
    {
        while (1)
	    {	// blink LED forever
	    }
    }
}

1 ACCEPTED SOLUTION

Accepted Solutions
SKled.1
Senior II

D'OH! Talk about "shoot yourself in the foot AND reuse the bullet".

The call right before the "signal handler called" entry, "_static_initialization_and_destruction_0()" - it shows me a global const struct instance being initialized with all constexpr stuff (c++ file). It was supposed to be done all at compile time.

I annotated that global struct instance with __attribute__((__section__(".buildinfo"))), to, in the linker script, fix the address offset of that struct so my external tool can fetch some info about the binary.

But one of the assignments in the struct initialization, albeit a constexpr function, was apparently really being compiled as static, sothe whole thing went to static i.e. compile-time initialization, which is not quite commensurate with fixed linker section address I guess. Argh!

When I replace that one line of all the lines of struct member initialization, it jumps to the bootloader and it runs.

There's something else unexpected in app behavior, or i'm just still confused right now, but it basically runs.

Now I need to find out why the heck that assignment code line is not really compile-time. (also, I would have liked a warning about that instead of extra surprize edition binary ^^)

View solution in original post

4 REPLIES 4
MM..1
Chief II

I dont see where your code is bad, but i in all my code in bootloader only jump to app and vector relocate as first main app job.

Too your app must reserve RAM for vectors RAM (xrw) : ORIGIN = 0x200000C0, LENGTH = 32K-192

and how mcu you use? 32K SRAM ?

And i see use I2C try deinit it after disable irq

Look at the actual code generated by the compiler. Watch what you're doing with the stack and where the local/auto variables are living.

Have code in the app's Reset_Handler set the stack pointer.

Look at what's actually faulting, what the MCU is reporting as the objectionable behaviour.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
SKled.1
Senior II

I de-init'ed all the peripherals and commented out setting MSP in the bootloader. The app's default reset handler sets it to _estack, which also just points at the end of the RAM.

When I now step over the jump, I land in code which the bootloader debugging session knows no source for. Looking at the application's .map file, I see it is the application's hardfault handler now instead of the bootloader's. Whee, that's progress! Lol.

Unfortunately, the Cortex M0 apparently has no fault status register.

How would I find out what the MCU is objecting to?

EDIT:

Okay, a little update: after setting up source level debugging when jumping from loader to app in the IDE, I can see something readable in the call stack display. Looks like some of the reset handler variable init stuff is going wrong for some reason.

Need to dig into that.

0693W000006GHulQAG.png 

I'm slightly confused why I get there instantly. I had instruction level stepping on already, and when executing the blx instruction to jump to the app, I immediately get this image above - no stepping through the reset handler assembly file.

SKled.1
Senior II

D'OH! Talk about "shoot yourself in the foot AND reuse the bullet".

The call right before the "signal handler called" entry, "_static_initialization_and_destruction_0()" - it shows me a global const struct instance being initialized with all constexpr stuff (c++ file). It was supposed to be done all at compile time.

I annotated that global struct instance with __attribute__((__section__(".buildinfo"))), to, in the linker script, fix the address offset of that struct so my external tool can fetch some info about the binary.

But one of the assignments in the struct initialization, albeit a constexpr function, was apparently really being compiled as static, sothe whole thing went to static i.e. compile-time initialization, which is not quite commensurate with fixed linker section address I guess. Argh!

When I replace that one line of all the lines of struct member initialization, it jumps to the bootloader and it runs.

There's something else unexpected in app behavior, or i'm just still confused right now, but it basically runs.

Now I need to find out why the heck that assignment code line is not really compile-time. (also, I would have liked a warning about that instead of extra surprize edition binary ^^)