primojones 发表于 2024-6-25 05:10

栈溢出导致hardfault问题

问题复现(1)首先看一下网友的问题:
<1>他说程序运行不到1分钟就进入hardFault。这个代码量很少,所以能够很快定位到问题,但是如果程序大了,如何定位问题。
<2>他说可以尝试通过查看R0–R15寄存器是否可以找出问题。
(2)首先回答第一个问题,对于程序的问题定位,肯定是一个复杂而又漫长的过程,经常能够遇到一些玄学问题因此我们要学会掌握技巧,下面的方法就是技巧之一。其次是第二个问题,这明显就是没搞明白寄存器的作用分别是干嘛的,完全是胡乱看寄存器。
(3)问题如下:https://img-blog.csdnimg.cn/direct/734374a51dc04575883741732ee866e7.png排查流程栈回溯分析(1)既然知道了问题是什么了,那么就开始排查,根据前面的介绍,我们可以通过keil的debug工具中的Call Stack进行栈回溯。分析程序卡死之前被哪些函数调用,然后逐步分析这些函数可能的问题。
(2)但是,不幸的是,我们会发现Call Stack中只有一个HardFault_Handler。那么明显说明Call Stack工具现在是用不了的。https://img-blog.csdnimg.cn/direct/039924a8504142538869db4334bfd217.pngCM3和CM4异常返回值(1)之前那篇博客,是介绍的普通的程序卡死如何进行排查,然后栈回溯调试。而这里有些许的不同,在于是hardfault错误,因此只有这个地方会不同。
(2)前文我介绍了,程序卡死一般是看R14(LR)寄存器,因为这个寄存器存放返回值信息,通俗来说就是C语言的return根据这个寄存器进行函数返回。
注:下图摘抄自CM3权威指南3.1寄存器组章节。https://img-blog.csdnimg.cn/direct/165bb6d78a1546018d3eac2497a4a36c.png(3)那么现在我们看一下R14(LR)寄存器值是什么。我们会发现里面居然是0xFFFFFFFD!很明显,我函数返回不可能是返回到一个0xFFFFFFFD,因为程序一般都是存储在0x08开头的位置。那么这个时候我们就需要来了解一下CM3和CM4的异常返回值的知识了。https://img-blog.csdnimg.cn/direct/06680bdd097944a7a65fafcb79dc1b8a.png(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异常返回值章节。https://img-blog.csdnimg.cn/direct/419ac58c6fa149a2adcd8b8208958921.png手动栈回溯(1)很好,既然我们有上述知识了之后。因为R14(LR),我们就知道现在要看PSP寄存器的值了,之后内存信息知道堆栈数据,然后手动栈回溯了解HardFault_Handler之前是卡死在哪里。
(2)现在我们看PSP寄存器,知道堆栈寄存器存储的是0x00000020。那么就打开Memory工具,查看当前芯片内部的存储信息。
这里需要注意,PSP的堆栈寄存器,不是PC或者LR寄存器,因此是看的Memory工具而不是Disassembly工具。https://img-blog.csdnimg.cn/direct/0d6a221d3dba4fd9a6a9416206c4636a.png(3)根据堆栈指针寄存器,我们知道了当前堆栈指向位置存储的信息。那么如何根据这些信息进行问题的排查呢?这个时候我们就需要了解一下CM3/CM4的中断/异常的入栈知识了。CM3/CM4的中断响应时候,硬件是会自动进行入栈的,他的栈存储位置如下。
注:下图摘抄自CM3权威指南9.1.1入栈章节。https://img-blog.csdnimg.cn/direct/f5f2ed139c0949389af8ec4ddb8f318a.png(4)之后我们就可以根据上面这个知识点,进行分析。可以得知,如果程序现在正常运行,那么应该是运行到0x08000145位置。https://img-blog.csdnimg.cn/direct/3b52a1178d9e46de929d4f45fd220d94.png(5)于是我们根据0x08000145找到程序实际卡死的地方。发现是卡死在port.c文件中,而这个文件又是FreeRTOS的官方源码,出错概率很低。https://img-blog.csdnimg.cn/direct/29b552e6da484f088a9ab42ac4e41ba3.png解决思路定位可能的原因(1)有了上述的分析,其实问题就已经解决一大半了。现在我们知道程序是卡死在0x08000145位置,于是我们可以在这个位置进行打断点。然后复位程序重新跑,此时Call Stack能够帮助我们进行栈回溯了。
注意:程序需要多按几次全速跑,一直等到进入HardFault_Handler的前一刻停止。因为我们不清楚到底是什么时候卡死在这个断点处的。https://img-blog.csdnimg.cn/direct/cbe9cad069b34e7f951bb0265d0f720e.png(2)然后我们就开始分析Call Stack的内容,发现除了TaskGenericFunction()其他的要么是FreeRTOS官方程序,要么是C库程序。于是我们直接看TaskGenericFunction()程序到底发生了什么。跳转过去之后,我们发现这个任务中只有一个printf()打印。我尝试逐步注释printf(),发现可以解决问题,然后就错误的认为这是和printf()线程安全有关导致的bug。但是后面发现,这并不问题的关键。https://img-blog.csdnimg.cn/direct/c02c89fb59c948439cf4710a94de2bfa.png(3)如何发现真正的问题是什么?我们可以看TaskGenericFunction()函数的Location/Value,会发现这里居然是0x00000000!我们都知道STM32 的内存管理起始就是对0X0800 0000 开始的Flash部分和0x2000 0000 开始的SRAM部分使用管理。因此,这个地方毫无疑问是有问题的。出现这种问题,基本就可以知道是栈溢出的问题了。https://img-blog.csdnimg.cn/direct/63dc4b96c0874eb7be856fd10c158098.pngFreeRTOS的uxTaskGetStackHighWaterMark()函数(1)既然我们知道了是栈溢出的问题,那么如何知道是那个地方栈溢出了呢?此时就不得不是使用到FreeRTOS的uxTaskGetStackHighWaterMark()函数了。这个函数可以监控任务使用的栈空间历史使用剩余值的最小值。使用方法如下:这里需要注意,需要在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()监控栈使用情况,并且都打上断点。https://img-blog.csdnimg.cn/direct/2f4dc793fc3f49e893dd2fb2a88cfe09.png(3)按照如下办法,之后开始查看他们的栈使用情况。https://img-blog.csdnimg.cn/direct/2f4645baeb544ae7b68e4982e906139c.png(4)我们一点一点的开始测试,最终发现是能够找到栈空间被榨干的地方了。https://img-blog.csdnimg.cn/direct/b4a4d70f7d6d4f26bc0801ae4aab9a3c.png(5)现在我们思考一下为什么这个地方栈空间会被榨干。我们知道,起始是执行了下面这个for循环之后,栈空间就为空了。因此我们再看一下任务分配的时候任务栈空间是多少,能够发现是100 word,也就是400字节。而这里的buf[]就有500字节了。于是问题就成功发现了,此时我们可以尝试调小buf[]或者是增大任务栈空间。for ( i = 0; i < 500; i++)        buf = 0;https://img-blog.csdnimg.cn/direct/3bc02dad2b12439ab50be224988f29fa.png

黑心单片机 发表于 2024-6-27 16:58

这些方法很好

埃娃 发表于 2024-6-28 17:28

栈空间应该怎么设置大小啊

dspmana 发表于 2024-7-4 21:42

这个中断向量指向一个硬故障处理程序,也称为异常处理服务程序

biechedan 发表于 2024-7-7 13:48

优化程序代码,减少栈的使用。例如,尽量减少递归调用,使用尾递归优化等。

alvpeg 发表于 2024-7-7 15:21

在某些微控制器中,可以设置堆栈溢出保护机制。当检测到栈溢出时,系统会自动进入安全模式,并禁止进一步的程序执行。这可以帮助防止系统崩溃,并为开发者提供调试信息。

bartonalfred 发表于 2024-7-9 20:20

当程序的栈指针超出其分配的内存范围时,处理器会触发HardFault异常。

febgxu 发表于 2024-7-9 23:22

硬错误(Hard Fault)是ARM Cortex-M系列微控制器在遇到无法处理的异常时触发的一种异常类型

usysm 发表于 2024-7-10 02:23

实施栈溢出检测机制,如前文所述的栈边界哨兵方法,以便在栈溢出发生时及时处理。

loutin 发表于 2024-7-10 05:23

尝试访问未分配的内存区域或者权限不允许的内存区域,比如读写只读内存或者外部存储器。

micoccd 发表于 2024-7-10 10:23

最怕遇到hardfault问题

albertaabbot 发表于 2024-7-10 20:36

在函数中定义了非常大的局部变量数组或结构体,尤其是如果这些函数被频繁调用。

wilhelmina2 发表于 2024-7-10 23:39

审查代码以减少递归调用的深度,避免过大的局部变量数组,以及减少不必要的函数调用。这些措施可以减少栈的使用量。

ingramward 发表于 2024-7-12 10:54

减少递归深度,优化函数调用结构,避免在函数中使用过大的局部变量。

lzmm 发表于 2024-7-12 14:01

减少不必要的函数调用和局部变量,特别是大型数组和结构体。

robincotton 发表于 2024-7-12 17:27

如果可行,使用循环结构代替递归以减少栈的使用。

backlugin 发表于 2024-7-12 20:32

如果栈溢出发生在异常处理程序中,那么异常处理程序本身可能无法正常执行,因为它的执行也需要栈空间。这可能导致系统无法恢复,甚至重启失败。

pixhw 发表于 2024-7-13 09:41

现代编译器提供了许多优化选项,可以帮助减少栈空间的使用。

zerorobert 发表于 2024-7-13 13:15

特别是使用ARM Cortex-M系列处理器的单片机系统中,栈溢出可能会导致所谓的"HardFault"(硬故障)异常。

janewood 发表于 2024-7-13 16:50

无限递归会导致栈空间被持续消耗直至耗尽。
页: [1] 2 3
查看完整版本: 栈溢出导致hardfault问题