笔者接着来介绍一下Bootloader的跳转代码以及优化
1、跳转代码理解
跳转代码可能要涉及到芯片架构的知识,要跳转到对应的位置,还要设置相关的SP 堆栈指针,具体可以参考笔者这篇文章BootLoader的理解与实现。
STM32的跳转代码如下所示:
u32 ApplicationAddress = 0x08008000; //app 地址
typedef void (*pFunction)(void); //函数指针
void Jump_Used_Main(void)
{
printf("\r\nboot2-------- Jump APP --------- \r\n");
/* 判断栈顶 是否位于 0x20000000 128K */
if ( ((*(__IO uint32_t*)ApplicationAddress) >= 0x20000000) &&((*(__IO uint32_t*)ApplicationAddress) <= 0x20010000))
{
JumpAddress = *(__IO uint32_t*) (ApplicationAddress +4);
Jump_To_Application = (pFunction) JumpAddress;
/*close the interrupt*/
_disable_interrupr();
__set_MSP(*(__IO uint32_t*) ApplicationAddress);
Jump_To_Application();
}
else
{
while(1)
{
printf("\r\nboot2 error\r\n");
}
}
}
可以看到__set_MSP(* (__IO uint32_t *) ApplicationAddress);这行代码中,在取地址里面的内容时,增加了__IO的选项,
#define __IO volatile
保证是从内存里面读出来的SP栈指针的数据,然后设置到MSP,否则可能导致SP设置错误,程序跑飞。
2、跳转代码编译优化
笔者在实际开发的过程中,遇到了一个跳转过去就崩掉的情况,单步调试,发现到Set_MSP就崩掉了,很是奇怪,通过汇编一查看,就很明显了。
- NXP LPC的单片机,
- arm-noen-eabi-gcc的编译器。
typedef struct addr_manager_struct
{
xxxxxxx;
u32 image_load_addr;
u32 image_exec_addr;
}addr_manager_t;
u8 main_jump(addr_manager_t* info)
{
u32 *image_addr = (u32*)info->image_load_addr;
u32 *sp = (u32*) image_addr [0];
u32 *jump = (u32*) image_addr [1];
disable_interrupt();
set_msp((u32)sp);
(*jump)();
return 0;
}
- 上述在设置完SP之后,放到r2寄存器里面,
- 然后获取jump地址,放到r3里面,
- 之后禁止中断修改了r2,然后禁止中断
- 然后就把r2传到SP里面,
- 造成跑飞,可能是一个不存在的地址
有人说sp设置的时候需要加volatile,即使改成下面的函数,也没用效果
void jump_main(addr_manager_t* info)
{
u32 *image_addr = (u32*)info->image_load_addr;
u32 *sp = (u32*) image_addr [0];
u32 *jump = (u32*) image_addr [1];
disable_interrupt();
set_msp((volatile u32)sp);
(*jump)();
}
接着再继续改,直
- 接通过Image指针取内容,然后去获取到SP值,然后就可以了,
- 看来是禁止中断的这个函数所影响,
- 禁止中断如果不报存返回值,则不会对SP的值产生影响
void jump_main(addr_manager_t* info)
{
u32 *image_addr = (u32*)info->image_load_addr;
u32 *sp = (u32*) image_addr [0];
u32 *jump = (u32*) image_addr [1];
disable_interrupt();
set_msp(*((volatile u32*)image_addr));
(*jump)();
}
接着我们再尝试一种方法,就是将禁止中断函数移动位置,看一下情况,发现也是可以的。
void jump_main(addr_manager_t* info)
{
disable_interrupt();
u32 *image_addr = (u32*)info->image_load_addr;
u32 *sp = (u32*) image_addr [0];
u32 *jump = (u32*) image_addr [1];
set_msp((u32)sp);
(*jump)();
}
然后我们看一下set sp的汇编函数,也并没有指明操作的寄存器,很可能是GCC编译器优化的bug。
inline void set_sp(u32 sp_value)
{
asm volatile ("msr msp, %0" : "=r"(sp_value))
}
然后我们再看看armcc编译器的结果,没有任何问题。
- armcc对于禁止中断的返回值没有处理,
- 而GCC处理了然后导致了问题(将其中断保存值填到r2里面,然后r2的sp值被覆盖),
- 其实应用层如果没有对返回值进行处理,即使函数有返回值,编译器优化会将其处理掉。显然GCC的编译器优化做的还是差一点。