Course contentsShow
← Embedded Systems & Computer Architecture
Lesson 484 of 9366. Embedded Software Construction and ToolchainsFree lesson

Debugging Bootloader and Application Transitions

Learners will use hardware debuggers and trace logs to inspect register states and memory contents during the jump from bootloader to application.

Debugging Bootloader and Application Transitions

In this lesson, you will learn how to verify the integrity of the firmware handoff by using hardware debuggers to inspect CPU state during the jump from your bootloader to the application image.

The transition from a bootloader to an application is a critical juncture where many systems fail. This jump typically involves updating the Program Counter (PC) and Stack Pointer (SP) to point to the application’s memory space. Because this process happens at the machine code level, simple print statements are often insufficient—or impossible—if the system crashes immediately.

Inspecting State at the Handoff

To debug this transition, you must bridge the gap between your bootloader’s final instructions and the application’s entry point. Set a hardware breakpoint at the exact address where the jump instruction occurs (e.g., the line where you dereference the application's vector table).

When the debugger hits the breakpoint, perform these steps:

  1. Verify the Stack Pointer: Examine the SP register. It should match the initial stack pointer value defined in the application's vector table (usually the first entry in the image header). If it points to the bootloader’s stack or a random memory region, the application will trigger a HardFault immediately upon execution.
  2. Inspect the Program Counter: Ensure the PC is being loaded with the address of the application's Reset Handler.
  3. Check Memory Permissions: Use your debugger to read the memory location of the application entry point. If the read returns 0xFF or 0x00, the flash programming process failed or the memory partition is not mapped as expected.
// Example transition logic
void jump_to_app(uint32_t app_addr) {
    uint32_t app_stack = *(volatile uint32_t*)app_addr;
    uint32_t app_reset = *(volatile uint32_t*)(app_addr + 4);

    // Set breakpoint here to inspect registers
    __set_MSP(app_stack);
    ((void (*)(void))app_reset)();
}

If the system hangs, use trace logs to identify which peripherals were left in an active state. Often, the bootloader’s interrupt vectors are still active, leading to unexpected ISR calls before the application can initialize its own vector table. Always disable global interrupts and clear pending interrupts before executing the jump.

Key Takeaway: A successful transition requires the atomic synchronization of the Stack Pointer and Program Counter to the application's vector table, verified by inspecting CPU registers at the exact moment of the jump.