【问题标题】:Given return address, how to get the address of the function?给定返回地址,如何获取函数的地址?
【发布时间】:2013-06-09 03:35:32
【问题描述】:

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

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

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

【问题讨论】:

  • 由于这个问题涉及到汇编代码,或许您可以告诉我们我们的目标是什么汇编语言?
  • 这将取决于处理器。没有通用的方法可以成功地做到这一点。
  • @StevenBurnap,你不是说依赖于架构吗?
  • @user2467539 使用&test_func 还不够吗?您提出的问题具有挑战性,因为单个函数中可能有多个返回指令。我想我只是好奇这是一个实际问题还是一个教育问题。
  • @TaylorFlores 问题是这样做时我不知道 foo 的名称,因此不能使用这样的符号。虽然不是作业问题,但我想这更像是一个教育问题,因为在实践中我们通常知道我们正在使用的所有功能。

标签: c assembly x86


【解决方案1】:

在 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 docs, __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) 的汇编等效项或其他奇怪的 self 混合物- 修改指令对 CPU 来说就像结构合理的编译器生成的代码一样“有效”。 x86 指令是可变大小,并且操作码映射密集 足以让几乎任何随机的字节序列构成一个“有效”指令流;因此,启发式反向反汇编二进制代码并不是 100% 安全的事情。

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

【讨论】:

    【解决方案2】:

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

    诶?这会给你bar的地址,而不是foo.

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

    【讨论】:

    • 这里的“返回地址”是指 foo 执行完成后返回的地址(在调用 foo 的函数内部)。
    • 您能否再解释一下“最高程序入口点”是什么,以及如何找到它?
    • (1) 我知道退货地址是什么,谢谢。 (2) 调用操作码的内容与包含它的过程的地址无关。 (3) 调用过程有一个小于返回地址的入口点。您只需找到其入口点最接近但小于返回地址的过程。
    【解决方案3】:

    假设存在常规页框并且 bar 是通过常规调用(而不是寄存器间接调用)来获取 bar 的地址的,那么您进一步“走出”一层并找到 @ 987654323@指令。

    在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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-03-29
      • 2013-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-24
      • 1970-01-01
      相关资源
      最近更新 更多