4

假设在一段 C 代码中,我有一个调用bar的函数foo。在bar内部时,我可以使用程序集来获取bar将返回的地址。如何使用这些信息来确定foo的地址?

一种方法是获取foo将返回的返回地址,并从调用foo的调用指令的操作码中获取地址。但是,这需要知道使用哪种调用方法(例如偏移/绝对),因此不可靠。有没有更简单的方法来确定来电者的地址?

编辑:我忘了提到这个问题是关于 32 位 Intel unix 机器上的 IA32 程序集。

4

3 回答 3

3

在 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 联机帮助页dladdr(3)

该功能dladdr()在 Solaris/MacOSX/*BSD 上也可用,但需要其他预处理器定义而不是_GNU_SOURCE可见;请参阅相应操作系统的手册页...

编辑:请注意,由于这依赖于符号表的存在,它可能无法在剥离的二进制文件上成功解析。我没有尝试在上面添加错误处理;通常,任何类型的自动回溯(具有函数名称解析)支持都不喜欢剥离符号表。

对于一个非常快速的,我有时只是使用:

#include <execinfo.h>

...

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

因为这会得到一个十项深度的堆栈跟踪。同样,依赖于不剥离符号表。由于您要解析多个地址,因此会降低性能。

Edit2: 没有符号表(其中包含可执行文件/库中函数的起始地址大小),什么是“起始地址”的信息是毫无意义的;就 CPU 本身而言,实际上并没有记录指令指针如何在特定时刻到达它所在的位置 - goto( jmp) 的汇编等效项或其他自修改指令的奇怪混合物就像对于 CPU 来说“有效”,因为它是结构正确的编译器生成的代码。x86指令大小可变,操作码映射密集足以让几乎任何随机的字节序列构成一个“有效”的指令流;因此,启发式反向反汇编二进制代码并不是 100% 安全的事情。

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

于 2013-06-10T15:07:20.273 回答
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

bar因此,要从 inside获取地址foo,您将执行以下操作(这不是我的想法,因此可能需要进行细微调整,但您应该了解总体思路)。

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
于 2013-06-09T07:18:13.823 回答
2

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

诶?这将为您提供bar的地址,而不是foo。

您所需要的只是低于返回地址的最高过程入口点。

于 2013-06-09T03:53:10.687 回答