处理器偶发死机时有发生,让人头疼,不知道如何下手,因为没有打印,不知道如何定位。
现在介绍一种方法,不管使用什么开发环境keil,iar,gcc等,不管使用什么系列的处理器M1,M4,M7等,原理上都是一样的。
现在用STM32F407处理器,keil开发环境为例介绍。
主要方法是:使用Jlink命令行工具对发生死机的设备读取寄存器和内存,根据反汇编定位问题。
资料 | 说明 | 备注 |
---|---|---|
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命令行操作 |
根据教程操作可以不熟悉上述资料,需要进一步理解,必须熟悉上述资料。
以下是总结的需要用到的知识,每个处理器不同。
处理器只有两个模式: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;应用可以直接访问所有资源.
stacking
stack frame
中断是先减,再存
根据LR的值可以判断发生死机时,软件在执行的状态:
关于浮点单元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 |
设置环境变量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工具
命令 | 说明 | 备注 |
---|---|---|
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 |
|
mem | Read memory. Syntax: mem [ |
|
mem8 | Read 8-bit items. Syntax: mem8 [ |
|
mem16 | Read 16-bit items. Syntax: mem16 [ |
|
mem32 | Read 32-bit items. Syntax: mem32 [ |
|
w1 | Write 8-bit items. Syntax: w1 [ |
|
w2 | Write 16-bit items. Syntax: w2 [ |
|
w4 | Write 32-bit items. Syntax: w4 [ |
|
r | Reset target (RESET) | |
rx | Reset target (RESET). Syntax: rx |
|
RSetType | Set the current reset type. Syntax: RSetType |
|
loadfile | Load data file into target memory. Syntax: loadfile Supported extensions: *.bin, *.mot, *.hex, *.srec; is needed for bin files only. |
|
loadbin | Load *.bin file into target memory. Syntax: loadbin |
|
savebin | Saves target memory into binary file. Syntax: savebin |
|
verifybin | Verfies if the specified binary is already in the target memory at the specified address. Syntax: verifybin |
|
SetPC | Set the PC to specified value. Syntax: SetPC |
|
log | Enables log to file. Syntax: log |
|
SetBP | Set breakpoint. Syntax: SetBP |
|
ClrBP | Clear breakpoint. Syntax: ClrBP |
|
SetWP | Set Watchpoint. Syntax:
|
|
ClrWP | Clear watchpoint. Syntax: ClrWP |
|
VCatch | Write vector catch. Syntax: VCatch |
|
moe | Shows mode-of-entry, meaning: Reason why CPU is halted | |
wm | Write test words. Syntax: wm |
#切换工作目录
cd E:\test\jlink_workspace
#使用jlink连接电路板
jlink -device STM32F407VG -if SWD -speed 50000
#-device STM32F407VG 选择处理器
#-if SWD 选择调试接口
#-speed 50000 设置调试速度[kHz]单位
使用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 | 浮点状态控制寄存器 |
准备工作完成后,就开始解决问题吧.
为了简便直接使用正点原子的库函数版本的跑马灯例子.
需要修改的工作如下:
//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
在实际解决问题之前模拟各种死机情况,使用命令行解决。
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)
{
; //模拟死循环
}
}
}
#切换工作目录
cd E:\test\jlink_workspace
#使用jlink连接电路板
jlink -device STM32F407VG -if SWD -speed 50000
#-device STM32F407VG 选择处理器
#-if SWD 选择调试接口
#-speed 50000 设置调试速度[kHz]单位
#根据提示输入 "connect"建立连接
connect
在命令行中输入"h"命令会显示当前寄存器信息
h
根据PC,R14(LR)可以看出程序运行在FLASH中代码,没有死机。
为了不破坏环境,使用另一台设备,进入在线调试模式。
打开disassambly window
在disassambly window中右键打开"Show disassambly at address …",
分别输入PC寄存器地址和R14(LR)寄存器地址。
0x800075E,0x80006CB
#在真实世界中死循环可能运行一大段程序,多次运行停止可以定位到程序大概位置。
h #停止打印所有寄存器
g #全速运行
h #停止打印所有寄存器
如下模拟访问非法地址。
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
}
}
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 |
根据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
为了不破坏环境,使用另一台设备,进入在线调试模式。
打开disassambly window
在disassambly window中右键打开"Show disassambly at address …",
输入PC寄存器地址
0x08000732
根据以上的模拟实验,解决实际问题时就比较简单了,这里有几个注意事项.
偶发死机很难复现,为了不破坏环境,最好使用另外一套设备模拟一遍。
抓取信息后,需要到实验环境中进入在线调试模式定位代码,保持问题设备维持现状,能继续观测调试。
代码不一致,读取的寄存器信息,stack信息,flash地址信息和C语言代码对应不上,没法定位。
需要保证三点:1.保证代码一致;2.保证编译器版本和编译器选项一致;3.保证使用的固件库,依赖库一致。
可以在有问题的设备上验证程序是否一致。
把编译生成LED.bin文件复制到JLink工作目录E:\test\jlink_workspace下。
使用命令验证程序是否一致
verifybin LED.bin, 0x8000000
#verifybin 验证命令
#LED.bin 程序二进制文件
#0x8000000 程序烧写的开始地址
__DATE__
__TIME__