STMG431 - Hardfault in startup assembly? - Page 1
Forum section: electronics / general

author: eTobey
author_rank: Super Contributor
timestamp: December 23, 2023, 03:00:21 am
content: Hi,i am working on a bootloader, and i need help while investigating the error cause. The problem is, that debugging the application is not easy, as the underlying bootloader.From the addresses in the PC, i think i successfully jumped to the reset_handler of the application. But it seems, that in the reset_handler an error happens.The assembly i find in the startup.s seems to be different to what i can find in the dissassembly.

author: uer166
author_rank: Super Contributor
timestamp: December 23, 2023, 03:26:33 am
content: STM32CubeIDE has the hard-fault viewer that decodes the registers to figure out what specific issue caused it. Stuff like unaligned access, bus fault, etc

author: ataradov
author_rank: Super Contributor
timestamp: December 23, 2023, 03:34:31 am
content: Are you looking at the bootloader code and comparing it to the application code?This is not the best way to jump to the application. Once __set_MSP() is called, your code no longer has a valid stack, so if anything tries to use the stack, you will have a bad time. Here is a better code:Code:uint32_t msp = *(uint32_t *)(APP_START);uint32_t reset_vector = *(uint32_t *)(APP_START + 4);asm("msr msp, %0 \n bx %1" :: "r" (msp), "r" (reset_vector));This is atomic and guaranteed to work regardless of what the compiler is doing and optimization settings.Also, disabling IRQs before jumping to the application is not the best. You really need to leave clean CPU. To do this, when you need to run the application, set a flag and do a reset. On reset check the flag and run the application without initializing any peripherals or clocks.The flag can be a certain value written into the first few SRAM locations or similar.

author: DavidAlfa
author_rank: Super Contributor
timestamp: December 23, 2023, 07:45:25 am
content: This worked for me in a M0+ device, sourcehere.Code:void JumpToBootloader(void){typedef struct {uint32_t Initial_SP;void (*Reset_Handler)(void);}boot_vectable_t;boot_vectable_t * BOOTVTAB = (boot_vectable_t *)APP_START;__set_MSP(BOOTVTAB->Initial_SP); // Set the MSPBOOTVTAB->Reset_Handler(); // Jump to bootloader}

author: eTobey
author_rank: Super Contributor
timestamp: December 23, 2023, 03:32:27 pm
content: I found the problem for the hardfault:I havent finished the FLASH erase section, so there was abandoned code.But now i have another issue: i can verify, that my code is startet. I measure the time, a pin goes high/low, and this is as it should happen. But the LPUART does not work any more. I use the very same function to init the bootloader and the app (the app uses the init only when using without bootloader). The bootloader happly prints stuff in the terminal, but then the app does not.Quote from: ataradov on December 23, 2023, 03:34:31 amDo you mean with "code" the bootloader? Once i jumped, i will never come back there, so i dont care what is left behind. (Kind of a human thought to have, huh?)Isnt your code essentialy doing the same as i do? set the msp and jumpaddress (resethandler)?Quote from: DavidAlfa on December 23, 2023, 07:45:25 amWhats the point to put it in to a struct, when you immediately use it out of there?

author: DavidAlfa
author_rank: Super Contributor
timestamp: December 23, 2023, 03:56:18 pm
content: You're not really putting anything into a struct, just pointing to it, basically explaining the compiler how to access the data.It's doing the same as your code without messing with numbers.Code:void JumpToBootloader(void){8005614:b510push{r4, lr}uint32_t Initial_SP;void (*Reset_Handler)(void);}boot_vectable_t;boot_vectable_t * BOOTVTAB = (boot_vectable_t *)0x8004000;__set_MSP(BOOTVTAB->Initial_SP); // Set the MSP8005618:4b03ldrr3, [pc, #12]; (8005628 <JumpToBootloader+0x14>)__ASM volatile ("MSR msp, %0" : : "r" (topOfMainStack) : );800561a:681aldrr2, [r3, #0]800561c:f382 8808msrMSP, r2BOOTVTAB->Reset_Handler(); // Jump to bootloader8005620:685bldrr3, [r3, #4]8005622:4798blxr3}

author: ataradov
author_rank: Super Contributor
timestamp: December 23, 2023, 04:39:01 pm
content: Quote from: eTobey on December 23, 2023, 03:32:27 pmYes, I do mean the bootloader code. Between the __set_MSP() and jump you have the code that assigns reset_handler variable. This code may still need stack. It is not likely, since optimizing compilers are likely to use registers. But there is still a chance.

author: peter-h
author_rank: Super Contributor
timestamp: December 25, 2023, 07:35:34 pm
content: Do a search here for my posts about how to do a boot loader, with code running in RAM, etc. There are quite a few gotchas.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: December 26, 2023, 01:01:37 am
content: Does your UART code rely on interrupts? If yes, check that the app part re-enables them (but better listen to Alex and avoid disabling them before the jump) and that the VTOR reg is correct when the app is running already (some versions of ST’s SystemInit code are touching it).Peter-h, please, no 😁

author: peter-h
author_rank: Super Contributor
timestamp: December 26, 2023, 08:21:36 am
content: Most people leech from forums and never post the final working code (often employer-prohibited) whereas I do generally post the eventual solution

author: DavidAlfa
author_rank: Super Contributor
timestamp: December 26, 2023, 10:13:00 am
content: If the main app initializes everything, I would reset all peripherals and interrupts before jumping, it's pretty simple, see my 1st post.Or just perform the RCC resets in the main app before init.

author: peter-h
author_rank: Super Contributor
timestamp: December 26, 2023, 11:04:06 am
content: What is the "boot loader" actually doing? Is it the complete product i.e. one is trying to run everything from RAM (like one used to do in the Z80 days, for development) or is it just a bit of code which reads data (from a UART, SPI FLASH, etc) and write the data to the CPU FLASH? And what does this loader do when it is finished?If the latter, one is not looking at much code. This is how I do itCode:#ifdef ITM_DEBUGB_debug_puts("Entering Loader\n");#endif// This should not be neededSCB->VTOR = FLASH_BASE;// === At this point, interrupts and DMA must still be disabled ====// Execute loader. Reboots afterwards.// Parameters for loader are in the SSA.extern char _loader_ram_start;extern char _loader_ram_end;extern char _loader_flash_start;// Copy loader code and its init data to RAM. B_memcpy is a local memcpy() because we don't have access to stdlib at this point!!B_memcpy(&_loader_ram_start, &_loader_flash_start, &_loader_ram_end - &_loader_ram_start);// Set SP to top of CCM. This is not where the general stack is normally but it doesn't matter because// the loader always reboots at the end. This assignment can't be done inside loader because it trashes// the stack frame and any local variables which are allocated *at* the call.// NOTE local variables cannot be used after the SP load.asm volatile ("ldr sp, = 0x10010000 \n");// See comments in loader.c for why the long call.extern void loader_entry() __attribute__((long_call));loader_entry();// never get here (loader always reboots)for (;;);You have to be careful with stuff like memcpy because the compiler will try to detect code which looks like memcpy and will substitute it with a call to stdlib mempy which may not work, etc, etc, all dependent subtly on optimisation levels, etc, etc.So...Code:// Local version__attribute__((optimize("O0")))// prevent replacement with memcpy()static void B_memcpy (void *dest, const void *src, size_t len){char *d = dest;const char *s = src;while (len--)*d++ = *s++;}// Local version__attribute__((optimize("O0")))// prevent replacement with memset()static void B_memset(void *s, uint8_t c, uint32_t len){uint8_t * p=s;while(len--){*p++ = c;}}and my loader ends withCode:static inline void L_reboot(void){// Ensure all outstanding memory accesses including buffered write are completed before reset__ASM volatile ("dsb 0xF":::"memory");// Keep priority group unchangedSCB->AIRCR = (uint32_t)((0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |(SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |SCB_AIRCR_SYSRESETREQ_Msk );__ASM volatile ("dsb 0xF":::"memory");// wait until resetfor(;;){__NOP();}}I don't do anything with MSP. The CPU starts with a reset (obviously), then does some stuff, then copies the loader from flash to ram (above) then jumps to it, the loader does its stuff, and does a software reset.

author: Siwastaja
author_rank: Super Contributor
timestamp: December 26, 2023, 11:59:06 am
content: Is this "let's post our bootloaders?" If so, look at mine. This time, bigger isn't necessarily better:Code:static void __attribute__((naked)) start_app(uint32_t pc, uint32_t sp){__asm("msr msp, r1\n\bx r0\n");}static void __attribute__((noreturn)) boot(void* addr){SCB->VTOR = (uint32_t)addr;uint32_t sp = ((uint32_t*)addr);uint32_t pc = ((uint32_t*)addr);start_app(pc, sp);while(1);}void main(){uint32_t crc_a = crc32b((uint8_t*)APP_A, meta_a.fw_len*4);uint32_t crc_b = crc32b((uint8_t*)APP_B, meta_b.fw_len*4);int a_valid = meta_a.fw_len > 64 && meta_a.fw_len <= (APP_B-APP_A)/4 && meta_a.update_cnt <= META_LARGEST_UPDATE_CNT&& crc_a == meta_a.crc32;int b_valid = meta_b.fw_len > 64 && meta_b.fw_len <= (APP_B-APP_A)/4 && meta_b.update_cnt <= META_LARGEST_UPDATE_CNT&& crc_b == meta_b.crc32;boot_app_shared.a_crc = crc_a;boot_app_shared.b_crc = crc_b;boot_app_shared.a_valid = a_valid;boot_app_shared.b_valid = b_valid;if( (a_valid && b_valid && meta_a.update_cnt >= meta_b.update_cnt ) || // Both valid, A newer, or(a_valid && !b_valid)) // only A valid{boot_app_shared.bootloader_magic = MAGIC_BOOT_A;boot((void*)APP_A);}else if( (a_valid && b_valid && meta_a.update_cnt < meta_b.update_cnt ) || // Both valid, B newer, or(b_valid && !a_valid)) // only B valid{boot_app_shared.bootloader_magic = MAGIC_BOOT_B;boot((void*)APP_B);}else{// No valid app: eternal LED blinkerwhile(1){LED2_ON();delay_ms(100);LED2_OFF();delay_ms(100);}}}

author: abyrvalg
author_rank: Frequent Contributor
timestamp: December 26, 2023, 09:58:37 pm
content: Siwastaja, forgive my red teamer’s paranoia, no chance of CRCing a 4GB-1 size when one of the boot slots is empty due to meta_x.fw_len use before check?Totally agree with "smaller is better" for bootloaders. Make it as simple as possible, test thoroughly, don’t touch/update until really necessary (definitely no "run from RAM" nonsense on an RWW-capable MCU).

author: Siwastaja
author_rank: Super Contributor
timestamp: December 27, 2023, 10:28:52 am
content: Quote from: abyrvalg on December 26, 2023, 09:58:37 pmGreat demonstration of how beneficial it is to post code online! Thanks.

author: gpr
author_rank: Contributor
timestamp: December 28, 2023, 11:23:26 pm
content: Quote from: peter-h on December 26, 2023, 11:04:06 ambtw, why memcpy from stdlib doesn't work here? I encountered this in my code, it tried to jump to some strange non-existing address or something like that.I didn't find the reason and just replaced it with my implementation.

author: ejeffrey
author_rank: Super Contributor
timestamp: December 29, 2023, 05:14:21 am
content: Quote from: gpr on December 28, 2023, 11:23:26 pmmemcpy from stdlib should work fine as long as you are using a standard compile/link workflow to build the code. If you use link options like -nostartfiles and -nostdlib which completely disable runtime support you will need to provide your own versions of some builtins such as memcpy. The compiler may need to call memcpy for things like structure assignment so you really need to have it no matter what. If you link with these options but don't provide memcpy you will get link errors about undefined symbols. Linker error messages can be a bit obtuse, but it shouldn't be too hard to figure out what's going on.If you need to implement memcpy yourself you may have to seriously bully the compiler to avoid replacing part or all of it with calls to the expected built-in memcpy -- this can lead to unfortunate recursive loop that will quickly fault. Or just implement memcpy in assembly.peter's problem is that he was modifying the linker script to put some symbols (such as memcpy) into sections of the executable which didn't actually get loaded into the target (yet: it was part of the application code). Since the symbol was actually present at link time, it didn't cause a link error. If you don't do this, you won't have that problem.

author: peter-h
author_rank: Super Contributor
timestamp: December 29, 2023, 08:17:33 am
content: QuoteIf you are building a boot block project, say 32k, you probably won't have stdlib available.QuoteNot sure about thatThe situation was more complicated.QuoteYou give it a different name, and use the attribs I showed to make doubly sure.

author: eutectique
author_rank: Frequent Contributor
timestamp: December 29, 2023, 11:32:52 am
content: Quote from: gpr on December 28, 2023, 11:23:26 pmA map file could showwhat exactlyis linked into the binary.