查找堆栈框架大小

时间:2014-02-01 17:31:24

标签: c linux gcc

可以通过__builtin_frame_address(1)轻松获取调用函数的堆栈帧,但堆栈帧大小又如何呢?

是否有一个函数可以让我知道调用函数的堆栈帧有多大?

1 个答案:

答案 0 :(得分:18)

我的第一反应是,为什么有人想要这个?对于C函数来说,动态确定堆栈帧的大小应该被认为是不好的做法。 cdecl 经典C调用约定)的重点是函数本身('callee')不知道堆栈帧的大小。从这种理念转移可能会导致代码在切换到不同的平台,不同的地址大小(例如从32位到64位),不同的编译器甚至不同的编译器设置(特别是优化)时中断。 / p>

另一方面,由于 gcc 已经提供了这个函数__builtin_frame_address,因此可以看到可以从那里获得多少信息。

来自documentation

  

帧地址通常是函数推送到堆栈的第一个字的地址。

在x86上,函数通常以:

开头
push ebp       ; bp for 16-bit, ebp for 32-bit, rbp for 64-bit

换句话说,__builtin_frame_address返回调用者堆栈帧的基指针。 不幸的是,基指针很少或根本没有说明任何堆栈帧的开始或结束位置; 基指针指向位于堆栈框架中间某处的位置(在参数局部变量之间)。

如果您只对包含局部变量的堆栈帧的部分感兴趣,那么函数本身就具有所有知识。该部分的大小是堆栈指针和基指针之间的差异。

register char * const basepointer  asm("ebp");
register char * const stackpointer asm("esp");

size_localvars = basepointer - stackpointer;

请记住,gcc似乎从一开始就在堆栈上分配空间,用于保存从被调用者内部调用的其他函数的参数。严格来说,该空间属于其他函数的堆栈帧,但边界不清楚。这是否是一个问题,取决于你的目的;您将如何处理计算的堆栈帧大小?

至于其他部分(参数),这取决于。如果您的函数具有固定数量的参数,那么您可以简单地测量(形式)参数的大小。它并不能保证调用者实际上在堆栈上推送了相同数量的参数,但假设调用者编译时没有针对被调用者原型的警告,那么应该没问题。

void callee(int a, int b, int c, int d)
{
    size_params = sizeof d + (char *)&d - (char *)&a;
}

您可以结合使用这两种技术来获得完整的堆栈帧(包括返回地址和保存的基指针):

register char * const stackpointer asm("esp");

void callee(int a, int b, int c, int d)
{
    total_size = sizeof d + (char *)&d - stackpointer;
}

但是,如果您的函数具有可变数量的参数('省略号',如printf已有),则参数的大小仅为调用者所知。除非被调用者有一种方法来获取参数的大小和数量(在printf - 样式函数的情况下,通过分析格式字符串),否则您必须让调用者将该信息传递给被调用者。

修改 请注意,这只能让函数测量自己的堆栈帧。被叫者无法计算其呼叫者的堆栈帧大小;被叫者必须向呼叫者询问该信息。

然而,callee 可以对调用者的局部变量的大小做出有根据的猜测。该块从被调用者的参数结束(sizeof d + (char *)&d)开始,并以调用者的基指针(__builtin_frame_address(1))结束。由于编译器强加的地址对齐,起始地址可能稍微不准确;计算出的大小可能包括一块未使用的堆栈空间。

void callee(int a, int b, int c, int d)
{
   size_localvars_of_caller = __builtin_frame_address(1) - sizeof d - (char *)&d;
}