打印
[开发生态]

栈溢出导致hardfault问题

[复制链接]
1245|41
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
primojones|  楼主 | 2024-6-25 05:10 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
问题复现

(1)首先看一下网友的问题:
<1>他说程序运行不到1分钟就进入hardFault。这个代码量很少,所以能够很快定位到问题,但是如果程序大了,如何定位问题。
<2>他说可以尝试通过查看R0–R15寄存器是否可以找出问题。
(2)首先回答第一个问题,对于程序的问题定位,肯定是一个复杂而又漫长的过程,经常能够遇到一些玄学问题因此我们要学会掌握技巧,下面的方法就是技巧之一。其次是第二个问题,这明显就是没搞明白寄存器的作用分别是干嘛的,完全是胡乱看寄存器。
(3)问题如下:

排查流程栈回溯分析

(1)既然知道了问题是什么了,那么就开始排查,根据前面的介绍,我们可以通过keildebug工具中的Call Stack进行栈回溯。分析程序卡死之前被哪些函数调用,然后逐步分析这些函数可能的问题。
(2)但是,不幸的是,我们会发现Call Stack中只有一个HardFault_Handler。那么明显说明Call Stack工具现在是用不了的。

CM3和CM4异常返回值

(1)之前那篇博客,是介绍的普通的程序卡死如何进行排查,然后栈回溯调试。而这里有些许的不同,在于是hardfault错误,因此只有这个地方会不同。
(2)前文我介绍了,程序卡死一般是看R14(LR)寄存器,因为这个寄存器存放返回值信息,通俗来说就是C语言的return根据这个寄存器进行函数返回。
注:下图摘抄自CM3权威指南3.1寄存器组章节。

(3)那么现在我们看一下R14(LR)寄存器值是什么。我们会发现里面居然是0xFFFFFFFD!很明显,我函数返回不可能是返回到一个0xFFFFFFFD,因为程序一般都是存储在0x08开头的位置。那么这个时候我们就需要来了解一下CM3CM4的异常返回值的知识了。

(4)我们在看上面的寄存器介绍的时候,有没有发现一个问题,R13怎么有两个寄存器,一个MSP,一个是PSP?因为MSP用于内核和异常处理,而PSP用于进程的堆栈。这使得Cortex-M3处理器可以轻松实现多任务操作系统。
(5)上述这些,如果想详细了解的个人建议直接看这篇博客,介绍的非常的好:RTOS系列文章(6):Cortex-M3/4之SP,MSP,PSP,Thread模式、Handler模式、内核态、用户态
(6)但是,这部分知识跟我们问题定位关系不大。我们只需要了解,如果R14(LR)如果是0xFFFFFFFD,那么就看PSP寄存器。如果R14(LR)0xFFFFFFF9,那么就看MSP寄存器。
注:下图摘抄自CM3权威指南9.6异常返回值章节。

手动栈回溯

(1)很好,既然我们有上述知识了之后。因为R14(LR),我们就知道现在要看PSP寄存器的值了,之后内存信息知道堆栈数据,然后手动栈回溯了解HardFault_Handler之前是卡死在哪里。
(2)现在我们看PSP寄存器,知道堆栈寄存器存储的是0x00000020。那么就打开Memory工具,查看当前芯片内部的存储信息。
这里需要注意,PSP的堆栈寄存器,不是PC或者LR寄存器,因此是看的Memory工具而不是Disassembly工具。

(3)根据堆栈指针寄存器,我们知道了当前堆栈指向位置存储的信息。那么如何根据这些信息进行问题的排查呢?这个时候我们就需要了解一下CM3/CM4的中断/异常的入栈知识了。CM3/CM4的中断响应时候,硬件是会自动进行入栈的,他的栈存储位置如下。
注:下图摘抄自CM3权威指南9.1.1入栈章节。

(4)之后我们就可以根据上面这个知识点,进行分析。可以得知,如果程序现在正常运行,那么应该是运行到0x08000145位置。

(5)于是我们根据0x08000145找到程序实际卡死的地方。发现是卡死在port.c文件中,而这个文件又是FreeRTOS的官方源码,出错概率很低。

解决思路定位可能的原因

(1)有了上述的分析,其实问题就已经解决一大半了。现在我们知道程序是卡死在0x08000145位置,于是我们可以在这个位置进行打断点。然后复位程序重新跑,此时Call Stack能够帮助我们进行栈回溯了。
注意:程序需要多按几次全速跑,一直等到进入HardFault_Handler的前一刻停止。因为我们不清楚到底是什么时候卡死在这个断点处的。

(2)然后我们就开始分析Call Stack的内容,发现除了TaskGenericFunction()其他的要么是FreeRTOS官方程序,要么是C库程序。于是我们直接看TaskGenericFunction()程序到底发生了什么。跳转过去之后,我们发现这个任务中只有一个printf()打印。我尝试逐步注释printf(),发现可以解决问题,然后就错误的认为这是和printf()线程安全有关导致的bug。但是后面发现,这并不问题的关键。

(3)如何发现真正的问题是什么?我们可以看TaskGenericFunction()函数的Location/Value,会发现这里居然是0x00000000!我们都知道STM32 的内存管理起始就是对0X0800 0000 开始的Flash部分和0x2000 0000 开始的SRAM部分使用管理。因此,这个地方毫无疑问是有问题的。出现这种问题,基本就可以知道是栈溢出的问题了。

FreeRTOS的uxTaskGetStackHighWaterMark()函数

(1)既然我们知道了是栈溢出的问题,那么如何知道是那个地方栈溢出了呢?此时就不得不是使用到FreeRTOSuxTaskGetStackHighWaterMark()函数了。这个函数可以监控任务使用的栈空间历史使用剩余值的最小值。使用方法如下:这里需要注意,需要在FreeRTOSConfig.h中将INCLUDE_uxTaskGetStackHighWaterMark设置为1,才可以使用这个函数。

/** * @brief   查看任务使用的栈空间大小 * * @param   xTask 任务句柄 * * @return 任务堆栈可用的最小值,单位word(4字节) */UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );/* === 使用方法 === */void StartCubemxTask(void *argument){  /* USER CODE BEGIN StartCubemxTask */        char *CubemxTaskPrintf = (char *)argument;        UBaseType_t Cubemx_Stack;  /* Infinite loop */  for(;;)  {                printf(CubemxTaskPrintf);                Cubemx_Stack = uxTaskGetStackHighWaterMark(keilTaskHandle);                printf("CubemxTask is %ld\r\n",Cubemx_Stack);                  }  /* USER CODE END StartCubemxTask */}

(2)了解了这个函数之后,那么我们尝试在每个任务中加入一个uxTaskGetStackHighWaterMark()监控栈使用情况,并且都打上断点。

(3)按照如下办法,之后开始查看他们的栈使用情况。

(4)我们一点一点的开始测试,最终发现是能够找到栈空间被榨干的地方了。

(5)现在我们思考一下为什么这个地方栈空间会被榨干。我们知道,起始是执行了下面这个for循环之后,栈空间就为空了。因此我们再看一下任务分配的时候任务栈空间是多少,能够发现是100 word,也就是400字节。而这里的buf[]就有500字节了。于是问题就成功发现了,此时我们可以尝试调小buf[]或者是增大任务栈空间。

for ( i = 0; i < 500; i++)        buf = 0;



使用特权

评论回复
沙发
黑心单片机| | 2024-6-27 16:58 | 只看该作者
这些方法很好

使用特权

评论回复
板凳
埃娃| | 2024-6-28 17:28 | 只看该作者
栈空间应该怎么设置大小啊

使用特权

评论回复
地板
dspmana| | 2024-7-4 21:42 | 只看该作者
这个中断向量指向一个硬故障处理程序,也称为异常处理服务程序

使用特权

评论回复
5
biechedan| | 2024-7-7 13:48 | 只看该作者
优化程序代码,减少栈的使用。例如,尽量减少递归调用,使用尾递归优化等。

使用特权

评论回复
6
alvpeg| | 2024-7-7 15:21 | 只看该作者
在某些微控制器中,可以设置堆栈溢出保护机制。当检测到栈溢出时,系统会自动进入安全模式,并禁止进一步的程序执行。这可以帮助防止系统崩溃,并为开发者提供调试信息。

使用特权

评论回复
7
bartonalfred| | 2024-7-9 20:20 | 只看该作者
当程序的栈指针超出其分配的内存范围时,处理器会触发HardFault异常。

使用特权

评论回复
8
febgxu| | 2024-7-9 23:22 | 只看该作者
硬错误(Hard Fault)是ARM Cortex-M系列微控制器在遇到无法处理的异常时触发的一种异常类型

使用特权

评论回复
9
usysm| | 2024-7-10 02:23 | 只看该作者
实施栈溢出检测机制,如前文所述的栈边界哨兵方法,以便在栈溢出发生时及时处理。

使用特权

评论回复
10
loutin| | 2024-7-10 05:23 | 只看该作者
尝试访问未分配的内存区域或者权限不允许的内存区域,比如读写只读内存或者外部存储器。

使用特权

评论回复
11
micoccd| | 2024-7-10 10:23 | 只看该作者
最怕遇到hardfault问题

使用特权

评论回复
12
albertaabbot| | 2024-7-10 20:36 | 只看该作者
在函数中定义了非常大的局部变量数组或结构体,尤其是如果这些函数被频繁调用。

使用特权

评论回复
13
wilhelmina2| | 2024-7-10 23:39 | 只看该作者
审查代码以减少递归调用的深度,避免过大的局部变量数组,以及减少不必要的函数调用。这些措施可以减少栈的使用量。

使用特权

评论回复
14
ingramward| | 2024-7-12 10:54 | 只看该作者
减少递归深度,优化函数调用结构,避免在函数中使用过大的局部变量。

使用特权

评论回复
15
lzmm| | 2024-7-12 14:01 | 只看该作者
减少不必要的函数调用和局部变量,特别是大型数组和结构体。

使用特权

评论回复
16
robincotton| | 2024-7-12 17:27 | 只看该作者
如果可行,使用循环结构代替递归以减少栈的使用。

使用特权

评论回复
17
backlugin| | 2024-7-12 20:32 | 只看该作者
如果栈溢出发生在异常处理程序中,那么异常处理程序本身可能无法正常执行,因为它的执行也需要栈空间。这可能导致系统无法恢复,甚至重启失败。

使用特权

评论回复
18
pixhw| | 2024-7-13 09:41 | 只看该作者
现代编译器提供了许多优化选项,可以帮助减少栈空间的使用。

使用特权

评论回复
19
zerorobert| | 2024-7-13 13:15 | 只看该作者
特别是使用ARM Cortex-M系列处理器的单片机系统中,栈溢出可能会导致所谓的"HardFault"(硬故障)异常。

使用特权

评论回复
20
janewood| | 2024-7-13 16:50 | 只看该作者
无限递归会导致栈空间被持续消耗直至耗尽。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

41

主题

1263

帖子

0

粉丝