4

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

比方说,在 Linux 平台的架构上执行gcc编译后的 ELF 二进制文件:x86

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

foo(a,b)从中调用main(),我知道现在指向的堆栈指针(SP)值foo()。如何检索参数a和的值b

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

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

编辑:以下程序使用上述拱形和规范打印函数 argsa的值。b

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 处的值始终相同,每次调用 function 时,偏移量 11 处的值增加 20 foo()

更新:我发现gcc内置函数来检索帧指针地址

void * __builtin_frame_address (unsigned int level)

__builtin_frame_address(0)当我在从函数参数开始的偏移量处打印值时,从offset 2. 我如何确认这种行为始终是一致的?

4

3 回答 3

2

您必须知道调用约定才能知道参数被压入堆栈的顺序,或者即使它们在堆栈上。许多人在寄存器中传递前几个参数。即使在 x86 上,您也有 fastcall、pascal、register、stdcall 和 cdecl,仅举几例。

编辑:不要忘记这printf也是一个函数,并且局部变量也在堆栈上。所以,在你的示例应用程序中,你有你的参数(因为它是 cdecl),然后是你的本地人,然后是你的函数保存状态和返回地址,然后是参数printf(也许,不确定它是cdecl还是fastcall),然后printf是本地人任何东西实际上都会出现在屏幕上。

于 2012-12-12T06:58:12.303 回答
2

没有简单的方法,当然也没有可移植的方法(对于同一个源文件,这甚至可以在 gcc 4.1 和 gcc 4.2 之间改变),但 gdb 肯定可以做到。使用 gcc,您可能可以找到分析 DWARF 信息所需的一切。

gdb 还使用序言分析来检测局部变量在堆栈中的分配方式(除其他外),但我不确定 gdb 的源中是否存在类似“调用分析”的东西。可能正在阅读 gdb 源代码中的 prologue-value.h 可以帮助您。

于 2012-12-12T09:59:05.963 回答
0

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

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

于 2012-12-12T14:13:07.840 回答