给定返回地址,如何获取函数的地址?

4
假设在一段C代码中,我有一个名为foo的函数调用了bar。而在bar内部,我可以使用汇编语言获取将要返回到的地址。如何使用此信息确定foo的地址?
一种方法是获取foo 将要返回的返回地址,并从调用foo的call 指令的操作码中获取地址。但是,这需要知道使用哪种调用方法(例如偏移量/绝对值),因此不可靠。有没有更简单的方法来确定调用者的地址?
编辑:我忘记提到这个问题是关于32位Intel unix机器上的IA32汇编语言的。

由于这个问题涉及汇编代码,也许您可以告诉我们我们要针对哪种汇编语言? - John Zwinck
这将取决于处理器。没有通用的方法可以成功地完成这个任务。 - Gort the Robot
@StevenBurnap,你是不是指架构相关的? - tay10r
@user2467539,使用&test_func不足够吗?你提出的问题很具有挑战性,因为在单个函数内可能会有多个返回指令。我猜想这只是一个实际问题还是教育性问题而已。 - tay10r
我同意这是一个更好的方法。然而,让我感到不安的是,这依赖于函数以push ebp开头,并且在函数体中不包含任何push ebp。我对编译细节不是很熟悉,但这个假设总是成立吗? - user2467539
显示剩余4条评论
3个回答

4
在Linux中,您可以使用dladdr()来解析调用函数,方法如下:
#define _GNU_SOURCE
#include <dlfcn.h>

...

void *retAddr = __builtin_extract_return_addr(__builtin_return_address(0));
Dl_info d;
(void)dladdr(retAddr, &d);
printf("%s called from %s + 0x%p\n",
    __FUNC__,
    d.dli_sname,
    (retAddr - d.dli_saddr));

请参阅GCC文档,__builtin_return_address()和Linux manpage dladdr(3)了解详细信息。
函数dladdr()在Solaris/MacOSX/*BSD上也可用,但需要其他预处理器定义才能变得可见;请参阅相应操作系统的手册...
注意,由于这依赖于符号表的存在,因此可能无法成功解析已剥离的二进制文件。我没有尝试将错误处理添加到上述内容中;通常,任何类型的自动回溯(具有函数名称解析)支持都不喜欢符号表被剥离。
对于真正快速的方法,有时我只是简单地使用:
#include <execinfo.h>

...

void *retAddr[10];
backtrace_symbols_fd(retAddr, backtrace(retaddr, 10), STDERR_FILENO);

如果有十个入口的深度堆栈,就会出现这种情况。同样,这取决于没有剥离符号表。这会带来性能上的惩罚,因为您需要解析多个地址。

编辑2: 没有符号表(其中包含可执行文件/库中函数的起始地址大小等信息),什么是“起始地址”这一信息变得毫无意义;就CPU本身而言,在特定时刻指令指针到达某个位置的方式并没有真正记录——汇编语言相当于gotojmp)或者其他自修改指令的奇怪组合与结构良好、由编译器生成的代码一样“有效”。x86指令是可变长的,且操作码映射足够密集,所以几乎任何随机字节序列都可以构成“有效”的指令流;因此,二进制代码的启发式反向反汇编并不是100%安全的。

符号表在这个意义上也为调试器建立了“标记”。如果您从符号表中记录的函数起始地址开始反汇编,可以期望找到有效的指令流,并且可以通过验证回溯中找到的任何返回地址是否实际上是由call指令先行的来进行交叉验证。

2

一种方法是获得函数foo将返回的地址,并从调用foo的call指令的操作码中获取地址。

啊?这样会给你bar的地址,而不是foo的地址。

你只需要获取低于返回地址的最高过程入口点即可。


这里的“返回地址”是指 foo 在执行完毕后将返回到的地址(即调用 foo 的函数内部)。 - user2467539
你能否解释一下什么是“最高过程入口点”,以及我们如何找到它? - user2467539
(1) 谢谢,我知道返回地址是什么。 (2) 调用操作码的内容与包含它的过程的地址无关。 (3) 调用过程有一个入口点,该点小于返回地址。你只需要找到入口点最接近但小于返回地址的过程即可。 - user207421

2
假设常规页面帧存在,并且bar是通过普通调用(而不是寄存器间接调用)来获取bar地址的,您需要“向外”进一步查找call bar指令才能找到它。
当在foo中时,您的堆栈将类似于:
.
.
parameters to bar (if any)
return address, i.e. address following 'call bar'
saved base page (ebp register) value
locals to bar
...
parameters to foo (if any)
return address, i.e. address following 'call foo' within bar
saved base page (ebp register) value
locals to foo

因此,要从foo中获取bar的地址,您可以执行以下操作(这是我脑海中的想法,因此可能需要进行微调,但您应该能够理解一般思路)。

mov eax, [ebp]   // load calling scope (bar's) frame pointer
mov eax, [eax+4] // load the return address for bar
mov edx, [eax-4] // load offset from the call instruction that called bar
lea eax, eax+edx // adjust (or something similar) to convert from offset to abs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接