STM32 程序跳转遇到的问题

本文详细解析ARM单片机STM32F412xx的启动流程,探讨BL(Bootloader)与AP(Application)之间的切换机制。通过一个实际案例,深入分析内存布局、加载流程及中断处理可能导致的内存数据踩踏问题,提供解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

遇到了一个坑,顺便就梳理了下ARM单片机的启动流程.

简单的介绍开发环境:

  • 目标板:STM32F412xx. 512K的FLASH(0x08000000,0x080080000)和256K的RAM(0x02000000,0x020040000)

  • 开发环境:keil + JTAG

应用具有升级功能,存在两个固件BL(bootloader),AP(application).

空间分配

  • BL使用的空间是(0x08000000,0x08040000),分了256K,因为我的BL中放了很多的字符和图片资源.

  • AP使用的空间是(0x08040400,0x08080000) ,0x08040000 ~ 0x080403FF 存放了AP的签名

启动跳转

上电后,BL获取控制权,对AP的签名信息和固件进行检查.检查通过后,跳入到AP的入口地址处(0x08040400).

跳入的代码如下

void jump_ap(uint32_t addr)
{
    __set_MSP(*(uint32_t*)addr);
    ( (void(*)(void)) (*((int*)(addr+4))) )();
}

/*不是很好懂,可以写成这样*/
typedef void (*pv_call_t) (void);
void jump_ap(uint32_t addr)
{
    int *p_sp = (int*)addr;
    int *p_pc = (int*)(addr + 4)
    //set msp
     __set_MSP(*p_sp);
    //set pc
    (pv_call_t)(*p_pc)();
}

/*还是用汇编写比较容易看*/
static __asm void jump_ap(uint32_t addr)
{
    LDR r1,[r0]    ; r1  = *addr
    MSR MSP,r1     ; msp = r1
    LDR pc,[r0,#4] ; pc  = *(addr + 4)
}

在BL中做一些检查工作,检查完毕,直接调用 jump_ap(0x08040400) 进入AP.

预警,坑出现

在我的ap里,有一个比较大的字符串结构体表,代码类似这样.

struct __string_table_st {
    int res_id;
    const char *str_en;
    const char *str_cn;
};
struct __string_talbe_st string_tbl[] = { //string_tbl的大小大概在80左右.
    {RES1,"hello world","你好世界"},
    ...
};

我用JTAG跟踪AP,在BL执行完毕后,断点停在了AP的main()函数入口处.ap的main类似如下

int main()
{ //[x] 断点处
    SCB->VTOR = 0x08040400; //设置中断向量入口
    HAL_Init();
    xx_Init();
    ...
    xx_Init();
    ...
    work();
}

断住后,我去查看string_tbl的值,发现有数组的第55组数据竟然被更改,如下图:
在这里插入图片描述

感觉头顶悬了一颗地雷,一下子紧张起来,这里数据被更改,意味着内存数据有踩踏的地方.危险大发了.

观察和分析:

  1. 其他变量都是正常的,唯独第数组第55组中文字符串部分出错
    • 因为这个现象,误导我以为是中文的错误,花了不少时间排查,最终发现与中文无关,与在内存位置有关
    • 如果将 string_tbl修改为const,这样所有的数据都放在flash里,肯定是正常的.其实也应该将这个数组放在flash里,不过,很感谢当初我忘记了加const. 不然真的非常难暴露这个问题.
    • 基本确定是内存被踩踏了
  2. 考虑到ap是从bl跳过来,如果直接运行ap呢? 于是我将ap编译到0x08000000的位置上.证明是没问题的.
  3. 基本确定是bl跳转到ap的过程中出问题的. (至此,我并没有在代码中暴露太多的细节,有经验的老手,应该猜出几种可能了)

启动内存加载流程

既然问题出现在跳转和加载过程,我们就来分析一下固件的加载流程。
BL和AP的加载流程是一样的,无非是PC指针从哪里开始的区别.这里使用AP做例子.
flash布局和内存布局

如图,AP固件编译后的并下载到flash的布局如图左边部分,Vectors里前两个地址里存放的内容是SP指针和ResetHander函数,从BL到AP跳转过程,就是将SP指针赋值给MSP,将ResetHander赋值给PC指针(见,启动跳转).于是PC从ResetHander开始执行,ResetHander大致如下:

Reset_Handler PROC
	EXPORT Reset_Handler 
	IMPORT SystemInit
	IMPORT __main
		LDR R0,=SystemInit
		BLX R0,
		LDR R0,=__main
		BX R0
		ENDP

SystemInit函数,初始化体系相关的寄存器,这里关键是–main 函数. 这个不是我们实现的函数,我们实现的main().–main函数是keil提供的库里实现的,存在的目的是帮助我们实现将

  • 将.data从flash里拷贝到内存里(上图中箭头标识)
  • 将.bss端清0
  • 调用main函数
    回到我们的BUG里,string_tbl是属于.data段的,我把断点设置在了main的入口处,而在此之前已经由–main完成了string_tbl的加载和初始化. 所以是在这个过程中,string_tbl的第55个元素的所在内存被修改了.原因是发生了中断,而且在这个中断里对内存进行了修改,这个中断处理函数在BL里,在BL固件里我们执行了某些驱动的初始化,配置了某些中断,而在跳转的时候,有的中断被触发了,于是在BL里的中断服务函数被执行了.在我的应用里,是系统定时器被触发了,而且定时器的全局变量恰好与string_tbl的内存重叠.进而修改了该全局变量所在内存,而该内存恰好就是string_tbl的第55个元素的位置!!

总结

知识点很重要,不按规则做,看似正常的代码,往往隐藏很大的坑,这也是代码拿来主义最大的弊端。以此文为例,好在修改的是我需要显示的内容,比较容易发现问题。否则,极难找到问题,你的内存某个地方被修改,可能是无关紧要的部分,也可能是非常重要的部分;调试的时候,能调试到你怀疑人生。
知道问题时,解决就很简单. 跳转前,关闭所有中断响应,具体指令看你的固件手册吧.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值