前言
(1)如果有嵌入式企业需要招聘湖南区域日常实习生,任何区域的暑假Linux驱动实习岗位,可C站直接私聊,或者邮件:zhangyixu02@gmail.com,此消息至2025年1月1日前均有效
(2)最近看到很多网友询问一个问题,在开发程序的时候,遇到一些bug就卡死了,然后就不知所措了。反复看到他们问类似的问题,而我最近又学习了韦东山老师的栈回溯相关的知识,因此做一个分享。
(3)这里需要注意一点,韦东山老师的方法个人认为太原始,门槛太高了。Keil
其实已经集成了相关的操作,但是韦东山老师却硬要手动回溯代码。为了让debug
更加的方便和入门,我在此将会结合keil工具来进行讲解。
keil配置
(1)
TARMSTM.DLL
pSTM32F103C8
这里选择你的芯片型号
(2)然后是Debug的一些窗口,按照这个教程打开如下四个窗口:
C站:KEIL5中Debug调试
debug过程
查看当前卡死位置
(1)首先程序全速跑,复现一次
bug
,然后停止调试。之后在上方的Disassembly
中右键,点击图中所示信息。
(2)看一下左侧的
Registers
的PC
指针,看一下当前是卡死在哪个位置。
(3)最终发现是卡死在
portmacro.h
文件中的vPortRaiseBASEPRI()
函数,portmacro.h
不是我们编写的程序,所以似乎这并不是有效的信息。
查看调用卡死函数的位置
(1)一般程序卡死,大概率不是卡死位置出现故障,很可能是函数A在调用卡死函数B时候,存在错误操作。因此,此时我们就需要看LR寄存器。
LR
寄存器存放程序的返回地址,用通俗一点的话来说,就是我们C语言调用return
的时候,就会根据LR
寄存器进行跳转。
(2)通过LR
寄存器我们可以发现,我们似乎是调用了xQueueGenericSend()
函数,然后才导致的程序卡死。
(3)熟悉FreeRTOS
的朋友应该很快就能够意识到问题所在,xQueueGenericSend()
不就是一个队列函数吗?那么,我们是不是可以推断出,是哪个函数调用了队列函数,那么这个很可能就是问题所在。
栈回溯
(1)既然有了思路,那么我们就需要找到当前卡死时候,函数的调用关系,然后知道被调用函数中,哪个任务会调用和队列相关的函数。那个就说问题所在。
(2)这个时候就需要涉及到栈回溯的相关知识了,因为这部分知识有一定的门槛,所以我不会进行介绍。
(如果想要学习了解的,可以看看韦东山老师的90天RTOS双架构双系统项目实战班的5-1-8栈回溯原理)
(3)程序卡死的时候,我们可以通过栈知道程序的调用关系。在Keil
的Debug
工具中,有一个Call Stack
的工具能够看到被调用过的函数。
(4)通过上面分析,我们知道程序vPortRaiseBASEPRI()
和xQueueGenericSend()
这两个函数大概率不是问题所在,因为这两个是FreeRTOS官方源码,出错概率微乎其微。
(5)然后我们就可以看看HAL_GPIO_EXTI_Callback()
函数,大概率是这个函数导致的bug
。按照下图方式可以快速定位到HAL_GPIO_EXTI_Callback()
函数位置。
(6)如果status = xQueueSendToFront(KeilQueueHandle, &Buf, 0);
被顺利执行,那么就会跳转到if (status == pdTRUE)
中,此时我们利用FreeRTOS的知识可以知道,中断中必须使用FromISR
后缀的函数。因此,我们可以推断出问题是在这里了。
总结
(1)栈回溯的一个很好的技巧,我们应当了解和掌握.在一些大型项目中,很少使用keil开发,所以掌握栈回溯的原理非常重要。
(2)可是,因为他的门槛过高,所以我个人建议先从keil开始,熟悉栈回溯的使用,饭要一口一口的吃,步要一步一步的走。
参考
(1)C站:STM32 触发HardFault_Handler如何查找原因
(2)C站:hardfault问题分析解决及记一次ucosIII环境下的hardfault解决
(3)C站:KEIL5中Debug调试
(4)CM3权威指南