2012-12-12 58 views
3

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

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

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

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

编辑:如果堆栈地址小长到更大的地址和参数传递正确的使用cdecl到左,我能获得ARGS值是这样的:

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

编辑:下面的程序使用以上拱形和规格打印功能参数值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 

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

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

void * __builtin_frame_address (unsigned int level) 

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

+3

它取决于底层架构及其调用约定... –

+5

您引用'a'和'b'?如果你想纯粹基于SP,那么你必须知道你的平台上编译器的内存布局。你没有指定哪个平台或编译器,所以没有人可以帮助你。 –

+1

你不可能一般地知道这一点。如果参数在寄存器中传递,它们可能永远不会登陆堆栈,如果它们确实存在,则不知道堆栈上的位置。 –

回答

2

您必须知道calling convention以了解将参数压入堆栈的顺序,或者即使它们位于堆栈上。许多人通过寄存器中的前几个参数。即使在x86上,您也可以使用fastcall,pascal,register,stdcall和cdecl等等。

编辑:不要忘记printf也是一个函数,并且局部变量也在堆栈中。因此,在你的示例应用程序中,你有你的参数(因为它是cdecl),然后你的本地人,然后你的功能保存状态和返回地址,然后参数printf(也许,不知道它是否cdeclfastcall),然后printf's当时的任何东西实际上到达屏幕。

+0

在C中,参数列表是从左到右传递的,反之亦然? –

+0

参数的顺序取决于编译器和平台。它可以是;它可能既不是(如果机器注册丰富),取决于规则。 –

+0

我使用gcc编译器,x86 arch和linux操作系统。参数传递顺序没有任何C99标准吗? –

2

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

gdb还使用序言分析来检测堆栈中的局部变量是如何分配的(除此之外),但我不确定gdb的源文件中是否存在类似“调用分析”的内容。可能正在读取gdb源文件中的prologue-value.h可以帮助你。

0

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

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