堆栈溢出是一个大问题:如果我看到系统崩溃,通常第一件事就是尝试增加堆栈大小以查看问题是否消失。 GNU 链接器可以检查我的全局变量是否适合 RAM。但它不知道我需要多少堆栈。那么,如果有办法找出我需要多少堆栈,该有多酷?
GNU 静态堆栈使用分析
事实上,这可以通过 GNU 工具实现(例如,我将它与 GNU ARM Embedded (launchpad) 4.8 和 4.9 编译器一起使用 :-)。不过这个能力好像并不广为人知?
概述
我使用了很长时间的一种方法是:
- 用定义的模式填充堆栈的内存。
- 让应用程序运行。
- 检查调试器有多少堆栈模式已被覆盖。
效果很好。除了它是非常经验的。我需要的是来自编译器的一些数字以获得更好的视图。
在本文中,我介绍了一种使用 GNU 工具和 Perl 脚本来报告应用程序中堆栈使用情况的方法。
GNU
-fstack-usage
编译器选项
GNU 编译器套件有一个有趣的选项:
-fstack-usage
“使用
-fstack-usage
编译的单元将生成一个额外的文件,该文件指定每个函数使用的最大堆栈量。该文件与带有
.su
扩展名的目标对象文件具有相同的基本名称。” (
https://gcc.gnu.org/onlinedocs/gnat_ugn/Static-Stack-Usage-Analysis.html
)
如果我将该选项添加到编译器设置,现在有一个 .su(堆栈用法)文件以及每个对象 (.o) 文件:
堆栈使用文件
这些文件是像这样的简单文本文件:
main.c:36:6:bar 48 static
main.c:41:5:foo 88 static
main.c:47:5:main 8 static
它列出了源文件 (main.c)、函数的行 (35) 和列 (5) 位置、函数名称 (bar)、堆栈使用字节数 (48) 和分配(静态,这是正常情况)。
创建堆栈报告
虽然 .su 文件已经是基于文件/函数的重要信息来源,但如何将它们组合起来以获得完整的图片?我找到了 Daniel Beer 开发的 Perl 脚本 (avstack.pl)(参见 http://dlbeer.co.nz/oss/avstack.html )。
在原始脚本中,您可能需要调整
$objdump
和
$call_cost
。使用
$objdump
我指定 GNU
objdump
命令(确保它存在于 PATH 中)并且
$call_cost
是添加到每次调用成本中的常量值:
main.c:36:6:bar 48 static
main.c:41:5:foo 88 static
main.c:47:5:main 8 static
使用目标文件列表调用 avstack.pl,例如
main.c:36:6:bar 48 static
main.c:41:5:foo 88 static
main.c:47:5:main 8 static
:idea: 您需要列出所有目标文件,脚本没有使用目录中所有 .o 文件的功能。我通常将对 Perl 文件的调用放入一个批处理文件中,我从构建后步骤调用该文件(请参阅“ 在 Eclipse 中作为构建后步骤执行多个命令 ”)。
这会生成如下报告:
main.c:36:6:bar 48 static
main.c:41:5:foo 88 static
main.c:47:5:main 8 static
- 前面带 '>' 的函数名表示'根'函数:它们不是从其他任何地方调用的(也许我没有传递所有目标文件,或者真的没有被使用)。
- 如果函数是递归的,则用 'R' 标记。成本估算将针对单个递归级别。
- Cost 显示累积堆栈使用量(此函数加上所有被调用者)。
-
Frame
是 .su 文件中使用的堆栈大小,包括
$call_cost
常量。 - 高度 表示由该函数引起的调用级别数。
注意 INTERRUPT 条目:它是中断所需的堆栈级别。该工具假定非嵌套中断:它将最坏情况下的中断向量 (IV) 堆栈使用情况计入峰值执行:
main.c:36:6:bar 48 static
main.c:41:5:foo 88 static
main.c:47:5:main 8 static
什么算作中断例程是由Perl脚本中的这部分控制的,所以每个以__vector_开头的函数都被视为中断例程:
main.c:36:6:bar 48 static
main.c:41:5:foo 88 static
main.c:47:5:main 8 static
汇编代码
如果我的项目中有内联汇编和汇编代码,那么编译器就无法报告堆栈使用情况。这些函数被报告为“零”堆栈使用:
main.c:36:6:bar 48 static
main.c:41:5:foo 88 static
main.c:47:5:main 8 static
编译器会警告我:
此目标不支持堆栈使用计算
:idea: 我还没有找到在源代码中向编译器提供该信息的方法。
实时操作系统任务
对于基于 RTOS(例如 FreeRTOS)的系统中的任务,该工具运行良好且开箱即用。因此,使用该工具,我可以很好地估计每个任务堆栈的使用情况,但我需要将中断堆栈的使用情况计入该值:
main.c:36:6:bar 48 static
main.c:41:5:foo 88 static
main.c:47:5:main 8 static
-Wstack-usage
警告
另一个有用的编译器选项是
-Wstack-usage
。使用此选项,只要堆栈使用量超过给定限制,编译器就会发出警告。
警告堆栈使用的选项
这样我就可以快速检查哪些函数超出了限制:
堆栈使用警告
概括
GNU 编译器套件带有非常有用的选项
-fstack-usage
,它为每个编译单元(源文件)生成列出堆栈使用情况的文本文件。这些文件可以进一步处理,我正在使用 Daniel Beer 创建的出色 Perl 脚本(谢谢!)。通过提供的工具和技术,我可以预先估计堆栈的使用情况。我知道这只是一个估计,递归只在最低级别计算,汇编代码不计算在内。我可能会扩展 Perl 文件以扫描文件夹中的所有目标文件,除非有人已经做过这个?如果是这样,请发表评论并分享 :-)。