1. 问题现象:跳转代码执行了,APP却纹丝不动
先说我遇到的具体情况,方便大家对号入座。某项目用的是STM32F429IGT6,Flash 1MB,RAM 256KB,自己写的bootloader放在0x08000000起始的64KB区域,APP从0x08010000开始。上位机通过串口把固件包发给bootloader,校验通过后擦除APP区、写入固件,一切正常。但复位后执行跳转语句时,程序就是卡死在跳转点附近,或者更奇怪——跳转指令看起来执行了(用调试器单步能看到PC指针变了),但APP里的代码就是不跑,或者跑起来就进HardFault。
这个问题的典型表现可以归结为三种:
- 跳转后直接进HardFault,调试器停在0x08010000附近的某个地址。
- 跳转后没有任何反应,看门狗复位,然后又回到bootloader。
- 跳转后程序跑到一个奇怪的地址,比如0xFFFFFFFF或者0x08000000,反复重启。
如果你是第一次写STM32的IAP功能,遇到这类问题大概率会怀疑是跳转代码写错了。但实际上,跳转代码本身非常简单,就那么三四行,真正出问题的地方往往藏在工程配置、编译选项、启动文件细节这些"看不见的地方"。
这篇文章我会把排查跳转失败的完整思路、相关原理和实测有效的解决方案都整理出来,按从简到繁的顺序来,方便你跟着一步步定位问题。
2. 前置认知:跳转为什么不是简单的"goto"
在开始排查之前,必须先搞清楚一个底层事实:从bootloader跳转到APP,本质上不是普通函数调用,而是处理器异常向量表的重新映射。这个认知决定了后续所有排查方向。
2.1 从向量表和栈指针说起
Cortex-M4内核(STM32F429用的就是M4F)上电后,硬件自动从向量表起始地址读取两个关键值:
- 地址偏移0x0000处的值:初始栈指针(MSP),加载到SP寄存器。
- 地址偏移0x0004处的值:复位中断向量(Reset_Handler),加载到PC寄存器。
所以处理器"运行"APP,本质上是告诉它:新的向量表在哪,新的栈顶在哪,第一条要执行的代码在哪。这三者分别对应:
- SCB->VTOR:向量表偏移寄存器,告诉内核向量表搬到哪了。
- APP工程链接脚本里的RAM起始地址和栈大小:决定初始栈指针。
- APP的Reset_Handler地址:决定PC从哪开始跑。
跳转代码做的事情就是手动模拟上电复位的过程:先把MSP设成APP向量表第一个字,再把PC设成APP向量表第二个字,同时把VTOR指到APP向量表开头。任何一步出问题,APP都跑不起来。
2.2 为什么不能用函数指针直接"调用"APP
很多新手写跳转是这样写的:
void jump_to_app(uint32_t app_addr) { uint32_t app_entry = *(volatile uint32_t *)(app_addr + 4); void (*jump)(void) = (void (*)(void))app_entry; jump(); }看起来逻辑没问题:取出APP的Reset_Handler地址,转成函数指针,调用。但这段代码实际运行的时候会有隐患——跳转前没有关中断、没有设置MSP、没有设置VTOR。如果APP编译时默认从0x08010000开始,而它的启动文件里向量表偏移没有配置,APP的异常处理入口都在0x08000000(bootloader的向量表),中断一来,PC直接跳回bootloader的中断处理函数,栈和变量状态全乱了,表现就是各种诡异死机。
所以一个合格的跳转过程必须是:
#define APP_FLASH_ADDR 0x08010000 #define APP_STACK_ADDR (*(__IO uint32_t *)APP_FLASH_ADDR) #define APP_ENTRY_ADDR (*(__IO uint32_t *)(APP_FLASH_ADDR + 4)) typedef void (*pFunction)(void); void jump_to_app(void) { pFunction app_entry; uint32_t app_stack_addr; uint32_t app_entry_addr; __disable_irq(); // 跳转前必须先关全局中断 app_stack_addr = APP_STACK_ADDR; app_entry_addr = APP_ENTRY_ADDR; if ((app_stack_addr & 0x2FFE0000) != 0x20000000) // 栈指针合法性检查 { return; // 栈指针不在RAM区,APP数据不完整 } if ((app_entry_addr & 0xFFF00000) == 0x08000000) // 入口地址必须在Flash区 { SCB->VTOR = APP_FLASH_ADDR; // 重映射向量表 __set_MSP(app_stack_addr); // 设置主栈指针 app_entry = (pFunction)app_entry_addr; app_entry(); // 跳转 } }这段代码是修正后的完整版本。最开始我遇到问题时,用的还是那个"简化版",跳转失败几乎是必然的。后面我把排查过程中每个环节踩过的坑都展开讲一遍。
3. 最容易被忽略的元凶:APP没有偏移向量表
这是所有跳转失败案例中占比最高的原因,我自己遇到的第一起跳转失败就是它。现象很典型:bootloader里单步执行能看到PC已经指向APP的Reset_Handler,但一进APP就HardFault,或者直接跑飞。
3.1 原因分析:VTOR配置缺失
APP工程的启动文件(startup_stm32f429xx.s)里,默认的复位处理流程是:
- 调用SystemInit(),其中会设置VTOR = 0x08000000(默认Flash起始地址)。
- 调用__libc_init_array初始化C运行环境。
- 调用main()。
关键就在SystemInit()。STM32标准库和HAL库的SystemInit()函数里,有一段条件编译:
#if (__VTOR_PRESENT == 1) && (__NVIC_PRIO_BITS == 4) SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; #else SCB->VTOR = FLASH_BASE; #endif其中VECT_TAB_OFFSET定义在system_stm32f4xx.h里,默认值是0。如果你的APP工程没有把VECT_TAB_OFFSET改成APP实际偏移量(0x10000),那么APP启动时会把VTOR重新写成0x08000000,指向bootloader的向量表。
结果就是:跳转那一刻VTOR是对的(指向APP),但APP执行SystemInit()时,又把VTOR改回bootloader的向量表了。之后任何一个中断发生,CPU都去bootloader的向量表里找中断处理函数,而bootloader的向量表里,对应位置可能是空的(bootloader没使能那些外设中断),或者干脆是0xFFFFFFFF,直接HardFault。
3.2 正确配置方式
在APP工程里,根据启动文件是否在跳转前手动设置了VTOR,有两种做法:
做法一:APP工程里修改VECT_TAB_OFFSET
在system_stm32f4xx.h里修改:
#define VECT_TAB_OFFSET 0x00010000U // APP起始地址相对于Flash基地址的偏移这种做法最省事,只要APP的链接脚本也对应改了,就没问题。但有个细节要注意:有些bootloader在跳转前已经设置了VTOR,APP的SystemInit()再设置一次也是安全的,因为设置的是同一个值,不会冲突。
做法二:跳转前设置VTOR,且APP不覆盖
如果APP工程用的是自己写的启动流程,或者不想改HAL库文件(比如用CubeMX重新生成代码会被覆盖),可以在APP的main()函数最开始强制设置一次:
SCB->VTOR = 0x08010000;放在SystemInit()之后、外设初始化之前。
3.3 检查方法
怎么确认是不是这个问题?两个手段:
- 看反汇编:在APP的Reset_Handler里,查有没有对SCB->VTOR(地址0xE000ED08)的写操作。有的话,看写入的值是不是0x08010000。
- +调试器读寄存器:跳转后在调试器里读SCB->VTOR的值。如果显示0x08000000,而APP地址是0x08010000,那就是这个原因没跑了。
我第一次排查时,就是用第二种方法定位的——当时在跳转语句处打断点,执行完跳转后再暂停,读VTOR发现还是0x08000000,马上就知道问题出在APP的SystemInit()里。
4. 栈指针问题:MSP设置不当导致的"灵异现象"
第二个高频原因,是跳转前没有正确设置主栈指针,或者APP的起始栈指针本身就不对。这类问题的表现比较有迷惑性,它不一定马上崩,可能在跑了一段代码后才随机死机。
4.1 MSP与散乱执行的执行关系
Cortex-M4有两个栈指针:MSP(主栈指针)和PSP(进程栈指针)。裸机环境下默认用的是MSP,中断和线程模式都用它。上电复位时,硬件自动从向量表偏移0处加载MSP的值。跳转时,如果代码里没显式调用__set_MSP(),那么MSP保持的还是bootloader的栈顶。
假设bootloader的栈顶在0x20020000,APP在链接脚本里把RAM布局改了,栈顶定义在0x2001C000。跳转到APP后,APP的局部变量、函数调用压栈都用0x20020000这块区域。如果这块区域被APP的全局变量占用了,变量会被栈操作践踏,结果就是"看起来执行正常的诡异问题"——某个全局变量莫名其妙变值,某个函数返回后PC跳飞。
4.2 另外一个隐蔽问题:栈指针向量的值本身非法
APP的向量表第一个字必须是合法的RAM地址。STM32F429的RAM是0x20000000起始,大小按型号从192KB到256KB不等。如果固件写入Flash时损坏了,或者APP工程的链接脚本把栈顶定义错了(比如写到了ROM区域),跳转后MSP就是个非法地址。这时候执行第一条指令可能没事,但第一次压栈、第一次函数调用就直接总线错误。
所以跳转前三重检查是必要的:
if ((app_stack_addr & 0x2FFE0000) != 0x20000000) { // 栈指针不在RAM范围,拒绝跳转 return; } if ((app_entry_addr & 0xFFF00000) != 0x08000000) { // 入口地址不在Flash范围,拒绝跳转 return; }这个检查思路来自产品级的IAP设计。虽然看起来多写了几行,但能避开大量"固件包损坏导致变砖"的售后问题。bootloader里固件校验不通过时,最安全的行为就是停在bootloader等待重新升级,而不是带着损坏的固件跳转。
4.3 跳转后死在第一条语句的特殊场景
有一种情况特别坑:跳转代码写对了、VTOR也设置了、MSP也设置了,但APP进入main()后,第一条语句就HardFault。用调试器单步,能进main,然后死在第一个操作外设的语句。
这个往往是时钟配置问题。APP的SystemInit()里会重新配置RCC(复位时钟控制器),而bootloader已经把系统时钟调到了180MHz。如果APP的SystemInit()里对PLL(锁相环)配置做了修改,但没等PLL稳定就切过去,会导致系统时钟切换异常。不过这个问题不属于"跳转失败"的核心范围,我提一句是提醒你排查顺序:先把跳转本身跑通,再管时钟配置。
5. 中断与外设残留:跳转前没擦干净"屁股"
第三个大坑,是bootloader在运行过程中使能了外设和中断,跳转前没有关干净。这一类问题的隐蔽性最强,因为它在bootloader里运行一切正常,跳转那一刻也不报错,但APP运行一段时间后就开始各种异常。
5.1 中断残留的连锁反应
bootloader用了串口(比如USART1接收升级数据)、用了定时器(比如做超时判断)、还可能用了DMA。这些外设的中断使能位在NVIC里置1了。跳转到APP后,如果APP没有重新初始化这些外设(或者初始化的顺序问题),中断依然会触发。但这时候中断向量表已经指向APP的向量表。如果APP的对应中断向量是空的(因为它的启动代码里没使能该中断,所以向量表里可能是默认的handler,或者0),就会出问题。
更麻烦的是:调频后的外设状态残留。比如bootloader开了DMA传输,跳转时DMA还挂在总线上,APP里如果恰好访问了同一片内存,数据错乱几乎是必然的。
所以跳转前,除了__disable_irq(),还得做一轮"外设复位和中断清理":
void system_reset_before_jump(void) { __disable_irq(); // 关闭所有外设时钟(在RCC层面关闭,外设不再响应) RCC->AHB1ENR = 0; RCC->AHB2ENR = 0; RCC->APB1ENR = 0; RCC->APB2ENR = 0; // 清除所有NVIC使能位 for (int i = 0; i < 8; i++) { NVIC->ICER[i] = 0xFFFFFFFF; // 清除使能 NVIC->ICPR[i] = 0xFFFFFFFF; // 清除挂起 } // 关闭SysTick SysTick->CTRL = 0; // 关闭全部中断(保险) __disable_irq(); }注意两次调用__disable_irq()不是写错了。第一次在清NVIC之前调用,是为了避免"清挂起寄存器"的过程中有新的中断进来。第二次是在所有清理完成后,确保全局中断始终处于关闭状态,直到APP重新配置完中断再开。不过实际上,只要第一次调用了__disable_irq(),第二次是冗余的——我习惯写上,图个心安,你按实际情况取舍。
5.2 SysTick的特殊处理
SysTick(系统节拍定时器)的坑非常隐蔽。很多RTOS或者裸机轮询框架用SysTick做延时。bootloader里如果调用了HAL_Delay()或者自己写的delay函数,SysTick中断是使能的。跳转时如果不清SysTick,APP启动后,SysTick中断随时可能触发。如果APP用SysTick做系统时钟,那还得看它自己初始化的时机对不对。
更闹心的是,如果bootloader里的SysTick中断处理函数还在执行,而跳转发生时它正好在中断中——Cortex-M4的中断现场保护在跳转后没有恢复机制,APP的启动流程可能直接崩。
处理方式很简单:跳转前把SysTick->CTRL清零,并清除SysTick挂起标志(写SysTick->VAL为0)。
5.3 一个真实排查案例的完整链路
某开发者当时遇到的现象是:bootloader通过USB HID升级,跳转后APP能启动(OLED显示正常),但一按按键(外部中断)就死机。排查链路是这样的:
- 先怀疑APP的EXTI配置,读了EXTI->IMR和NVIC->ISPR,发现EXTI中断确实在bootloader里被使能过,但是APP初始化EXTI时没有清除挂起标志。
- 再查发现,跳转前没有清NVIC->ICPR。bootloader里有一个按键长按进入升级模式的功能,使用了EXTI0中断,跳转时中断挂起位为1。APP启动后,按键中断一旦使能,NVIC立刻触发挂起中断,但APP的中断向量表里EXTI0_IRQHandler地址是0xFFFFFFFF(因为没定义),于是直接HardFault。
- 修复方案:跳转前调用NVIC_ClearPendingIRQ(EXTI0_IRQn),并在bootloader全局清理NVIC挂起位。修完就正常了。
这个案例说明,跳转失败的根因不一定是"跳转那几行代码",而是之前一整段bootloader生命周期内留下来的"状态垃圾"。排查时要跳出跳转函数本身,看看bootloader在跳转前干了什么。
6. 链接脚本与编译选项:APP自己就没"准备好"
这类原因最容易被忽略,因为它不在运行链路里,而是在构建阶段就埋下的炸弹。
6.1 L链接脚本里的Flash起始地址必须和VTOR一致
APP的链接脚本(.icf文件或者.ld文件)里,必须把Flash的起始地址从0x08000000改成APP的实际起始地址。这是常识,但有个细节很多人会搞错:如果APP工程的启动文件是norflash启动版本,链接脚本改了,但启动文件里的向量表地址没改,同样白搭。
比如IAR的.icf文件:
define symbol __ICFEDIT_intvec_start__ = 0x08010000; define symbol __ICFEDIT_intvec_end__ = 0x08010000 + 0x400; define symbol __ICFEDIT_region_RAM_start__ = 0x20000000;这样向量表放在0x08010000,RAM从0x20000000开始。如果只改了Flash起始,没改intvec_start,向量表还是会被放到0x08000000,APP的Reset_Handler入口地址就不是0x08010004了,跳转自然失败。
检查方法:编译后看map文件,确认:
- APP的__initial_sp(初始栈指针)值。
- Reset_Handler地址。
- 向量表首地址。
比如map文件里应该能看到类似:
__initial_sp 0x20020000 Reset_Handler 0x08010169如果__initial_sp是0x20000000,说明链接脚本里RAM配置可能有问题。正常情况,栈顶应该在RAM最高地址附近(取决于你链接脚本的定义)。
6.2 编译优化等级带来的跳转异常
这个坑比较冷门,但一旦踩到,排查起来相当费劲。某个项目里,bootloader开了-O2优化,跳转函数如下:
void jump_to_app(void) { uint32_t app_entry = *(volatile uint32_t *)(app_addr + 4); void (*jump)(void) = (void (*)(void))app_entry; __set_MSP(*(volatile uint32_t *)app_addr); jump(); }在-O0下一切正常,开了-O2后,跳转就进HardFault。原因:编译器优化后,把app_entry的读取提前到了__set_MSP()之前,或者更隐蔽——函数指针变量被优化到寄存器里,跳转后寄存器被APP的启动代码覆盖,导致实际跳转地址不对。
根本原因其实是跳转前已经设置了MSP,但当前还在用旧的栈(因为__set_MSP()之后没有立即给编译器一个"栈变更了"的屏障),优化器可能做了栈复用。
解决方式有几种靠谱的:
推荐方式A:用内联汇编做跳转,不让编译器干预寄存器状态。
__asm volatile ( "ldr sp, [%0] \n" "ldr pc, [%0, #4] \n" : : "r" (APP_FLASH_ADDR) : "memory" );推荐方式B:用一个不返回的函数来跳转,并加编译屏障。
__attribute__((noreturn)) void jump_to_app(void) { __set_MSP(APP_STACK_ADDR); __disable_irq(); SCB->VTOR = APP_FLASH_ADDR; void (*jump)(void) = (void (*)(void))APP_ENTRY_ADDR; jump(); while (1); }方式B其实还是不够彻底,因为jump()还是普通函数调用,理论上有栈帧问题。不过实战中只要加了__attribute__((noreturn))和while(1),绝大多数编译优化下都不会有问题。
我个人的建议是直接上内联汇编,一次性根除这个隐患。反正就两行汇编,注释写清楚,以后维护也不费劲。
6.3 编译选项与启动文件的匹配
还有一个坑在启动文件。APP工程用的启动文件,必须和处理器的启动方式匹配。STM32F429如果用内部Flash启动,复位后从0x08000000取向量,启动文件没问题。但如果APP打算从0x08010000运行,而启动文件里某些宏(比如__VECTOR_TABLE_OFFSET)还是默认值,也会出问题。
在CMSIS(Cortex微控制器软件接口标准)版本较新的环境里,VECTOR_TABLE_OFFSET宏的定义会影响启动是否在main前额外设置VTOR。有的启动文件甚至提供了"在Reset_Handler里设置VTOR"的选项。如果APP工程里VECT_TAB_OFFSET已经改了,但启动文件里的宏定义没同步,等于白改。
检查方法最直接:在APP的Reset_Handler入口处打断点,进调试模式,看看SCB->VTOR的当前值。如果是0x08000000,说明启动文件或者SystemInit里把VTOR覆盖了(如果没有额外代码被调用),那么问题很可能就是启动配置和链接脚本不一致。
7. 完整的排查流程和验证方法
结合前面提到的各种可能原因,我整理了一套完整的排查清单,按照从外到内、从简单到复杂的顺序来。
7.1 跳转前自检清单
| 检查项 | 方法 | 判定标准 |
|---|---|---|
| APP固件是否已正确写入 | 读0x08010000和0x08010004的值 | 栈指针在0x20000000~0x20030000范围内;入口地址在0x08010000~0x08020000范围内 |
| APP向量表是否偏移 | 检查APP工程的VECT_TAB_OFFSET | 等于APP偏移量(如0x00010000) |
| 跳转代码是否完整 | 检检查是否包含关中断、设MSP、设VTOR | 三项缺一不可 |
| 外设中断是否清理 | 跳转前执行外设复位+NVIC清理 | 跳转后NVIC->ISPR全为0 |
| 链接脚本起始地址 | 查看map文件 | __initial_sp和Reset_Handler地址正确 |
7.2 跳转后的验证手段
跳转进APP后,三种验证方法:
- 打断点到APP的main()函数处:如果能停住,说明启动流程没问题。然后单步执行,同时看RCC、GPIO等寄存器的初始化是否正常。
- 在跳转指令后用调试器读内核寄存器:确认PC = APP_ENTRY_ADDR,MSP = APP_STACK_ADDR,VTOR = APP_FLASH_ADDR。这三个值同时正确,跳转代码层面就没问题。
- APP里设置串口打信息:在APP的main()最开始加一行串口输出(用APP自己的串口配置),能打出来说明启动跑通了,再逐步排查后面的问题。
7.3 最实用的一个工具:J-Link的Flash断点
调试这类问题时,J-Link(或其他调试器)的Flash断点功能极其好用。在APP的main()处下断点,bootloader跳转进去后,如果能在main停下,就证明跳转链路通了一半。剩下的就是检查外设初始化、时钟配置等后续环节。
如果没有硬件调试器,还有一个土办法:在APP的main()开头翻转一个LED或GPIO。跳转后如果LED亮了,说明APP至少启动了。这个方法在产线调试时特别管用,不用接调试器就能快速判断。
8. 我踩过的一次完整坑:从现象到根因的复盘
最后分享一个完整的案例,把前面所有知识点串起来。某项目用的就是STM32F429,bootloader功能包括串口升级、固件校验、软复位跳转。
8.1 现象
跳转后有两种表现,取决于固化到Flash里的APP是什么版本:
- 旧版本APP:跳转后一切正常,功能稳定。
- 新版本APP:跳转后100% HardFault。
8.2 排查过程
第一步,怀疑跳转代码问题。检查跳转代码,写得很标准:关中断、设MSP、设VTOR、函数指针跳转。单步执行,PC确实跳到了0x08020004(新版本的Reset_Handler),然后走进HardFault。
第二步,怀疑APP向量表偏移。检查新版本的VECT_TAB_OFFSET,发现没有改,还是默认的0。这就奇怪了——旧版本APP也是默认的0,为什么旧版本没问题?
第三步,对比新旧版本的链接脚本。发现旧版本APP的Flash起始地址是0x08010000,而新版本改成了0x08020000(因为bootloader从64KB扩大到了128KB)。问题一下明朗了:
跳转代码里硬编码了APP地址(还是0x08010000),但新APP的实际地址是0x08020000。跳转时取0x08010004处的值作为入口地址——那个位置是bootloader的代码区域,取出来的是bootloader的一条指令地址,跳过去跑的自然就是乱码。
修复方案:跳转函数改成从"出厂配置"里读取APP地址,或者从Flash末尾的一个配置扇区读取。如果在现场排查时看到"同一个bootloader跳旧APP正常、跳新APP失败",第一反应就该查APP地址一致性——bootloader里定义的地址、APP链接脚本的Flash起始地址、VTOR偏移量,这三者必须完全一致。
8.3 根因总结
| 问题层级 | 根因 | 修复 |
|---|---|---|
| 跳转代码 | 跳转目标地址硬编码 | 改为从配置区读取APP地址 |
| APP工程 | 旧版本链接脚本起始地址与新版本不一致 | 确认统一为0x08020000 |
| APP工程 | VECT_TAB_OFFSET未同步修改 | 改为对应的偏移量 |
这个案例最有价值的点在于:它不是某一个配置错误导致的跳转失败,而是bootloader和APP之间存在地址契约不一致的问题。所以在设计IAP方案时,建议把以下几个常量集中定义在一个公共头文件里,bootloader和APP工程都引用这一份:
#define APP_FLASH_ADDR 0x08020000 #define APP_FLASH_SIZE 0x000C0000 // 768KB,根据实际Flash大小调整 #define APP_VECT_OFFSET (APP_FLASH_ADDR - 0x08000000)这样至少能避免因为地址不一致导致的低级跳转失败。
9. 最后补充几条实操心得
说几个前面没展开,但实际项目中很有用的经验。
第一,跳转前多留一个"后门"。跳转失败后不能直接锁死设备,不然下次升级就没法做了。我的做法是在bootloader里加一个"跳转超时看门狗"机制:跳转前启动独立看门狗(IWDG),如果APP在5秒内没有喂狗,系统复位后再次回到bootloader,等待下一次升级。这样即使APP完全跑不起来,设备也不会变砖。
第二,关于固件校验。跳转前的栈指针检查和入口地址检查只是最基本的合法性校验,真正上线前一定要加CRC校验。CRC校验有两个作用:一是防止传输过程中数据错误;二是防止Flash写入时掉电导致数据不完整。如果没有CRC,一个被截断的固件包写入Flash后,跳转失败是轻的,直接导致后续升级流程异常才是麻烦的。
第三,关于"跳转失败"的调试定位顺序。我自己的经验是:先查地址契约,再查VTOR,再查中断残留,再查编译优化。这个顺序能把80%的问题在第一层就解决掉。因为地址契约问题最简单也最隐蔽——bootloader和APP各写各的,两边都觉得没问题。
第四,关于BootLoader和APP共用库函数。如果两边共用一套HAL库,跳转前最好把系统时钟恢复到默认值(比如外部晶振HSI)。否则APP在SystemInit()里会以为自己的时钟配置是唯一有效的,可能会出一些不好查的时钟问题。
第五,也是最容易被忽视的,保持现场可追踪。在做跳转前,把关键状态(如跳转地址、固件版本号、校验结果)保存到备份寄存器(备份SRAM或者RTC备份寄存器)里。如果跳转后系统异常复位,bootloader重启时能读出这些信息,打印或上报,对定位问题帮助极大。我吃过一次大亏,就是因为跳转现场没有留任何信息,问题复现后只能从头盲查。
跳转失败这个问题,说复杂也复杂,涉及向量表、栈、中断、链接脚本、编译优化好几层;说简单也简单,只要把"处理器怎么启动一个新程序"这个过程想清楚,再逐一核对,基本都能拿下来。希望这篇内容能帮你少走弯路。