RT-Thread操作系统中Hard Fault是比较常见的死机问题,造成这个问题的原因多种多样,但排查方式大同小异。本文以执行空函数死机和操作不可写内存死机两种情况为例,和大家分享下这类问题的排查定位方法。
手动写一个执行空函数指针执行死机的shell指令为例
void hard_fault_test1(void)
{
void (*fun) (void) = 0;
fun();
}
MSH_CMD_EXPORT(hard_fault_test1, hard_fault_test1);
RTT在hardfault后会打印MCU的几个运行相关的寄存器、各线程的运行情况:
首先查看各线程栈的运行状态、线程栈占用是正常的。(只看线程列表不能完全排除线程栈溢出的情况,有的时候线程栈溢出导致死机但list里无法判断出来)
再查看几个MCU运行的指针,发现PC指针为0。
PC是指程序计数器(Program Counter)。用于存放下一条将要执行的指令地址。很明显在该环境下0地址不是存放固件的地址,PC指针为0是不正常的。(PC指针不止为0是不正常,所有出现非代码段的地址都不正常)。
此时可以确定是执行了一个0地址的函数导致的,一般是回调函数未设置就直接执行了函数指针导致的。但此时还不确定这个错误的函数是在哪被执行的。
下一步是要定位这个错误的函数指针是被谁调用的。
直接看LR指针,这里记录的是PC指针执行后的返回地址,也就是调用错误函数指针的位置。
在keil中打开汇编代码,在任意地方右键,选择show disassembly at address。
在输入框中输入刚刚的LR指针地址0x7001857B,点击go to
可以看到光标指到了hard_fault_test1()函数的结尾,说明是在执行该函数时触发的hard fault。
结合之前确定的空指针函数的问题,分析代码可以看到是44行执行了空指针函数。
问题找到。
如果当前环境不能使用IDE进行debug查看汇编代码,通过编译器生成的.map文件定位也可以定位到是哪个.c文件的哪个函数。
注意,map文件中显示的是各个函数的起始地址,但LR指针指向的是函数中的某条指令的地址,因此可以将指针值减去一到两位搜索,再根据搜索结果判断LR在哪个函数的地址范围内。
如打开map文件后查找0x7001857,可以看到在hard_fault_test1的地址范围内再按照上面的方法去该函数中排查。
问题找到。
手动写一个操作不可写内存导致死机的shell指令为例
void hard_fault_test2(void)
{
char *str = 0;
*str = '1';
rt_kprintf("%s", str);
}
MSH_CMD_EXPORT(hard_fault_test2, hard_fault_test2);
首先查看各线程栈的运行状态、线程栈占用是正常的。再查看几个MCU运行的指针,也没有明显的异常。
因为根据错误日志没有发现明显的错误,只能先跳转到PC指针指向的代码,查看发生hard fault时正在执行哪些代码。
通过上一节的方法,使用IDE查找汇编,或查找map文件,定位到对应的C代码:
PC指针指向的是52行的rt_kprintf输出,即死机之前执行的是51行的数据写入,分析代码发现是想0地址写入了数据导致的死机。
问题找到。
相关文章:
RT-Thread线程调度机制、线程切换时机分析:
https://blog.csdn.net/weixin_43854928/article/details/119985822
RT-Thread RT_ASSERT 断言死机问题定位方法:
https://blog.csdn.net/weixin_43854928/article/details/123884381?spm=1001.2014.3001.5502