为什么在从引导加载程序跳转到应用程序之前需要更新堆栈指针
Why stack pointer need to be updated before jumping to application from bootloader
从引导加载程序跳转到应用程序时,我们通常将堆栈指针更新为应用程序堆栈指针,然后将程序计数器更新为应用程序的Reset_Handler。
void jump_to_application(void)
{
/* Function pointer to the address of the user application. */
fnc_ptr_for_app jump_to_app;
jump_to_app = (fnc_ptr_for_app)(*(volatile uint32_t*) (FLASH_APP_START_ADDRESS+4u));
__CRC_CLK_DISABLE();
HAL_DeInit();
/* Change the main stack pointer. */
__set_MSP(*(volatile uint32_t*)FLASH_APP_START_ADDRESS);
jump_to_app();
}
Reset_Handler的第一行代码如下,将栈指针初始化为自己的栈指针值
Reset_Handler:
ldr sp, =_estack /* Atollic update: set stack pointer */
/* Copy the data segment initializers from flash to SRAM */
movs r1, #0
b LoopCopyDataInit
但仍然建议使用应用程序的指针更新堆栈指针。
为什么需要它,不更新就直接跳转会有什么副作用
在ARM Cortex-M中,初始堆栈指针存储在向量的前4个字节table,初始程序计数器存储在后4个字节。复位时,硬件 从这 8 个字节加载 SP 和 PC。例如,这允许用 C 而不是汇编程序编写 Cortex-M 启动代码。
您的引导加载程序模拟此 reset 行为,通过将硬件取消初始化到其重置状态,并从 应用程序的 [=29] 加载 SP 和 PC =]向量table。这允许应用程序启动 就好像 它是从重置启动的,而不依赖于引导加载程序的任何初始化或设置。
引导加载程序与应用程序分开编译和链接,并且必须能够加载任何具有适当起始地址的应用程序代码。因此,引导加载程序无法确定或强制加载的任何应用程序代码将设置堆栈指针,因为它可能合理地假设硬件已经设置了它。同样在这种情况下, _estack != *(volatile uint32_t*)FLASH_APP_START_ADDRESS)
在任何情况下都是完全可能的。
对您的引导加载程序的一个观察是,它不会将矢量 table 设置为应用程序的矢量,这有潜在的危险 - 如果启动代码在没有先设置矢量的情况下启用中断 table,它可能会导致错误地调用引导加载程序中断处理程序。这样会更安全:
// Switch vector table
SCB->VTOR = APPLICATION_START_ADDR ;
在 jump_to_app()
之前。或者,如果您选择使用 HAL:
NVIC_SetVectorTable( NVIC_VectTab_FLASH, APPLICATION_START_ADDR ) ;
从引导加载程序跳转到应用程序时,我们通常将堆栈指针更新为应用程序堆栈指针,然后将程序计数器更新为应用程序的Reset_Handler。
void jump_to_application(void)
{
/* Function pointer to the address of the user application. */
fnc_ptr_for_app jump_to_app;
jump_to_app = (fnc_ptr_for_app)(*(volatile uint32_t*) (FLASH_APP_START_ADDRESS+4u));
__CRC_CLK_DISABLE();
HAL_DeInit();
/* Change the main stack pointer. */
__set_MSP(*(volatile uint32_t*)FLASH_APP_START_ADDRESS);
jump_to_app();
}
Reset_Handler的第一行代码如下,将栈指针初始化为自己的栈指针值
Reset_Handler:
ldr sp, =_estack /* Atollic update: set stack pointer */
/* Copy the data segment initializers from flash to SRAM */
movs r1, #0
b LoopCopyDataInit
但仍然建议使用应用程序的指针更新堆栈指针。
为什么需要它,不更新就直接跳转会有什么副作用
在ARM Cortex-M中,初始堆栈指针存储在向量的前4个字节table,初始程序计数器存储在后4个字节。复位时,硬件 从这 8 个字节加载 SP 和 PC。例如,这允许用 C 而不是汇编程序编写 Cortex-M 启动代码。
您的引导加载程序模拟此 reset 行为,通过将硬件取消初始化到其重置状态,并从 应用程序的 [=29] 加载 SP 和 PC =]向量table。这允许应用程序启动 就好像 它是从重置启动的,而不依赖于引导加载程序的任何初始化或设置。
引导加载程序与应用程序分开编译和链接,并且必须能够加载任何具有适当起始地址的应用程序代码。因此,引导加载程序无法确定或强制加载的任何应用程序代码将设置堆栈指针,因为它可能合理地假设硬件已经设置了它。同样在这种情况下, _estack != *(volatile uint32_t*)FLASH_APP_START_ADDRESS)
在任何情况下都是完全可能的。
对您的引导加载程序的一个观察是,它不会将矢量 table 设置为应用程序的矢量,这有潜在的危险 - 如果启动代码在没有先设置矢量的情况下启用中断 table,它可能会导致错误地调用引导加载程序中断处理程序。这样会更安全:
// Switch vector table
SCB->VTOR = APPLICATION_START_ADDR ;
在 jump_to_app()
之前。或者,如果您选择使用 HAL:
NVIC_SetVectorTable( NVIC_VectTab_FLASH, APPLICATION_START_ADDR ) ;