在编译时检查堆栈使用情况
Posted
技术标签:
【中文标题】在编译时检查堆栈使用情况【英文标题】:Checking stack usage at compile time 【发布时间】:2010-09-12 15:59:51 【问题描述】:有没有办法在 C 语言编译时知道并输出函数所需的堆栈大小? 以下是我想知道的:
让我们使用一些函数:
void foo(int a)
char c[5];
char * s;
//do something
return;
在编译这个函数时,我想知道调用它时会消耗多少堆栈空间。这对于检测隐藏大缓冲区的结构的堆栈声明可能很有用。
我正在寻找可以打印这样的东西:
文件 foo.c : 函数 foo 堆栈使用为 n
字节
有没有办法不查看生成的程序集来知道这一点?还是编译器可以设置的限制?
更新:我不是试图避免给定进程的运行时堆栈溢出,我正在寻找一种方法来在运行时之前找到,如果编译器确定的函数堆栈使用情况可用作编译的输出过程。
让我们换一种说法:是否有可能知道函数本地所有对象的大小?我猜编译器优化不会是我的朋友,因为一些变量会消失,但一个高级限制是好的。
【问题讨论】:
如果你想知道,我输入了秘密 '' 字符 这个问题对我来说似乎不清楚。我想如果你写更多关于为什么你想知道这个以及为什么检查反汇编或可执行文件(这是检查编译器输出的最简单方法)是不可接受的,也许有人可以找到一些简单的解决方案? 【参考方案1】:Linux 内核代码在 x86 上的 4K 堆栈上运行。因此他们关心。他们用来检查的是他们编写的 perl 脚本,您可以在最近的内核 tarball 中找到它作为 scripts/checkstack.pl(2.6.25 有它)。它在 objdump 的输出上运行,使用文档在初始注释中。
我想我很久以前就已经将它用于用户空间二进制文件了,如果你懂一点 perl 编程,如果它坏了,很容易修复它。
无论如何,它的基本作用是自动查看 GCC 的输出。事实上,内核黑客编写了这样一个工具,这意味着使用 GCC 没有静态方法(或者它可能是最近添加的,但我对此表示怀疑)。
顺便说一句,使用 mingw 项目中的 objdump 和 ActivePerl,或者使用 Cygwin,您应该能够在 Windows 以及使用其他编译器获得的二进制文件上执行此操作。
【讨论】:
更新:Michael Greene 在下面指出 GCC 4.6 具有可用于 C 的 -fstack-usage:gcc.gnu.org/gcc-4.6/changes.html; -fstack-usage 在 shodanex 的回答中描述如下:***.com/a/126490/53974.【参考方案2】:StackAnlyser 似乎检查了可执行代码本身以及一些调试信息。 this reply 所描述的是我正在寻找的东西,堆栈分析器对我来说看起来有点矫枉过正。
类似于 ADA 存在的东西就可以了。从 gnat 手册中查看此手册页:
22.2 静态堆栈使用分析
使用 -fstack-usage 编译的单元将生成一个额外的文件,该文件指定每个函数使用的最大堆栈数量。该文件与目标对象文件具有相同的基本名称,扩展名为 .su。该文件的每一行由三个字段组成:
* The name of the function.
* A number of bytes.
* One or more qualifiers: static, dynamic, bounded.
第二个字段对应于函数帧已知部分的大小。
限定符 static 表示函数帧大小是纯静态的。这通常意味着所有局部变量都具有静态大小。在这种情况下,第二个字段是函数堆栈利用率的可靠度量。
限定符动态意味着函数帧大小不是静态的。它主要发生在某些局部变量具有动态大小时。当这个限定符单独出现时,第二个字段不是函数堆栈分析的可靠度量。当它有界时,意味着第二个字段是函数堆栈利用率的可靠最大值。
【讨论】:
这是一个老问题,但 fwiw GCC 4.6 有 -fstack-usage 可用于 C:gcc.gnu.org/gcc-4.6/changes.html @Michael Greene:这可能是一个答案!谢谢!【参考方案3】:我不明白为什么静态代码分析无法为此提供足够好的数字。
在任何给定函数中查找所有局部变量是微不足道的,每个变量的大小可以通过 C 标准(对于内置类型)或通过计算它(对于结构和联合等复杂类型)来找到。
当然,不能保证答案是 100% 准确的,因为编译器可以进行各种优化,例如填充、将变量放入寄存器或完全删除不必要的变量。但它给出的任何答案至少应该是一个很好的估计。
我快速谷歌搜索,找到了StackAnalyzer,但我猜其他静态代码分析工具也有类似的功能。
如果您想要一个 100% 准确的数字,那么您必须查看编译器的输出或在运行时检查它(就像 Ralph 在his reply 中建议的那样)
【讨论】:
StackAnalyzer 看起来不错,但它并没有完成所要求的工作,因为它分析的是可执行文件,而不是源代码。虽然理论上应该真的可以编写这样的工具,但我不认为存在这样的工具 - 检查堆栈使用运行时或基于 assemly 非常实用。 我只说一个函数名,你就知道为什么静态代码分析不足以得到一个函数的已用堆栈空间:alloca。一旦一个函数使用它(具有非 const 值),您就无法使用它。已经提到的另一件事:递归。【参考方案4】:只有编译器才会真正知道,因为它是把你所有的东西放在一起的人。您必须查看生成的程序集并查看序言中保留了多少空间,但这并不能真正解释像 alloca
这样在运行时执行它们的操作。
【讨论】:
理论上静态代码分析工具如 lint 可以完成这项工作,实际上我认为他们做不到。【参考方案5】:假设您使用的是嵌入式平台,您可能会发现您的工具链在这方面有所帮助。良好的商业嵌入式编译器(例如 Arm/Keil 编译器)通常会生成堆栈使用报告。
当然,中断和递归通常有点超出它们的范围,但是如果有人在堆栈上的某个地方使用数兆字节的缓冲区犯了一些可怕的错误,它会给你一个粗略的想法。
【讨论】:
【参考方案6】:不完全是“编译时间”,但我会将此作为构建后步骤:
让链接器为您创建地图文件 为map文件中的每个函数读取可执行文件的对应部分,并分析函数序言。这类似于 StackAnalyzer 所做的,但要简单得多。我认为分析可执行文件或反汇编是获得编译器输出的最简单方法。虽然编译器内部知道这些东西,但恐怕您无法从中获取(您可能会要求编译器供应商实现该功能,或者如果使用开源编译器,您可以自己做或让别人做给你)。
要实现这一点,您需要:
能够解析地图文件 了解可执行文件的格式 了解函数序言的外观并能够“解码”它这将是多么容易或困难取决于您的目标平台。 (嵌入式?哪个 CPU 架构?什么编译器?)
所有这些都可以在 x86/Win32 中完成,但如果您从未做过类似的事情并且必须从头开始创建所有这些,则可能需要几天时间才能完成并开始工作。
【讨论】:
【参考方案7】:一般不会。理论计算机科学中的停止问题表明,您甚至无法预测通用程序是否在给定输入上停止。计算用于程序运行的堆栈通常会更加复杂。所以不行。也许在特殊情况下。
假设你有一个递归函数,它的递归级别取决于输入,输入可以是任意长度,而你已经不走运了。
【讨论】:
您在谈论进程堆栈,而不是每次调用函数将使用多少 停机问题不会停止静态分析(即在编译器中),并通过查看程序文本给出近似答案。事实上,当两个不同的程序等价时,计算是一个很大的挑战,因此两个等价的程序在静态分析下可能会给出不同的结果。以上是关于在编译时检查堆栈使用情况的主要内容,如果未能解决你的问题,请参考以下文章