我在C中实现了一个回溯函数,它可以输出调用者的信息。像这样
ebp:0x00007b28 eip:0x00100869 args:0x00000000 0x00640000 0x00007b58 0x00100082
但是我怎么知道调用者的参数计数呢?
非常感谢
答案 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
中看到他们是如何做到的。请注意,这很难阅读。