在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本身而言,在特定时刻指令指针到达某个位置的方式并没有真正记录——汇编语言相当于goto
(jmp
)或者其他自修改指令的奇怪组合与结构良好、由编译器生成的代码一样“有效”。x86指令是可变长的,且操作码映射足够密集,所以几乎任何随机字节序列都可以构成“有效”的指令流;因此,二进制代码的启发式反向反汇编并不是100%安全的。
符号表在这个意义上也为调试器建立了“标记”。如果您从符号表中记录的函数起始地址开始反汇编,可以期望找到有效的指令流,并且可以通过验证回溯中找到的任何返回地址是否实际上是由
call
指令先行的来进行交叉验证。
&test_func
不足够吗?你提出的问题很具有挑战性,因为在单个函数内可能会有多个返回指令。我猜想这只是一个实际问题还是教育性问题而已。 - tay10rpush ebp
开头,并且在函数体中不包含任何push ebp
。我对编译细节不是很熟悉,但这个假设总是成立吗? - user2467539