Problem with Jumping from Bootloader to Apploader with cortex_m::asm::bootload
Opened this issue · 2 comments
Hi everyone,
I’m working on an embedded Rust project using Embassy, cortex-m, and a STM32L471RG. I’ve set up a bootloader and an apploader, but I’m running into a strange issue when trying to jump from the bootloader to the apploader.
Here’s the function I’m using to perform the jump:
fn jump_to_apploader() -> ! {
unsafe {
let start = &__apploader_origin as *const u32 as u32;
info!("booting to apploader at 0x{:X}", start);
let p = cortex_m::Peripherals::steal();
p.SCB.vtor.write(start);
cortex_m::asm::bootload(start as *const u32)
}
}
When I call this, the code completely stops running — it doesn’t execute the apploader, and I don’t even hit my panic handler. It’s just stuck.
Here is my panic handler for reference:
#[cfg(target_os = "none")]
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
use defmt::error;
error!("{}: {}", info, defmt::Display2Format(&info.message()));
cortex_m::peripheral::SCB::sys_reset();
}
So far:
The jump address (__apploader_origin) is set correctly in the linker script.
The log message shows the expected address before jumping.
After cortex_m::asm::bootload, the MCU seems to hang completely — no reset, no panic, no debug logs.
Has anyone faced a similar issue before?
Could this be related to stack pointer initialization, interrupts still enabled, or something about how bootload expects the vector table?
Any hints or debugging approaches would be greatly appreciated!
- Can you print the vector table at
__apploader_originto verify that it is correct? - Perhaps you have a hard fault, but you set VTOR to point to the new table, so you'd end up in the new HardFault handler. But if the address is wrong and the table is garbage, you'd bounce off to nowhere. You could try not setting VTOR and see if you at least enter the new code?
- To debug this I would probably be single stepping in GDB, and printing the full CPU state as often as possible.
- (As a nit, you can use
core::ptr::addr_of!to avoid taking a reference to the linker supplied symbol)
Thank you very much for your reply!
The tricky part of this problem is that it does not happen consistently — sometimes the jump works perfectly, and sometimes the MCU hangs. Unfortunately, I cannot easily debug this with a ST-Link because when I try to perform an over-the-air firmware update (using the apploader), having the ST-Link connected always breaks the update process and changes the behavior.
To properly reproduce the issue and observe the transition from bootloader to apploader, I have to disconnect the ST-Link after flashing both the bootloader and the apploader. For debugging, I’m currently sending logs over Ethernet at strategic points in both the bootloader and the apploader to see where it hangs.
Following your advice, I added a function in the bootloader to dump the vector table at __apploader_origin before jumping:
async fn jump_to_apploader_feedback() {
let start = unsafe { &__apploader_origin as *const u32 as u32 };
let mut feedback = String::<128>::new();
write!(feedback, "apploader origin: 0x{:X}\r", start).unwrap();
send_feedback(&feedback, true).await.unwrap();
for i in 0..10 {
let entry = unsafe { core::ptr::read_volatile((start + i * 4) as *const u32) };
let mut feedback = String::<128>::new();
write!(feedback, "Vector[{}] = 0x{:08X}\r", i, entry).unwrap();
send_feedback(&feedback, true).await.unwrap();
}
}
Here is what I get:
[bootloader] apploader origin: 0x8018800
[bootloader] Vector[0] = 0x20017C00
[bootloader] Vector[1] = 0x08018989
[bootloader] Vector[2] = 0x08025519
[bootloader] Vector[3] = 0x0802F461
[bootloader] Vector[4] = 0x08025519
[bootloader] Vector[5] = 0x08025519
[bootloader] Vector[6] = 0x08025519
[bootloader] Vector[7] = 0x00000000
[bootloader] Vector[8] = 0x00000000
[bootloader] Vector[9] = 0x00000000
I don’t see any significant difference in this table between the cases where it works and where it hangs.
Also, regarding your suggestion: if I skip p.SCB.vtor.write(start); the jump never works — the apploader does not start at all in that case.
A couple of hypotheses I have, but cannot yet confirm:
Pending interrupts might still be active when I perform the jump, which could lead to unexpected behavior after changing VTOR.
Or, a HardFault may occur just before the bootload happens: I rewrite the vector table, but the fault occurs while still in the old application, leaving the MCU pointing to an invalid vector.