GNU 静态堆栈使用分析

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 63w+ 字,讲解图 2808+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2200+ 小伙伴加入学习 ,欢迎点击围观

堆栈溢出是一个大问题:如果我看到系统崩溃,通常第一件事就是尝试增加堆栈大小以查看问题是否消失。 GNU 链接器可以检查我的全局变量是否适合 RAM。但它不知道我需要多少堆栈。那么,如果有办法找出我需要多少堆栈,该有多酷?

GNU 静态堆栈使用分析

事实上,这可以通过 GNU 工具实现(例如,我将它与 GNU ARM Embedded (launchpad) 4.8 和 4.9 编译器一起使用 :-)。不过这个能力好像并不广为人知?

概述

我使用了很长时间的一种方法是:

  1. 用定义的模式填充堆栈的内存。
  2. 让应用程序运行。
  3. 检查调试器有多少堆栈模式已被覆盖。

效果很好。除了它是非常经验的。我需要的是来自编译器的一些数字以获得更好的视图。

在本文中,我介绍了一种使用 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 文件以扫描文件夹中的所有目标文件,除非有人已经做过这个?如果是这样,请发表评论并分享 :-)。

相关文章