查找堆栈框架大小

ale*_*nst 15 c linux gcc

调用函数的堆栈帧可以很容易地通过__builtin_frame_address(1),但堆栈帧大小怎么样?

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

Ruu*_*man 18

我的第一反应是,为什么有人想要这个?对于C函数来说,动态确定堆栈帧的大小应该被认为是不好的做法.整点CDECL(经典的C调用约定)是函数本身("被叫")没有堆栈帧的大小的知识.任何转向该哲学都可能导致您的代码在切换到不同的平台,不同的地址大小(例如从32位到64位),不同的编译器甚至不同的编译器设置(特别是优化)时中断.

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

文档:

帧地址通常是函数推入堆栈的第一个字的地址.

在x86上,函数通常以以下内容开头:

push ebp       ; bp for 16-bit, ebp for 32-bit, rbp for 64-bit
Run Code Online (Sandbox Code Playgroud)

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

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

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

size_localvars = basepointer - stackpointer;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

但是,如果你的函数有一个可变数量的参数('省略号',比如printfhas),那么参数的大小只有调用者知道.除非被调用者有一种方法来获取参数的大小和数量(如果是printf-style函数,通过分析格式字符串),你必须让调用者将该信息传递给被调用者.

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

但是,被调用者可以对调用者的局部变量的大小做出有根据的猜测.该块从被调用者的参数end(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;
}
Run Code Online (Sandbox Code Playgroud)