一、问题描述
在智芯 SCM402F 芯片上,bootloader 如果按照 Debug 模式编译则无法成功引导 MS-RTOS,反之按照 Release 模式编译则可以正常引导。引导代码如下所示:
static void ms_boot_os(ms_addr_t *addr)
{
ms_func_t func = (ms_func_t)addr[1U];
ms_printk(MS_PK_NOTICE, "Start MS-RTOS...\n");
SysTick->CTRL = 0U;
__asm__ __volatile__ (
"MOV R13, %[sp]\n"
:
: [sp] "r" (addr[0])
: "cc");
func();
}
ms_boot_os() 函数传入 MS-RTOS 镜像基址 addr,基址指向向量表,向量表第一位是栈顶地址,第二位为复位入口。代码中首先保存 addr[1] 即复位入口到变量 func 中,再使用一段嵌入汇编将 R13(SP) 赋值为 addr[0]。最后调用 func(),即跳转到 MS-RTOS 的复位入口处。
二、问题分析
上述在逻辑上有一个很大的问题,我们先看一下 Debug 模式下的反汇编代码:
static void ms_boot_os(ms_addr_t *addr)
{
8007100: b500 push {lr}
8007102: b085 sub sp, #20
8007104: 9001 str r0, [sp, #4]
ms_func_t func = (ms_func_t)addr[1U];
8007106: 9b01 ldr r3, [sp, #4]
8007108: 3304 adds r3, #4
800710a: 681b ldr r3, [r3, #0]
800710c: 9303 str r3, [sp, #12]
ms_printk(MS_PK_NOTICE, "Start SW-RTOS...\n");
800710e: 4908 ldr r1, [pc, #32] ; (8007130 <ms_boot_os+0x30>)
8007110: 2005 movs r0, #5
8007112: f000 fc6d bl 80079f0 <ms_printk>
/*
* Disable system tick
*/
SysTick->CTRL = 0U;
8007116: 4b07 ldr r3, [pc, #28] ; (8007134 <ms_boot_os+0x34>)
8007118: 2200 movs r2, #0
800711a: 601a str r2, [r3, #0]
__asm__ __volatile__ (
"MOV R13, %[sp]\n"
:
: [sp] "r" (addr[0])
800711c: 9b01 ldr r3, [sp, #4]
800711e: 681b ldr r3, [r3, #0]
__asm__ __volatile__ (
8007120: 469d mov sp, r3
: "cc");
func();
8007122: 9b03 ldr r3, [sp, #12]
8007124: 4798 blx r3
}
在这段代码的最后,首先读取变量 func 的值到 r3,然后使用 blx 跳转到 r3 中保存的地址。这里因为 func 是局部变量,使用 SP 指针加偏移的方式获取 func 的值,但是在上一步中已经改变了 SP 的值,因此这里无法正确获取 func 变量的值。所以无法成功引导 MS-RTOS,如下图所示。
分析了 Debug 版本下无法引导的原因后又引出了另一个问题,为什么 Release 可以成功引导呢?同样我们先看一下 Release 模式下的反汇编:
ms_func_t func = (ms_func_t)addr[1U];
800445e: 4c1b ldr r4, [pc, #108] ; (80044cc <main+0x3a4>)
ms_printk(MS_PK_NOTICE, "Start SW-RTOS...\n");
8004460: 4f1b ldr r7, [pc, #108] ; (80044d0 <main+0x3a8>)
SysTick->CTRL = 0U;
8004462: 4e1c ldr r6, [pc, #112] ; (80044d4 <main+0x3ac>)
ms_func_t func = (ms_func_t)addr[1U];
8004464: 6865 ldr r5, [r4, #4]
ms_printk(MS_PK_NOTICE, "Start SW-RTOS...\n");
8004466: 4639 mov r1, r7
8004468: 2005 movs r0, #5
800446a: f000 f9b9 bl 80047e0 <ms_printk>
SysTick->CTRL = 0U;
800446e: 2300 movs r3, #0
8004470: 6033 str r3, [r6, #0]
__asm__ __volatile__ (
8004472: 6823 ldr r3, [r4, #0]
8004474: 469d mov sp, r3
func();
8004476: 47a8 blx r5
从这段反汇编可以看出,Release 版本中编译器识别到了 func 变量除了初始化和读取之外没有其它的操作,因此优化了这个变量。这里直接使用 r4 保存了 MS-RTOS 基址,即 ms_boot_os() 传入的参数,宏 MS_CFG_BOOT_OS_BASE,如下图所示。
紧接着使用 r5 保存基址的第二位,即复位入口。并在更新 SP 后跳转到 r5 中保存的地址上,因为此时获取复位入口的方式并不依赖 SP 指针,因此 Release 版本可以正常跳转到 MS-RTOS。如下图所示。
三、解决方案
从上述的分析可以看出,无法成功引导的原因是由于局部变量取值依赖 SP 指针,因此在更新 SP 指针后就无法正确读取局部变量的值了。ms_boot_os() 中启动 MS-RTOS 的逻辑是存在问题的,Release 可以成功启动也只是因为优化而产生的巧合而已。这里可以修改为不使用局部变量,直接使用一个寄存器保存复位入口,就可以避免这个问题。如下所示。