如果你在使用MCU进行开发,在项目中后期随着功能和代码的增多,死机似乎是无法避免的。每遇到这个问题,一般需要借助仿真器来定位代码缺陷,但如果遇到了一个非常难以复现的问题,或者硬件已经形成产品,无法再接仿真器的时候就比较难受了。在空指针读写、内存泄漏、堆栈溢出、参数错误等情况都可能使MCU运行死机,有些问题是非常难以复现的,为了复现发生异常的场景,往往需要打很多log进行辅助分析,但一般log无法定义的非常细,很难寻到细节,而正式发布的硬件产品固件中对log也会大幅精简。做过linux的人肯定知道crash dump这个东西,简直是内核调试的神器,在发生内核崩溃时,把函数调用的关系通过堆栈回溯进行还原,可以非常快速的定位到问题代码。那么对于MCU来说,一般跑小型RTOS或者裸机,是否可以用这个方法解决查死机的问题呢?
1.Crash dump 总体思路
在程序发生死机的时通过回溯任务堆栈,解析出函数调用的返回地址,入口参数等,将这些信息打印出来来定位bug。而只有函数地址是不够的,需要使用程序编译时生产的符号表对这些地址进行翻译,从而得到函数和变量名。这个符号表可以存在MCU flash内,进行在线翻译,也可以在上位机进行离线解析。第一种方法可以在第一现场直观的得到函数调用的层次关系,但要求每次烧写程序时,要把对应的符号表一同烧写到MCU的flash或者文件系统中,不太适用flash和ram比较小的MCU;第二种方法需要借助上位机去翻译符号表,MCU只需存储少量函数调用相关信息,几十至一两百个字节就能满足需求,待发生死机时读出这些信息,找到对应的符号表解析出来就行了。本文采取第二种实现方法以满足资源受限的MCU。
2. EABI接口
堆栈回溯需要使用EABI接口,全称为 Embedded Processor Application Binary Interface,也就是 嵌入式处理器应用二进制接口,其主要描述了一个处理器的系统寄存器的使用方法、参数传递过程、堆栈的组织结构、数据段、目标文件以及可执行文件的结构,处理器不同,这个定义也会不同,可以根据MCU的类型到厂家的官网上去下载这个文档。这里主要关注的是寄存器用法以及堆栈的组织结构。
以PPC系列32位MCU为例,该内核一共有32个通用寄存器(GPRs)以及32个浮点寄存器(FPRs),其中R1寄存器保存栈顶指针,R2保存只读小数据数据段地址,R3-R4为函数返回值,R3-R10 可以保存最多8个函数的入口参数,R13保存了可读写小数据段指针地址,其他为通用寄存器,FPRs保存与浮点数相关的传入传出参数。在这里最有用的就是R1,函数执行时保存的栈顶指针。
EABI接口规则定义了堆栈结构,当函数调用时根据这个定义来产生一个数据帧放到任务栈或者系统栈上,定义如下:
我们知道,堆栈一般是从高地址向地址进行增长,而栈顶在低地址,而R1寄存器就记录了这个地址SP。系统堆栈帧包含若干个存储区域,包括FPR、GPR、CR、局部变量、入口参数、填充、帧头等部分。
FPR save area:存储浮点数运算输入输出参数,视硬件是否有硬件计算单元而定;
GPR save area:存储用到的通用寄存器值;
CR:如果程序修改了非易失存储器,也就是上面那个Nonvolatile寄存器,那么就会把这些寄存器的值暂存在CR区,返回时来恢复。
Local Variables area:如果一个函数使用的局部变量比较少,会利用通用寄存器来存储,如果比较多则会存储在这个区域。
Function Parameter area:如果函数传入的参数超过了8个,则用这个区域来存储。
最重要的就是Frame Header包括上一个堆栈帧的地址,以及返回函数的地址,从而构成一个链式存储结构:
在函数中每调用下一层函数,都会生成一个堆栈数据帧,放在任务堆栈中(任务中调用),或者系统堆栈中(中断中调用),并通过帧头一级一级的连接起来,通过栈顶指针就可以遍历所有Stack frame header,并且找出所有的返回函数地址了。
3. 仿真测试
如果通过仿真器,可以查看当前的R1寄存器的值,从而可以看到堆栈的结构了:
根据这个思路,可以写一个函数,在系统异常发生时,打印出堆栈状态。需要注意的是,因为不知道原系统问题出现在哪里,所有像一些在dump时使用到的外设都需要重新初始化,并且不能对OS等有任何依赖。
dump <0>:0x10de42c
dump <1>:0x10de584
dump <2>:0x10e0fe4
......
4. 符号表文件处理
关于工程编译后生成的map文件格式这里就不赘述了,其实只要关心代码段就可以了,通过对map文件进行解析处理,提取出text段所有的函数名称、函数地址以及长度,如下图:
根据这些信息就可以匹配出crash发生的时候那些函数名称了,比如:
5.小结
对于嵌入式产品而言,及时快速解决系统难以复现的bug,对系统稳定性非常重要。无论在裸机还是使用了RTOS的系统中使用堆栈回溯方法可以极大提高调试效率,无疑让MCU也拥有了调试神器。
上一篇:I2C的 SCL和SDA为什么要上拉
下一篇:Keil警告和错误语句与消除方法笔记
设计资源 培训 开发板 精华推荐
- HDMI FPC软排线 延长线 转接头
- MCP7383XEV-DIBC,使用 MCP73837/8 AC/USB 双输入电池充电器的评估板
- FAN53541 2.4MHz、5A Tiny Buck 同步降压稳压器的典型应用
- UC2844B 电压反相电荷泵转换器的典型应用
- 具有高调光比和 LED 开路报告的 LT3756IUD 降压模式 1A LED 驱动器的典型应用电路
- DC620A,用于 LT1943EFE 4 输出开关的演示板。注册。对于 TFT-LCD 面板,Vin = 8-20V,Vout = 3.3V @ 2A,13V @ 500mA,30V @ 30mA,-10V @ 50mA
- NCN4555GEVB:Sim 卡接口评估板
- LT1071 驱动高压 NPN 的典型应用
- OP249GSZ-REEL瞬态输出阻抗测试治具典型应用电路
- 基于HVLED001A的具有高功率因数和低THD的宽输入和宽输出电压LED驱动器