【问题标题】:Recognizing stack frames in a stack using saved EBP values使用保存的 EBP 值识别堆栈中的堆栈帧
【发布时间】:2011-06-23 14:42:35
【问题描述】:

我想通过查看堆栈上的原始数据将堆栈划分为堆栈帧。我想通过找到保存的 EBP 指针的“链接列表”来做到这一点。

  1. 我是否可以假设(标准和常用的)C 编译器(例如 gcc)将始终在函数序言中的函数调用上更新和保存 EBP?

    推送 %ebp
    movl %esp, %ebp

    或者在某些情况下,某些编译器可能会为没有获取任何参数且没有局部变量的函数跳过该部分?

    x86 calling conventionsfunction prologue 上的 Wiki 文章对此没有多大帮助。

  2. 有没有更好的方法来仅通过查看原始数据来将堆栈划分为堆栈帧?

谢谢!

【问题讨论】:

  • gcc 有选项-fomit-frame-pointer;此外,懒惰的人使用调试器而不是沉思原始数据......
  • 我想以编程方式这样做,所以使用调试器不是我的意思。

标签: c compiler-construction assembly stack function-call


【解决方案1】:

某些版本的 gcc 有一个-fomit-frame-pointer 优化选项。如果有记忆,它甚至可以与参数/局部变量一起使用(它们直接从 ESP 索引而不是使用 EBP)。除非我弄错了,否则 MS VC++ 可以做的大致相同。

顺便说一句,我不确定一种接近普遍适用的方法。如果你有带有调试信息的代码,这通常很容易——否则……

【讨论】:

    【解决方案2】:

    即使优化了帧指针,堆栈帧通常也可以通过查看堆栈内存中保存的返回地址来区分。请记住,x86 中的函数调用序列始终包含:

        call someFunc             ; pushes return address (instr. following `call`)
        ...
    someFunc:
        push EBP                  ; if framepointer is used
        mov EBP, ESP              ; if framepointer is used
        push <nonvolatile regs>
        ...
    

    因此您的堆栈将始终 - 即使帧指针丢失 - 也有返回地址。

    您如何识别退货地址?

    • 首先,在 x86 上,指令有不同的长度。这意味着返回地址 - 与其他指针 (!) 不同 - 往往是 misaligned 值。据统计,其中有 3/4 个结尾不是四的倍数。
      任何未对齐的指针都是返回地址的良好候选者。
    • 那么,请记住 x86 上的 call 指令具有特定的操作码格式;在返回地址之前读取几个字节并检查是否在那里找到call 操作码(99% 的大多数情况下,直接调用返回五个字节,调用返回三个字节通过寄存器)。如果是这样,您已经找到了退货地址。
      这也是一种区分 C++ vtable 和返回地址的方式 - 你会在堆栈上找到 vtable 入口点,但从那些地址“回溯”你没有找到 call 指令。

    使用该方法,即使没有符号、帧大小调试信息或任何东西,您也可以从堆栈中取出调用序列的候选对象。

    但是,如何将这些候选者的实际调用序列拼凑在一起的细节并不那么简单,您需要一个反汇编程序和一些启发式方法来跟踪从找到的最低返回地址一直到最后一个已知程序的潜在调用流地点。也许有一天我会在博客上讨论它 ;-) 虽然在这一点上我宁愿说 stackoverflow 帖子的边距太小而无法包含它......

    【讨论】:

      猜你喜欢
      • 2011-01-16
      • 1970-01-01
      • 2012-05-01
      • 1970-01-01
      • 2014-01-07
      • 2016-01-12
      • 2012-11-28
      • 1970-01-01
      • 2014-10-26
      相关资源
      最近更新 更多