在编译时检查堆栈使用情况
有没有一种方法可以在C语言中的编译时知道和输出函数所需的堆栈大小?
这是我想知道的:
让我们来做一些功能:
void foo(int a) { char c[5]; char * s; //do something return; }
编译此函数时,我想知道调用该函数将消耗多少堆栈空间。这对于检测隐藏大缓冲区的结构的堆栈声明可能很有用。
我正在寻找可以打印如下内容的东西:
文件foo.c:函数foo堆栈的使用量为n个字节
有没有办法不看看生成的程序集就知道这一点?还是可以为编译器设置的限制?
更新:我不是在尝试避免给定进程的运行时堆栈溢出,而是在寻找一种在运行时之前查找的方法,以确定编译器确定的函数堆栈用法是否可用作编译过程的输出。
让我们换一种说法:是否可以知道函数本地所有对象的大小?我猜编译器优化不会成为我的朋友,因为有些变量会消失,但更好的限制就可以了。
解决方案
只有编译器才真正知道,因为正是这个家伙将我们所有的东西放在一起。我们必须查看生成的程序集,并查看在序言中保留了多少空间,但这并不能真正解决诸如`alloca'之类在运行时完成工作的事情。
不一般。理论计算机科学中的"暂停问题"表明,我们甚至无法预测通用程序是否在给定输入上暂停。计算通常用于程序运行的堆栈会更加复杂。所以不行。也许在特殊情况下。
假设我们有一个递归函数,其递归级别取决于输入,该输入可以是任意长度,并且我们已经不走运了。
我不明白为什么静态代码分析不能为此提供足够好的数据。
在任何给定的函数中找到所有局部变量都是很简单的,每个变量的大小都可以通过C标准(对于内置类型)或者通过对其进行计算(对于复杂的类型,如结构和联合)来找到。
当然,不能保证答案是100%准确的,因为编译器可以进行各种优化,例如填充,将变量放入寄存器或者完全删除不必要的变量。但是它给出的任何答案至少应该是一个很好的估计。
我做了一个快速的谷歌搜索,发现StackAnalyzer,但我的猜测是其他静态代码分析工具具有类似的功能。
如果我们想获得100%准确的数字,则必须查看编译器的输出或者在运行时进行检查(例如Ralph在他的答复中建议)
StackAnlyser似乎正在检查可执行代码本身以及一些调试信息。
此回复描述的是我正在寻找的内容,堆栈分析器对我来说似乎有些过激。
类似于ADA的东西就可以了。请从gnat手册中查看此手册页:
22.2静态堆栈使用情况分析
使用-fstack-usage编译的单元将生成一个额外的文件,该文件指定每个函数所使用的最大堆栈量。该文件的名称与目标对象文件的名称相同,扩展名为.su。该文件的每一行都由三个字段组成:
* The name of the function. * A number of bytes. * One or more qualifiers: static, dynamic, bounded.
第二字段对应于功能框的已知部分的大小。
限定词是静态的,意味着功能框架的大小纯粹是静态的。通常,这意味着所有局部变量都具有静态大小。在这种情况下,第二个字段是函数堆栈利用率的可靠度量。
限定符是动态的,意味着功能框架的大小不是静态的。它主要在某些局部变量具有动态大小时发生。当此限定符单独出现时,第二个字段不是函数堆栈分析的可靠度量。当它有边界限定时,表示第二个字段是函数堆栈利用率的可靠最大值。
假设我们使用的是嵌入式平台,则可能会发现工具链可以解决此问题。好的商用嵌入式编译器(例如Arm / Keil编译器)通常会生成有关堆栈使用情况的报告。
当然,中断和递归通常会超出它们的范围,但是如果有人在某个地方的堆栈上用了几兆字节的缓冲区犯了一些可怕的错误,那它给我们一个大概的想法。
不完全是"编译时间",但我会在构建后的步骤中执行此操作:
- 让链接器为我们创建一个映射文件
- 对于映射文件中的每个功能,请读取可执行文件的相应部分,并分析功能序言。
这类似于StackAnalyzer所做的事情,但简单得多。我认为分析可执行文件或者反汇编是获得编译器输出的最简单方法。尽管编译器内部了解这些内容,但恐怕我们将无法从中获得这些信息(我们可能会要求编译器供应商实现该功能,或者如果使用开放源代码编译器,则我们可以自己执行此操作或者让某人执行此操作为你)。
要实现这一点,我们需要:
- 能够解析地图文件
- 了解可执行文件的格式
- 知道函数序幕的外观,并能够对其进行"解码"
这将是多么容易或者困难取决于目标平台。 (嵌入式?哪种CPU架构?什么编译器?)
所有这些绝对可以在x86 / Win32中完成,但是如果我们从来没有做过这样的事情,而不得不从头开始创建所有这些,那么可能需要几天的时间才能完成工作。
Linux内核代码在x86的4K堆栈上运行。因此,他们关心。他们用来检查的是他们编写的perl脚本,我们可以在最近的内核tarball中找到scripts / checkstack.pl(2.6.25拥有它)。它在objdump的输出上运行,用法文档位于初始注释中。
我想我很早以前就已经在用户空间二进制文件中使用过它,如果我们了解一些perl编程,则很容易修复它是否已损坏。
无论如何,它的基本作用是自动查看GCC的输出。而且,内核黑客编写了这样的工具,这意味着没有静态方法可以使用GCC来进行此操作(或者可能是它是在最近才添加的,但我对此表示怀疑)。
顺便说一句,使用mingw项目中的objdump和ActivePerl,或者使用Cygwin,我们应该也可以在Windows以及使用其他编译器获得的二进制文件上执行此操作。