在IAR Embedded Workbench中分析和监控堆栈使用

 

堆栈是嵌入式系统中非常基础但又特别重要的概念。正确设置堆栈的大小对于嵌入式系统的稳定性和可靠性非常重要。如果堆栈设置过小,可能会引起堆栈溢出,从而导致系统运行异常。反之,如果堆栈设置过大,则会造成宝贵RAM资源的浪费。由于堆栈需要开发人员指定分配,因此开发人员需要知道堆栈的使用情况,从而正确设置堆栈的大小。

本文主要介绍如何在IAR Embedded Workbench中分析和监控堆栈的使用,帮助开发人员正确设置堆栈的大小,提高系统的稳定性和可靠性,同时最大程度地减小RAM资源的使用。

关于堆栈的基本介绍,请参考之前的文章:掌控堆栈,确保系统稳定

静态堆栈使用分析

在正确的情况下,链接器可以准确地计算出每个根函数(根函数是一个不被任何其他函数调用的函数,包括程序启动函数,中断函数和RTOS任务函数等)的最大堆栈使用。当在IAR Embedded Workbench 使能堆栈分析之后,堆栈使用将被添加到链接器生成的map文件中,列出每个根函数的最大堆栈深度。

在Project>Options>Linker>Advanced中,选中Enable stack usage analysis来使能堆栈使用分析功能:

由于堆栈使用分析的结果将会包含在链接器生成的map文件中,需要在Project>Options>Linker> List选项卡中选中Generate Linker map file:

在下面的例子中,程序入口根函数 (__iar_program_start) 的最大堆栈深度为 288 字节,中断根函数 (__interrupt_170 and _default_handler) 的最大堆栈深度为 120 字节。

*************************************************************************
*** STACK USAGE
***  

Call Graph Root Category  Max Use  Total Use  
------------------------  -------  ---------
interrupt                     120        120
Program entry                 288        288

Program entry
  "__iar_program_start": 0xffffb14c
  
Maximum call chain                                288 bytes
    "__iar_program_start"                             4
    "_main"                                           8
    "_printf"                                         8
    "__PrintfFullNoMb"                              152
    "__LdtobFullNoMb" in xprintffull_nomb.o [4]      80
    "__GenldFullNoMb" in xprintffull_nomb.o [4]      36

interrupt
  "__interrupt_170": 0xffffaa22
  
Maximum call chain                                52 bytes
    "__interrupt_170"                               52

interrupt
 "_default_handler": 0xffff98cb

  Maximum call chain                                68 bytes

    "_default_handler"                              52
    "_abort"                                         4
    "__exit"                                        12

注意:

  1. 只有每个函数的堆栈使用信息是准确的,计算出来的最大堆栈深度才是准确的。正常来说,编译器会为每个函数生成对应的堆栈使用信息。但是如果有通过函数指针进行函数的间接调用,需要开发人员提供对应的调用信息。
  2. map文件里面的最大堆栈深度没有包含中断/任务抢占需要的额外堆栈,需要开发人员手动添加计算。更多信息请参考 Arm Cortex-M 专家 Joseph Yiu 的文章:How much stack memory do I need for my Arm Cortex-M applications? (文末参考文献)

运行时堆栈使用监控

静态堆栈使用分析在构建时计算出理论上的最大堆栈深度。然而在程序运行过程中,实际的堆栈消耗一般很难到静态堆栈使用分析计算出的理论上的最大深度。IAR Embedded Workbench提供了一种方法来监控程序运行时的堆栈使用,由 C-SPY 调试器实现。C-SPY 调试器可以在应用程序开始执行前用一个特殊数值模式,例如 0xCD 来填充整个堆栈区域。在程序运行一段时间后(最好是在某些测试条件下),堆栈内存可以从其末端向上检查,直到找到一个与 0xCD 不同的值,这可以被认为是 SP 曾经到达的最大位置。由于堆栈内存中仍然包含 0xCD 的部分从未被覆盖过,因此将堆栈的大小减少到这个数量是安全的。当然,最好保留一点额外的空间,以防您的测试没有持续足够长的时间,或者没有准确地反映所有可能的运行情况。

在IDE Options> Stack选项中勾选Enable graphical stack display and stack usage tracking以启用运行时堆栈使用跟踪:

调试时,可通过View>Stack打开Stack窗口,每当执行停止时,C-SPY 会在Stack窗口中更新堆栈的使用情况:

图形显示堆栈条的左端代表堆栈的底部,即堆栈为空时 SP 的位置。右端代表为堆栈保留的内存空间的末端。深灰色区域代表已使用的堆栈内存,浅灰色区域代表未使用的堆栈内存。当堆栈使用超过阈值(可在“IDE Options”对话框中设置)时,图形堆栈条会变成红色。

注意:

  1. 该功能无法在发生堆栈溢出时及时检测到,而只能检测留下的痕迹。尽管这是一个检测堆栈使用的可靠方法,但这并不能保证一定能检测到堆栈溢出。例如,当堆栈发生溢出,可以不正确地在其范围外增长,甚至改变了堆栈区域以外的内存,但是堆栈以内的数据没有被修改。
  2. 该功能只能用于监控系统的主堆栈,不能监控RTOS里面的任务堆栈。为此IAR Embedded Workbench提供了对应RTOS的调试插件,用于监控运行时RTOS的任务堆栈。
    在Debugger>Plugin选项中,勾选对应的RTOS:

调试时就会有对应的RTOS插件的选项,暂停之后,就可以查看对应RTOS的任务堆栈使用情况:

总结

正确设置堆栈的大小对于嵌入式系统的稳定性和可靠性非常重要,本文主要介绍了如何在IAR Embedded Workbench中进行静态堆栈使用分析和运行时堆栈使用监控,帮助开发人员知道应用程序中的堆栈使用情况,提高系统的稳定性和可靠性。


参考文献:

  1. IAR C/C++ Development Guide (Stack usage analysis)
  2. How much stack memory do I need for my Arm Cortex-M applications? : https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/how-much-stack-memory-do-i-need-for-my-arm-cortex--m-applications
  3. C-SPY® Debugging Guide (Monitoring stack usage)
  4. IAR Embedded Workbench Integrated software and RTOS:https://www.iar.com/products/integrations/