从堆栈指针中找出函数参数值

时间:2012-12-12 06:50:34

标签: c++ c linux arguments stack-trace

给定堆栈指针值,是否可以确定传递给函数的参数的值?参数在哪里存储在堆栈框架中。

让我们说,在Linux平台上gcc架构上执行x86已编译的ELF二进制文件:

int foo(int a, int b)
{
...
}
foo(a,b)调用

main(),我知道现在指向foo()的堆栈指针(SP)值。如何检索参数ab

的值

编辑:如果堆栈从较小的地址增加到较大的地址,并且使用cdecl从右到左传递参数,我是否可以获得这样的args值:

b = *(SP + 1);
a = *(SP + 2);

编辑:以下程序使用上面的拱门和规范打印函数args ab的值。

void foo(int a, int b)
{
        int i;
        register int stackptr asm("sp");
        int *sp = (int *)stackptr;
        printf("\n\ta=%d b=%d\n", a, b);
        for (i=0; i<16; i++) {
                printf("*(sp + %d) = %d\n", i, *(sp +i));
        }
}

int main()
{
        foo(3, 8);
        foo(9, 2);
        foo(1, 4);
        return 0;
}

上述代码的输出为:

        a=3 b=8
*(sp + 0) = 134514016
*(sp + 1) = 0
*(sp + 2) = 0
*(sp + 3) = 134513373
*(sp + 4) = 8239384
*(sp + 5) = 134513228
*(sp + 6) = 6
*(sp + 7) = -1076716032
*(sp + 8) = 134513456
*(sp + 9) = 0
*(sp + 10) = -1076715960
*(sp + 11) = 134513759
*(sp + 12) = 3  //value of arg a
*(sp + 13) = 8  //value of arg b
*(sp + 14) = 134513817
*(sp + 15) = 10612724

        a=9 b=2
*(sp + 0) = 134514016
*(sp + 1) = 0
*(sp + 2) = 0
*(sp + 3) = 134513373
*(sp + 4) = 8239384
*(sp + 5) = 134513228
*(sp + 6) = 6
*(sp + 7) = -1076716032
*(sp + 8) = 134513456
*(sp + 9) = 0
*(sp + 10) = -1076715960
*(sp + 11) = 134513779
*(sp + 12) = 9  //value of arg a
*(sp + 13) = 2  //value of arg b
*(sp + 14) = 134513817
*(sp + 15) = 10612724

        a=1 b=4
*(sp + 0) = 134514016
*(sp + 1) = 0
*(sp + 2) = 0
*(sp + 3) = 134513373
*(sp + 4) = 8239384
*(sp + 5) = 134513228
*(sp + 6) = 6
*(sp + 7) = -1076716032
*(sp + 8) = 134513456
*(sp + 9) = 0
*(sp + 10) = -1076715960
*(sp + 11) = 134513799
*(sp + 12) = 1  //value of arg a
*(sp + 13) = 4  //value of arg b 
*(sp + 14) = 134513817
*(sp + 15) = 10612724

为什么函数参数存储在SP的偏移量12 中?另请注意,偏移0到10的值始终相同,并且在每次调用函数foo()时,偏移量11处的值增加20。

更新:我发现gccin-build function来检索帧指针地址

void * __builtin_frame_address (unsigned int level)

当我从__builtin_frame_address(0)开始以偏移量打印值时,函数参数从offset 2开始。如何确认此行为始终一致?

3 个答案:

答案 0 :(得分:2)

你必须知道calling convention才能知道参数被压入堆栈的顺序,或者即使它们在堆栈中也是如此。许多人在寄存器中传递了前几个参数。即使在x86上,你也有fastcall,pascal,register,stdcall和cdecl,仅举几例。

编辑:不要忘记printf也是一个函数,局部变量也在堆栈中。因此,在您的示例应用程序中,您有参数(因为它是cdecl),然后是您的本地人,然后您的函数保存状态和返回地址,然后参数printf(可能,不确定它是cdecl或者fastcall),然后printf的当地人,直到任何实际进入屏幕的时间。

答案 1 :(得分:2)

没有简单的方法,当然也没有可移植的方式(对于相同的源文件,这甚至可以在gcc 4.1和gcc 4.2之间进行更改)但gdb确实可以做到。使用gcc,您可以找到分析DWARF信息所需的全部内容。

gdb还使用序言分析来检测如何在堆栈中分配局部变量(以及其他内容),但我不确定gdb源代码中是否存在类似“调用分析”的内容。可能正在阅读prologue-value.h在gdb的源代码中可以帮到你。

答案 2 :(得分:0)

局部变量在堆栈上分配,因此变量istackptrsp分配在call stack上。因此,如果我们打印所有堆栈记录,我们将找到这些变量,然后是返回指针,然后是保存的帧指针(如果已保存),然后是函数参数。因此,在上面的示例args从12开始。

如果要立即访问函数调用参数,则应从使用__builtin_frame_address(unsigned int level)获取的帧指针地址开始。在保存的帧指针之前将参数压入堆栈,因此如果从堆栈上保存的帧指针记录的开头开始,则必须添加等于帧指针地址大小的偏移量。因此,在上面的示例中,args从偏移量2开始。