1.简介
处理器偶发死机时有发生,让人头疼,不知道如何下手,因为没有打印,不知道如何定位。
现在介绍一种方法,不管使用什么开发环境keil,iar,gcc等,不管使用什么系列的处理器M1,M4,M7等,原理上都是一样的。
现在用STM32F407处理器,keil开发环境为例介绍。
主要方法是:使用Jlink命令行工具对发生死机的设备读取寄存器和内存,根据反汇编定位问题。
2.准备知识
资料 | 说明 | 备注 |
---|---|---|
STM32 Cortex®-M4 MCUs and MPUs programming manual | 熟悉cortex-M4体系结构和指令集 | 对arm公司的资料进行了提取缩减 |
STM32F4xx英文参考手册.pdf | 介绍各个功能模块 | 重点看flash和RAM |
STM32F407_datasheet.pdf | 芯片手册 | 重点看Memory mapping |
J-Link / J-Trace User Guide | JLink用户使用手册 | 重点看Jlink命令行操作 |
根据教程操作可以不熟悉上述资料,需要进一步理解,必须熟悉上述资料。
以下是总结的需要用到的知识,每个处理器不同。
2.1.Cortex-M4 处理器
处理器只有两个模式:Thread mode,Handler mode;
Thread mode平时程序运行;
Handler mode异常处理;
处理器有两个级别:Unprivileged,Privileged;
Unprivileged 保护资源不被访问;
Privileged 访问所有资源;
处理器有两个stack:Main stack,process stack
处理器运行在Handler mode强制Privileged级别, 使用Main stack;
处理器运行在Thread mode可选Unprivileged或Privileged级别,使用Main stack或process stack栈.
基于RTOS的软件实现Thread mode一般配置成: Unprivileged级别,使用process stack;使用系统调用访问硬件资源.
基于裸机的软件实现Thread mode一般配置成: Privileged级别,使用process stack;应用可以直接访问所有资源.
2.2.中断
stacking
stack frame
中断是先减,再存
2.2.1 EXC_RETURN
根据LR的值可以判断发生死机时,软件在执行的状态:
- LR为FLASH范围内的地址时,说明程序发生了死循环
- LR不为FLASHI范围内的地址时,只能为EXC_RETURN,根据EXC_RETURN可以判断进入interrupt或fault前处理器是什么模式,使用什么stack。
关于浮点单元lazy工作原理参考文章 Cortex-M FPU的Lazy Stacking机制,以前学习linux源码时,对浮点数lazy机制似是而非,现在终于明白了.
EXC_RETURN | stack | mode | stack frame |
---|---|---|---|
0xFFFFFFF1 | MSP | Handler | non-floating-point state |
0xFFFFFFF9 | MSP | Thread | non-floating-point state |
0xFFFFFFFD | PSP | Thread | non-floating-point state |
0xFFFFFFE1 | MSP | Handler | floating-point-state |
0xFFFFFFE9 | MSP | Thread | floating-point state |
0xFFFFFFED | PSP | Thread | floating-point state |
2.2.2. Fault
2.3.JLink调试器命令行方式使用
2.3.1.搭建环境
设置环境变量PATH
C:\Program Files (x86)\SEGGER\JLink_V642
在命令行中运行
jlink
可以看到JLink正常功能
SEGGER J-Link Commander V6.42 (Compiled Jan 30 2019 17:51:00)
DLL version V6.42, compiled Jan 30 2019 17:50:21
Connecting to J-Link via USB...O.K.
Firmware: J-Link V11 compiled Apr 27 2041 16:36:21
Hardware version: V11.00
S/N: 941000024
License(s): GDB, JFlash, FlashDL, RDI, FlashBP
VTref=3.348V
Type "connect" to establish a target connection, '?' for help
退出
exit #关闭电路板连接
#或
q #关闭JLink连接
#或
qc #退出jlink工具
2.3.1.常用命令整理
命令 | 说明 | 备注 |
---|---|---|
h | halt | |
g | go | |
IsHalted | Returns the current CPU state (halted / running) | |
Sleep | Waits the given time (in milliseconds). Syntax: `Sleep | 写脚本时语句间等待时间 |
s | Single step the target chip | |
Regs | Display contents of registers | |
wreg | Write register. Syntax: wreg <RegName>, <Value> | |
mem | Read memory. Syntax: mem [<Zone>:]<Addr>,<NumBytes> (hex) | |
mem8 | Read 8-bit items. Syntax: mem8 [<Zone>:]<Addr>,<NumBytes> (hex) | |
mem16 | Read 16-bit items. Syntax: mem16 [<Zone>:]<Addr>,<NumItems> (hex) | |
mem32 | Read 32-bit items. Syntax: mem32 [<Zone>:]<Addr>, <NumItems> (hex) | |
w1 | Write 8-bit items. Syntax: w1 [<Zone>:]<Addr>, <Data> (hex) | |
w2 | Write 16-bit items. Syntax: w2 [<Zone>:]<Addr>, <Data> (hex) | |
w4 | Write 32-bit items. Syntax: w4 [<Zone>:]<Addr>, <Data> (hex) | |
r | Reset target (RESET) | |
rx | Reset target (RESET). Syntax: rx <DelayAfterReset> | |
RSetType | Set the current reset type. Syntax: RSetType <type> | |
loadfile | Load data file into target memory. Syntax: loadfile <filename>, [<addr>] Supported extensions: *.bin, *.mot, *.hex, *.srec; <addr> is needed for bin files only. | |
loadbin | Load *.bin file into target memory. Syntax: loadbin <filename>, <addr> | |
savebin | Saves target memory into binary file. Syntax: savebin <filename>,<addr>,<NumBytes> | |
verifybin | Verfies if the specified binary is already in the target memory at the specified address. Syntax: verifybin <filename>, <addr> | |
SetPC | Set the PC to specified value. Syntax: SetPC <Addr> | |
log | Enables log to file. Syntax: log <filename> | |
SetBP | Set breakpoint. Syntax: SetBP <addr> [A/T] [S/H] | |
ClrBP | Clear breakpoint. Syntax: ClrBP <BP_Handle> | |
SetWP | Set Watchpoint. Syntax: <Addr> [R/W] [<Data> [<D-Mask>] [A-Mask]] | |
ClrWP | Clear watchpoint. Syntax: ClrWP <WP_Handle> | |
VCatch | Write vector catch. Syntax: VCatch <Value> | |
moe | Shows mode-of-entry, meaning: Reason why CPU is halted | |
wm | Write test words. Syntax: wm <NumWords> |
2.3.2.使用KLink命令行连接电路板
#切换工作目录
cd E:\test\jlink_workspace
#使用jlink连接电路板
jlink -device STM32F407VG -if SWD -speed 50000
#-device STM32F407VG 选择处理器
#-if SWD 选择调试接口
#-speed 50000 设置调试速度[kHz]单位
2.3.3.寄存器解读
使用JLink读取到的寄存器如下:
PC = 0803C38E, CycleCnt = 484513CD
R0 = 00000000, R1 = 2000A634, R2 = 00000000, R3 = 00000002
R4 = 2000A5A0, R5 = 2000A634, R6 = 08010000, R7 = AA55AA55
R8 = 00000008, R9 = FFFFFFFF, R10= 080068B0, R11= 00000000
R12= 001DD9AD
SP(R13)= 2001C3D8, MSP= 2001C3D8, PSP= 00000000, R14(LR) = 0803C385
XPSR = 01000000: APSR = nzcvq, EPSR = 01000000, IPSR = AB805800000000 (?2~?
CFBP = 04000000, CONTROL = 04, FAULTMASK = 00, BASEPRI = 00, PRIMASK = 00
FPS0 = 00000000, FPS1 = 40E00000, FPS2 = 40000000, FPS3 = 00000000
FPS4 = 00000000, FPS5 = 00000000, FPS6 = 00000000, FPS7 = 00000000
FPS8 = 00000000, FPS9 = 00000000, FPS10= 00000000, FPS11= 00000000
FPS12= 00000000, FPS13= 00000000, FPS14= 00000000, FPS15= 00000000
FPS16= 00000000, FPS17= 00000000, FPS18= 00000000, FPS19= 00000000
FPS20= 00000000, FPS21= 00000000, FPS22= 00000000, FPS23= 00000000
FPS24= 00000000, FPS25= 00000000, FPS26= 00000000, FPS27= 00000000
FPS28= 00000000, FPS29= 00000000, FPS30= 00000000, FPS31= 00000000
FPSCR= 83000010
寄存器名称 | 功能 | 值 | 说明 |
---|---|---|---|
PC | 指令指针 | ||
R0-7 | 通用低寄存器 | ||
R8-12 | 通用高寄存器 | ||
SP(R13) | stack寄存器 | ||
MSP | thread和handler模式都使用MSP寄存器 | ||
PSP | 没有使用 | ||
R14(LR) | 返回地址寄存器 | ||
XPSR | |||
APSR | 应用程序状态寄存器 | nzcvq | 大写字母为1,小写字母为0 |
EPSR | 执行程序状态寄存器 | ||
IPSR | 中断状态寄存器 | 查看中断号 | |
CFBP | |||
CONTROL | 控制寄存器 | 04 | 浮点上下文打开 thread mode使用MSP thread mode使用privileged |
FAULTMASK | 00 | 所有中断都起作用 | |
BASEPRI | 00 | 所有优先级中断都起作用 | |
PRIMASK | 00 | 所有可配置中断都起作用 | |
FPS0-31 | 浮点通用寄存器 | ||
FPSCR | 浮点状态控制寄存器 |
3.解决问题
准备工作完成后,就开始解决问题吧.
3.1.准备工程
为了简便直接使用正点原子的库函数版本的跑马灯例子.
需要修改的工作如下:
//1.根据实际电路板设置PLL时钟为正确,system_stm32f4xx.c中修改
#define PLL_M 25
#2.设置生成bin文件
#Options Target for "LED" => User => After Build/Rebuild
#Run1 选择,设置内容为:
C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o ..\OBJ\LED.bin ..\OBJ\LED.axf
3.2.模拟死循环并解决
在实际解决问题之前模拟各种死机情况,使用命令行解决。
int main(void)
{
delay_init(168); //初始化延时函数
LED_Init(); //初始化LED端口
/**下面是通过直接操作库函数的方式实现IO控制**/
while(1)
{
GPIO_ResetBits(GPIOF,GPIO_Pin_9); //LED0对应引脚GPIOF.9拉低,亮 等同LED0=0;
GPIO_SetBits(GPIOF,GPIO_Pin_10); //LED1对应引脚GPIOF.10拉高,灭 等同LED1=1;
delay_ms(500); //延时300ms
GPIO_SetBits(GPIOF,GPIO_Pin_9); //LED0对应引脚GPIOF.0拉高,灭 等同LED0=1;
GPIO_ResetBits(GPIOF,GPIO_Pin_10); //LED1对应引脚GPIOF.10拉低,亮 等同LED1=0;
delay_ms(500); //延时300ms
while(1)
{
; //模拟死循环
}
}
}
3.2.1.编译、下载、运行
3.2.2.JLink连接电路板
#切换工作目录
cd E:\test\jlink_workspace
#使用jlink连接电路板
jlink -device STM32F407VG -if SWD -speed 50000
#-device STM32F407VG 选择处理器
#-if SWD 选择调试接口
#-speed 50000 设置调试速度[kHz]单位
#根据提示输入 "connect"建立连接
connect
3.2.3.获取寄存器信息
在命令行中输入"h"命令会显示当前寄存器信息
h
根据PC,R14(LR)可以看出程序运行在FLASH中代码,没有死机。
为了不破坏环境,使用另一台设备,进入在线调试模式。
打开disassambly window
在disassambly window中右键打开"Show disassambly at address …",
分别输入PC寄存器地址和R14(LR)寄存器地址。
0x800075E,0x80006CB
可以定位到死循环位置
#在真实世界中死循环可能运行一大段程序,多次运行停止可以定位到程序大概位置。
h #停止打印所有寄存器
g #全速运行
h #停止打印所有寄存器
3.3.模拟异常情况并解决
如下模拟访问非法地址。
int32_t g_u32Test[10];
int main(void)
{
int32_t i;
delay_init(168); //初始化延时函数
LED_Init(); //初始化LED端口
/**下面是通过直接操作库函数的方式实现IO控制**/
while(1)
{
for(i=0;i<100000000;i++)
{
g_u32Test[i]=i; //访问非法地址
}
GPIO_ResetBits(GPIOF,GPIO_Pin_9); //LED0对应引脚GPIOF.9拉低,亮 等同LED0=0;
GPIO_SetBits(GPIOF,GPIO_Pin_10); //LED1对应引脚GPIOF.10拉高,灭 等同LED1=1;
delay_ms(500); //延时300ms
GPIO_SetBits(GPIOF,GPIO_Pin_9); //LED0对应引脚GPIOF.0拉高,灭 等同LED0=1;
GPIO_ResetBits(GPIOF,GPIO_Pin_10); //LED1对应引脚GPIOF.10拉低,亮 等同LED1=0;
delay_ms(500); //延时300ms
}
}
3.3.1.获取寄存器信息
根据PC寄存器定位,代码位置
0x08000378
可以看出发生了异常。
根据R14(LR)寄存器"0xFFFFFFE9"查找EXC_RETURN码表
可以知道stack frame存储在MSP指向的stack中,程序从thread模式进入异常,stack frame中没有浮点寄存器。
EXC_RETURN | stack | mode | stack frame |
---|---|---|---|
0xFFFFFFF1 | MSP | Handler | non-floating-point state |
0xFFFFFFF9 | MSP | Thread | non-floating-point state |
0xFFFFFFFD | PSP | Thread | non-floating-point state |
0xFFFFFFE1 | MSP | Handler | floating-point-state |
0xFFFFFFE9 | MSP | Thread | floating-point state |
0xFFFFFFED | PSP | Thread | floating-point state |
3.3.2.栈内容
根据Cortex-M4 stack frame layout 读出栈内容
MSP内容0x200006F8
mem32 0x200006F8 8
200006F8 = 05F5E100 00000600 00000600 01000301
R0 R1 R2 R3
20000708 = 2000013C 080003B7 08000732 01000000
R12 LR PC xPSR
可以看出发生异常前PC寄存器地址是:0x08000732
3.3.3.定位问题
为了不破坏环境,使用另一台设备,进入在线调试模式。
打开disassambly window
在disassambly window中右键打开"Show disassambly at address …",
输入PC寄存器地址
0x08000732
可以定位到产生异常的位置。
3.4.解决实际问题
根据以上的模拟实验,解决实际问题时就比较简单了,这里有几个注意事项.
3.4.1.先使用模拟实验搭建环境
偶发死机很难复现,为了不破坏环境,最好使用另外一套设备模拟一遍。
3.4.2.准备一套实验环境
抓取信息后,需要到实验环境中进入在线调试模式定位代码,保持问题设备维持现状,能继续观测调试。
3.4.3.保证代码一致
代码不一致,读取的寄存器信息,stack信息,flash地址信息和C语言代码对应不上,没法定位。
需要保证三点:1.保证代码一致;2.保证编译器版本和编译器选项一致;3.保证使用的固件库,依赖库一致。
可以在有问题的设备上验证程序是否一致。
把编译生成LED.bin文件复制到JLink工作目录E:\test\jlink_workspace下。
使用命令验证程序是否一致
verifybin LED.bin, 0x8000000
#verifybin 验证命令
#LED.bin 程序二进制文件
#0x8000000 程序烧写的开始地址
尽量不要在代码中使用每次编译不一样的宏定义,例如
__DATE__
__TIME__