【问题标题】:Retrieve the content of EBP register in linux在linux中检索EBP寄存器的内容
【发布时间】:2013-06-24 17:00:21
【问题描述】:

我需要 EBP/RBP 的内容来检索函数的返回地址。 此地址应位于堆栈帧内的位置 8(%RBP)(我们仅考虑 x86_64 位架构)。

我从 ucontex_t 结构中检索这个值,该结构被传递给信号处理程序,但程序有一个非常奇怪的行为。有时 RBP 寄存器中包含的值根本没有意义(例如,0x00、0x01),有时它包含正确的堆栈基值。当然,这种行为会导致多个应用程序崩溃。

我需要检索函数的返回地址,因为我想找出调用函数的地址。

这是我正在使用的代码:

  syscall = ctx->uc_mcontext.gregs[REG_SYSCALL];
  pc=ctx->uc_mcontext.gregs[REG_PC];
  stack=ctx->uc_mcontext.gregs[REG_STACK];
  stack_base=ctx->uc_mcontext.gregs[REG_BASE];

  function_address=get_function_address((char *)stack_base); 

  DPRINT(DEBUG_INFO, "Received SYS_SECCOMP signal : syscall %lu\n", syscall); 
  DPRINT(DEBUG_ALL, "Syscall instruction address %p\n", info->si_call_addr);
  DPRINT(DEBUG_ALL, "PC  0x%lx, BASE_STACK  0x%lx, Stack 0x%lx\n", pc, stack_base, stack);
  DPRINT(DEBUG_ALL, "Syscall number %d\n", info->si_syscall);
  DPRINT(DEBUG_ALL, "Syscall arch   %u\n", info->si_arch);

宏定义如下:

#define REG_SYSCALL REG_RAX
#define REG_PC    REG_RIP
#define REG_BASE  REG_RBP
#define REG_STACK REG_RSP

前面代码的输出示例是: 正确值:

[DEBUG_INFO] Received SYS_SECCOMP signal : syscall 3
Syscall instruction address 0x7fa058d67452
PC  0x7fa058d67452, BASE_STACK  0x7fff145c1d00, Stack 0x7fff145c1bc0
Syscall number 3
Syscall arch   3221225534

错误的值:

[DEBUG_INFO] Received SYS_SECCOMP signal : syscall 78
Syscall instruction address 0x7fa058df2495
PC  0x7fa058df2495, BASE_STACK  0xffffffffffffffa8, Stack 0x7fff145c1ed0
Syscall number 78
Syscall arch   3221225534

更糟糕的是:

[DEBUG_INFO] Received SYS_SECCOMP signal : syscall 3
Syscall instruction address 0x7fa058e18360
PC  0x7fa058e18360, BASE_STACK  0x0, Stack 0x7fff145c1e68
Syscall number 3
Syscall arch   3221225534

我注意到,当 RBP 包含零时,它会保持相同的值,直到应用程序结束。

【问题讨论】:

  • 即使你有RBP,你仍然需要再次弹出来检索返回地址...?!
  • 是的,但问题是,如果您从地址 0x0、0x1 读取,应用程序当然会崩溃。
  • 那么...为什么不做一些简单的内联汇编来获取寄存器的值呢?
  • 因为我需要获取已经被信号中断的进程的RBP值,所以从ucontex_t结构中获取。如果我使用你所说的,我将检索当前的 RBP(即在信号处理程序中使用的)

标签: c linux cpu-registers ucontext


【解决方案1】:

无法保证所有函数都会使用标准堆栈帧,其中 ebp 被推送,然后在函数开始时设置为 esp。函数使用ebp作为通用寄存器,然后通过esp寄存器引用函数参数和局部变量的情况并不少见。

这对于代码生成器来说显然更复杂,因为 esp 的值会随着时间而变化(例如,在函数调用中推送变量时),但这种方式当然可以生成代码。

您最多可以尝试通过向上扫描堆栈来猜测返回地址,寻找潜在的返回地址(例如,通过检查该地址的 VMA 是否设置了 VM_EXEC 标志)。然后在找到一个潜在地址后,您需要从该地址向后扫描,寻找看起来是函数调用的代码(一个例子是 E8 五个字节后退)。

您可以走得更远,通过检查函数调用(假设它不是间接调用)指向您当前 IP 附近某处的地址,尽管弄清楚什么是“近”的安全定义并不是一个容易的决定要么。

底线是它会非常复杂,而且仍然不能保证你会找到正确的地址。

【讨论】:

  • 我同意,感谢您的回答。那你觉得有可能在运行时获取调用者函数的地址吗?
  • @GiuseppePes 我在回答中添加了更多内容。
【解决方案2】:

如果使用最新的 GCC 编译器,您可能会对 GCC builtins 感兴趣,例如 __builtin_return_address__builtin_extract_return_addr__builtin_frame_address

您可能对 GCC libbacktrace(可在 GCC 之外使用)和 Glibc backtrace 函数感兴趣。

【讨论】:

  • 感谢您的回答。您可以使用这些函数获得的信息与程序的当前执行有关。它们对于日志记录和调试目的非常有用,因为它们允许我检索有关当前执行堆栈的信息。虽然我的情况不同,因为我必须从信号处理例程中检索返回地址,该例程的堆栈与正常应用程序执行中使用的堆栈不同。
  • 看看 GCC 4.8 或未来的 4.9 是如何处理信号的;它确实将libbactrace 用于该确切目的(类似于您的情况)。
  • 谢谢,您能否说得更准确一些,因为我在 Google 上进行了研究,但没有发现任何相关信息。谢谢
猜你喜欢
  • 2012-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-01
  • 2016-06-07
  • 2016-03-06
  • 2018-10-05
相关资源
最近更新 更多