在C中实现回溯时如何知道参数的数量

时间:2012-03-04 11:07:30

标签: c assembly

我在C中实现了一个回溯函数,它可以输出调用者的信息。像这样

ebp:0x00007b28 eip:0x00100869 args:0x00000000 0x00640000 0x00007b58 0x00100082

但是我怎么知道调用者的参数计数呢?

非常感谢

4 个答案:

答案 0 :(得分:2)

在某些情况下,您可以推断函数在32位x86代码中使用的数量。
如果代码已编译为使用 framepointers ,则给定函数的堆栈帧在(最高地址)EBP和(最低地址/堆栈顶部)ESP之间延伸。如果您的代码使用C调用约定,则立即在<{1}}上的堆栈结尾处找到返回地址,并再次高于该值。 EBP),连续cdecl

这意味着:arg[0...]位于arg[0][EBP + 4]位于arg[1],依此类推。

当您反汇编函数时,查找引用[EBP + 8 ]的指令,并且您知道它们访问函数参数。使用的最高偏移值告诉您有多少。

这当然有点简化;尺寸不同于32位的参数,不使用[EBP + ...]的代码,例如cdecl,优化了帧指针的代码使方法至少部分地跳闸。

同样适用于fastcall函数的另一个选项是查看返回地址(cdecl在您感兴趣的函数中的位置),并在那里进行反汇编;在许多情况下,您将找到序列call,并且您可以推断出在此实例中传递了多少个参数。这实际上是唯一的方法(仅从代码中)来测试在特定实例中将多少个参数传递给像push argN; push ...; push arg0; call yourFunc这样的函数。 同样,不完美 - 这些天,编译器经常预先分配堆栈空间,然后使用printf()来编写参数而不是推送它们(在某些CPU上,这更好,因为mov指令的序列彼此之间存在依赖关系每个修改堆栈指针。)

由于所有这些方法都是启发式的,因此需要进行相当多的编码才能实现自动化。如果编译器生成的调试信息可用,请使用它 - 它更快。

编辑:还有另一个有用的启发式方法可以完成;编译器生成的函数调用代码通常如下所示:

push

... [ code that either does "push arg" or "mov [ESP ...], arg" ] ... call function add ESP, ... 指令用于清理用于参数的堆栈空间。根据直接操作数的大小,您知道此代码给add使用的args空间有多少,并且暗示(例如,假设它们都是32位),您知道有多少空间。<登记/> 如果您已经拥有<{1}}指令的地址,如果您有工作的回溯代码 - 返回地址的指令是{{1 }}。所以你通常可以简单地试图在返回地址处反汇编(单个)指令,看看它是function(有时它是add),如果是,则计算参数个数从直接操作数传递。这个代码要比完全反汇编库更简单。

答案 1 :(得分:1)

你做不到。参数的数量不会保存在任何地方,您可以在这个简单的反汇编中看到:

    f(5);
002B144E  push        5  
002B1450  call        f (2B11CCh)  
002B1455  add         esp,4  
    g(1, "foo");
002B1458  push        offset string "foo" (2B5740h)  
002B145D  push        1  
002B145F  call        g (2B11C7h)  
002B1464  add         esp,8  
    h("bar", 'd', 8);
002B1467  push        8  
002B1469  push        64h  
002B146B  push        offset string "bar" (2B573Ch)  
002B1470  call        h (2B11D1h)  
002B1475  add         esp,0Ch  

基本上,只有被调用的函数知道它有多少个参数。

答案 2 :(得分:0)

正如Yahia评论的那样,没有一般方法。

您可能需要解析调试器放置的调试信息(假设您已使用gcc -g编译)。

答案 3 :(得分:0)

Glibc实现了backtrace功能。它展开回溯,参见arg。

您可以在sysdeps/$ARCH/backtrace.c中看到他们是如何做到的。请注意,这很难阅读。