给定堆栈指针值,是否可以确定传递给函数的参数的值?参数在哪里存储在堆栈框架中。
让我们说,在Linux平台上gcc
架构上执行x86
已编译的ELF二进制文件:
int foo(int a, int b)
{
...
}
从foo(a,b)
调用 main()
,我知道现在指向foo()
的堆栈指针(SP)值。如何检索参数a
和b
?
编辑:如果堆栈从较小的地址增加到较大的地址,并且使用cdecl
从右到左传递参数,我是否可以获得这样的args值:
b = *(SP + 1);
a = *(SP + 2);
编辑:以下程序使用上面的拱门和规范打印函数args a
,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的值始终相同,并且在每次调用函数foo()
时,偏移量11处的值增加20。
更新:我发现gcc
有in-build function来检索帧指针地址
void * __builtin_frame_address (unsigned int level)
当我从__builtin_frame_address(0)
开始以偏移量打印值时,函数参数从offset 2
开始。如何确认此行为始终一致?
答案 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)
局部变量在堆栈上分配,因此变量i
,stackptr
和sp
分配在call stack上。因此,如果我们打印所有堆栈记录,我们将找到这些变量,然后是返回指针,然后是保存的帧指针(如果已保存),然后是函数参数。因此,在上面的示例args
从12开始。
如果要立即访问函数调用参数,则应从使用__builtin_frame_address(unsigned int level)
获取的帧指针地址开始。在保存的帧指针之前将参数压入堆栈,因此如果从堆栈上保存的帧指针记录的开头开始,则必须添加等于帧指针地址大小的偏移量。因此,在上面的示例中,args
从偏移量2开始。