Cortex-M系列处理器偶发死机定位方法

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 GuideJLink用户使用手册重点看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的值可以判断发生死机时,软件在执行的状态:

  1. LR为FLASH范围内的地址时,说明程序发生了死循环
  2. LR不为FLASHI范围内的地址时,只能为EXC_RETURN,根据EXC_RETURN可以判断进入interrupt或fault前处理器是什么模式,使用什么stack。

关于浮点单元lazy工作原理参考文章 Cortex-M FPU的Lazy Stacking机制,以前学习linux源码时,对浮点数lazy机制似是而非,现在终于明白了.

EXC_RETURNstackmodestack frame
0xFFFFFFF1MSPHandlernon-floating-point state
0xFFFFFFF9MSPThreadnon-floating-point state
0xFFFFFFFDPSPThreadnon-floating-point state
0xFFFFFFE1MSPHandlerfloating-point-state
0xFFFFFFE9MSPThreadfloating-point state
0xFFFFFFEDPSPThreadfloating-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.常用命令整理

命令说明备注
hhalt
ggo
IsHaltedReturns the current CPU state (halted / running)
SleepWaits the given time (in milliseconds).
Syntax: `Sleep
写脚本时语句间等待时间
sSingle step the target chip
RegsDisplay contents of registers
wregWrite register.
Syntax: wreg <RegName>, <Value>
memRead memory.
Syntax:mem [<Zone>:]<Addr>,<NumBytes> (hex)
mem8Read 8-bit items.
Syntax: mem8 [<Zone>:]<Addr>,<NumBytes> (hex)
mem16Read 16-bit items.
Syntax: mem16 [<Zone>:]<Addr>,<NumItems> (hex)
mem32Read 32-bit items.
Syntax: mem32 [<Zone>:]<Addr>, <NumItems> (hex)
w1Write 8-bit items.
Syntax: w1 [<Zone>:]<Addr>, <Data> (hex)
w2Write 16-bit items.
Syntax: w2 [<Zone>:]<Addr>, <Data> (hex)
w4Write 32-bit items.
Syntax: w4 [<Zone>:]<Addr>, <Data> (hex)
rReset target (RESET)
rxReset target (RESET).
Syntax: rx <DelayAfterReset>
RSetTypeSet the current reset type.
Syntax: RSetType <type>
loadfileLoad data file into target memory.
Syntax: loadfile <filename>, [<addr>]
Supported extensions: *.bin, *.mot, *.hex, *.srec; <addr> is needed for bin files only.
loadbinLoad *.bin file into target memory.
Syntax: loadbin <filename>, <addr>
savebinSaves target memory into binary file.
Syntax: savebin <filename>,<addr>,<NumBytes>
verifybinVerfies if the specified binary is already in the target memory at the specified address.
Syntax: verifybin <filename>, <addr>
SetPCSet the PC to specified value.
Syntax: SetPC <Addr>
logEnables log to file.
Syntax: log <filename>
SetBPSet breakpoint.
Syntax: SetBP <addr> [A/T] [S/H]
ClrBPClear breakpoint.
Syntax: ClrBP <BP_Handle>
SetWPSet Watchpoint.
Syntax: <Addr> [R/W] [<Data> [<D-Mask>] [A-Mask]]
ClrWPClear watchpoint.
Syntax: ClrWP <WP_Handle>
VCatchWrite vector catch.
Syntax: VCatch <Value>
moeShows mode-of-entry, meaning: Reason why CPU is halted
wmWrite 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寄存器
MSPthread和handler模式都使用MSP寄存器
PSP没有使用
R14(LR)返回地址寄存器
XPSR
APSR应用程序状态寄存器nzcvq大写字母为1,小写字母为0
EPSR执行程序状态寄存器
IPSR中断状态寄存器查看中断号
CFBP
CONTROL控制寄存器04浮点上下文打开
thread mode使用MSP
thread mode使用privileged
FAULTMASK00所有中断都起作用
BASEPRI00所有优先级中断都起作用
PRIMASK00所有可配置中断都起作用
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_RETURNstackmodestack frame
0xFFFFFFF1MSPHandlernon-floating-point state
0xFFFFFFF9MSPThreadnon-floating-point state
0xFFFFFFFDPSPThreadnon-floating-point state
0xFFFFFFE1MSPHandlerfloating-point-state
0xFFFFFFE9MSPThreadfloating-point state
0xFFFFFFEDPSPThreadfloating-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__
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值