从引导加载程序跳转到应用程序之前设置堆栈指针

h_e*_*sek 5 c arm stm32 bootloader cortex-m

我正在为 Nucleo-F429ZI 编写引导加载程序。我有两个不同的 STM32 项目,一个用于引导加载程序本身,另一个用于从引导加载程序跳转的应用程序。

引导加载程序的链接器脚本

MEMORY
{
  CCMRAM    (xrw)    :  ORIGIN = 0x10000000,   LENGTH = 64K
  RAM    (xrw)    :     ORIGIN = 0x20000000,   LENGTH = 32K
  FLASH    (rx)    :    ORIGIN = 0x8000000,   LENGTH = 32K
}   
Run Code Online (Sandbox Code Playgroud)

应用程序的链接器脚本

_estack = ORIGIN(RAM) + LENGTH(RAM);
MEMORY
{
  CCMRAM    (xrw)    :  ORIGIN = 0x10000000,   LENGTH = 64K
  RAM    (xrw)    :     ORIGIN = 0x20000000,   LENGTH = 192K
  FLASH    (rx)    :    ORIGIN = 0x8008000,   LENGTH = 64K
}   
Run Code Online (Sandbox Code Playgroud)

我没有忘记设置应用程序的闪光偏移。

system_stm32f4xx.c(在应用程序项目中)

#define VECT_TAB_BASE_ADDRESS   FLASH_BASE   // 0x8000000
#define VECT_TAB_OFFSET         0x00008000U 
Run Code Online (Sandbox Code Playgroud)

意法半导体关于bootloaders的教程有如下代码跳转

main.c(在引导加载程序项目中)

#define FLASH_APP_ADDR 0x8008000
typedef void (*pFunction)(void);
uint32_t JumpAddress;
pFunction Jump_To_Application;
void go2APP(void)
{
  JumpAddress = *(uint32_t*)(FLASH_APP_ADDR + 4);
  Jump_To_Application = (pFunction) JumpAddress;
  __set_MSP(*(uint32_t*)FLASH_APP_ADDR); // in cmsis_gcc.h 
  Jump_To_Application();
}
Run Code Online (Sandbox Code Playgroud)

cmsis_gcc.h(在引导加载程序项目中)

__STATIC_FORCEINLINE void __set_MSP(uint32_t topOfMainStack)
{
  __ASM volatile ("MSR msp, %0" : : "r" (topOfMainStack) : );
}
Run Code Online (Sandbox Code Playgroud)

可以看到,__set_MSP函数在跳转到FLASH_APP_ADDR + 4之前设置了主堆栈指针。我通过调试找到了目标位置的内存位置。FLASH_APP_ADDR + 4导致运行应用程序项目的Reset_Handler函数。让我们看看会执行什么。

startup_stm32f429zitx.c(在应用程序项目中)

    .section  .text.Reset_Handler
  .weak  Reset_Handler
  .type  Reset_Handler, %function
Reset_Handler: 
  ldr   sp, =_estack       /* set stack pointer */
 
/* Copy the data segment initializers from flash to SRAM */  
  ldr r0, =_sdata
  ldr r1, =_edata
  ldr r2, =_sidata
  movs r3, #0
  b LoopCopyDataInit
Run Code Online (Sandbox Code Playgroud)

Reset_Handler 所做的第一件事是设置堆栈指针。_estack 在链接描述文件中定义。

如果 Reset_Handler 正在设置堆栈指针,为什么我们要调用 __set_MSP 函数?我删除了函数 __set_MSP,引导过程仍然有效。不过,我检查了一些其他引导加载程序代码,发现了完全相同的逻辑。

我尝试了我所说的,但找不到解释。

小智 1

Cortex-M 内核在启动序列期间SP从地址加载寄存器的初始值。FLASH_BASE+0然后从地址跳转到代码入口点(重置向量)FLASH_BASE+4任何引导加载程序代码都会模仿核心行为。请注意,FLASH_BASE这里不一定是实际的闪存基础,而是一个抽象值,这取决于所使用的处理器及其设置。

提供的Reset_Handler代码使用 __estack(主堆栈顶部)值加载 sp 寄存器,但并非必须如此!引导加载程序不能期望主程序执行此操作,但在复位后会执行与内核相同的引导顺序。这样,主代码就不必依赖于了解谁启动了它 - 核心、引导加载程序、jtag 或其他东西。

我见过启动代码,它不会加载SP,但会使用第一条指令禁用中断。或者用 C 语言编写的启动代码,它可以将堆栈与第一条指令一起使用。

这里真正的问题可能是:如果这个启动代码已经加载了 SP,为什么还要加载 SP?但也许应该将其转发给原始代码作者。