cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F429 hardfaults in __libc_init_array after jumping to application's Reset_Handler from bootloader. Why?

timistof
Associate III

I’m creating a custom USB HS DFU bootloader for my STM32F429 project, using arm-none-eabi-gcc together with the CLion IDE. I’m having trouble jumping to the application code from the bootloader.

The CLion project consists of a bootloader target and a firmware image target, as well as a standalone firmware target that excludes the bootloader and enables me to debug the application code directly. The image target uses modified startup code (explained later), creates the image binary and signs it with a custom tool. This tool also merges the bootloader and image binaries into a single one. It places the bootloader at 

0x00000000 (0x08000000 in flash) and the firmware at 0x0004000 (0x08040000 in flash). With a hex-editor I can confirm the signing and placing of the parts is performed correctly.

After flashing the merged binary, I re-flash the bootloader target and debug it. Just before the jump to the application, the vector table is relocated to the start of the firmware code, the address of the reset-handler is casted to a void(void) function pointer and is called:

void DFUBootloader::startApplication(const uint32_t *imageBase)
{
    // Copy vector table to ram be able to look at its contents in the debugger easily
    VectorTable vectorTable;
    memcpy(&vectorTable, imageBase, sizeof(VectorTable));
 
    // get image reset handler function pointer
    void (*imageResetHandler)() = reinterpret_cast<void(*)()>(vectorTable.resetHandler);
 
    // disable interrupts
    RCC->CIR = 0x00000000;
 
    // relocate vector table to start of image binary.
    SCB->VTOR = (uint32_t)imageBase;
 
    __set_MSP(vectorTable.stackPointerInitValue);
 
    imageResetHandler();
}

From the debugger, I can confirm the jump to the firmware entry point is performed correctly. As execution continues from the firmware entry point, there is no more debug-info. I haven't found a way to "merge" the debug-info into one, but at least I can match the dissasembly to the modified startup_stm32f429xx.s assembly file, confirming the jump is successful. The modified startup file for the firmware image omits the call to SystemInit, since its job has been performed by the bootloader code already.

For some reason, the MCU seems to crash in the __libc_init_array routine. I suspect the relocation of the firmware from 0x08000000 (when it was built) to 0x08040000 in flash might have something to do with it, or maybe jump from the bootloader code leaves registers in a "dirty" state. I can't figure out what's going from from the disassembly alone, but it ends with a 

blx to 0x00000000. Not sure if that's deliberate or not.

In hope of taking away all of __libc_init_array's work, I have tried to make the firmware binary as bare-bones as possible, with only the linker script, the startup file and a main.c containing only a infinite while-loop, But alas.

Does anyone know how to solve this?

7 REPLIES 7

I'd guess you still have a bunch of other interrupts firing, and you've just nuked the structures they reference.​

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
berendi
Principal
    // disable interrupts
    RCC->CIR = 0x00000000;

This disables the RCC peripheral interrupts only. Check what other peripherals have enabled interrupts in the bootloader (USB and SysTick are the most likely candidates), and disable them. Mind that SysTick can't be disabled through NVIC.

> I suspect the relocation of the firmware from 0x08000000 (when it was built) to 0x08040000 in flash might have something to do with it

If it is linked at 0x08000000 then it sure won't work at 0x08040000. Pointers to values stored in flash will be all wrong. Modify the linker script of the application to pretend that the flash begins at 0x08040000. (BTW is your bootloader really 256k big?)

> maybe jump from the bootloader code leaves registers in a "dirty" state.

CPU registers should not be a problem unless you somehow managed to call DFUBootloader::startApplication() from interrupt context.

Peripheral registers should be reset to a state as close as their reset state as possible, but failing to do that would not cause problems in __libc_init_array(), only later, if spurious interrupts are taken care of.

Well, the startup target *was* missing its stm32f4xx_it.c file. But, I added a "blank" interrupt handler file to the target, removing all function calls present in the regular stm32f4xx_it.c fie.

I also made sure I'm disabling the interrupts before relocating the vector table, and re-enabling them after. Like so:

void DFUBootloader::startApplication(const uint32_t *imageBase)
{
    // Copy vector table to ram be able to look at its contents in the debugger
    VectorTable vectorTable;
    memcpy(&vectorTable, imageBase, sizeof(VectorTable));
 
    // call firmware image reset handler and we're off to the races.
    void (*imageResetHandler)() = reinterpret_cast<void(*)()>(vectorTable.resetHandler);
 
    // disable interrupts if there are enabled
    bool interruptsEnabled = __get_PRIMASK() != 0;
    if(interruptsEnabled)
        __disable_irq();
 
    // relocate vector table to start of active image binary.
    SCB->VTOR = (uint32_t)imageBase;
 
    __set_MSP(vectorTable.stackPointerInitValue);
 
    // re-enable interrupts if they were enabled
    if(interruptsEnabled)
        __enable_irq();
 
    imageResetHandler();
}

But, unfortunately, the MCU still crashes in the same way in the application's __libc_init_array...

Thanks, this gives me a new angle to try. As stated, I now use __disable_irq() and __enable_irq(). Those should work, right?

My idea was to have two "pages" for the application firmware. The bootloader holds a "index" in flash that points to the active application page. This way, if a update CRC verification fails, I could not update the index and still be able to boot into the last confirmed working page.

But, with this approach I don't know in what page a flashed firmware will end up, so I can't adjust the flash base in the linker script. I thought not adjusting the flash base this would work, since I've read somewhere every jump in STM code is relative. Maybe __init_libc_array() is not position independent?

Let's see what happens If I just use one page slot, this way I can let the flash start at 0x08040000.

> I now use __disable_irq() and __enable_irq(). Those should work, right?

No, alone it would just postpone the problem. You'd at least get past __libc_init_array() though. Interrupts could still happen after __enable_irq() when the application doesn't expect them.

> every jump in STM code is relative

Note that I've mentioned pointers, not jumps. Every single pointer to code or data in flash would be wrong. Including but not limited to interrupt vectors, const arrays, virtual method tables.

It is possible to compile relocatable code with gcc, and implement a bootloader that does the relocating on the fly before writing the application to the flash. Read about the basic idea here. You should recompile all runtime libraries (I don't know how, but there should be instructions in the arm-none-eabi-gcc sources), as it could still fail on any library function not compiled with -fPIC, including __libc_init_array(). I have no idea whether it would work with C++ at all.

Thank you so much! That does make a lot of sense. I will check it out and get back to you. Cheers, Tim Op wo 15 apr. 2020 om 14:23 schreef ST Community :

Probably want to review the linker script and map file about how things are placed.

Especially look at how/where the constructors are being placed (.preinit_array, .init_array and .fini_array as I recall).

If there are any dependencies on external memory, and if those devices/interfaces have been brought up in SystemInit()

Would probably also inspect the .ELF file with objcopy or FromELF type dumping tools.

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