【问题标题】:What is the purpose of EBP in the following code?以下代码中 EBP 的目的是什么?
【发布时间】:2014-03-19 19:35:50
【问题描述】:

我有两个关于 EBP 注册的问题。

我了解 ESP 和 EIP。但是,我真的不明白为什么要使用 EBP。

在下面的代码中,我将 EBP 寄存器(实际上是 0000000)压入堆栈。然后我将堆栈的内存地址移动到 EBP,以便 ESP 和 EBP 具有相同的数据。这是序言。有一些代码以系统调用结束。然后我做相反的事情(结语),因为“离开”表示我将 EBP 移动到 ESP(由于序言,这些值是相同的)然后将堆栈的最后一个值(即 EBP,即 00000000)弹出到 EBP。这使 EBP 的值与 prolog 之前发生的值相同。

为什么会有人这样做?重点是什么?请用简单的方式回答!请记住,我不了解 EBP(帧指针)的实际作用。

编辑:或者这是一种在函数中有效备份堆栈(ESP)的方法?换句话说:程序可以做它对堆栈所做的事情,并且“原始堆栈”将始终存在于 EBP 中。然后当程序结束时,EBP 会恢复到之前的状态。它是否正确?如果是这样,结语只是一个整理程序?

另外,AIUI,我可以使用 'enter' 来替换 'push ebp / mov ebp, esp'。然而,当我尝试在 nasm 中编译时,我得到“错误:操作码和操作数的无效组合”“离开”工作正常; “进入”没有。正确的语法是什么?

谢谢!

Example:

    push ebp
    mov, ebp, esp 

    [some code here]
    int 0x80

leave
ret   

【问题讨论】:

  • 你缺少的叫做stack frame
  • enter 的语法是 enter 0, 0。第一个参数是为局部变量(sub esp,???)保留的字节数。第二个参数是“lex level”——你不想知道,就让它为零。
  • 永远不要使用enter。与push/mov 相比,它非常慢,并且如果您还可以替换sub esp, imm 以保留一些堆栈空间,则只能节省一点代码字节。它比 push / mov 更大(32b)或相同大小(64b)。 leave 没有潜在的疯狂 CISC 语义,所以它并不慢,值得在一些微架构上使用。

标签: assembly


【解决方案1】:

EBP 形成一个对栈中变量的固定引用点:主要是函数的所有参数,函数的所有局部参数,最后是返回地址。有了这个固定点,函数可以几乎随机地增长/改变它的堆栈,从任何地方跳转到函数结尾并将堆栈指针恢复到原始位置。

这个概念是强制性的,因为原始 8086 代码不允许堆栈指针与 mov ax, [sp + 10] 中的位移一起使用,而只能与 pushpop 一起使用。引用除顶部元素之外的其他任何内容都需要使用mov xx, [bp + 10]

【讨论】:

    【解决方案2】:

    EBP的思想确实是形成一个固定的参考点。通常,您可能会摆弄堆栈指针(例如,在将参数压入堆栈以准备调用时)并发现找出某些数据相对于堆栈指针的位置真的很痛苦。但相对于基指针,它总是相同的。现代编译器很容易解决这个问题,但是如果您想(手动)编写一大段汇编代码,使用堆栈进行推送和弹出,您会发现相对于寄存器(EBP)引用局部变量更容易) 不会改变。

    【讨论】:

    • 另外值得一提的是:推送 EBP 的先前值会创建一个堆栈帧的链表,无需任何额外的元数据即可轻松展开堆栈。可以保存此信息以使异常处理(和调试)成为可能的现代目标文件格式使 -fomit-frame-pointer 成为默认值。
    【解决方案3】:

    enter 还需要一个数字,它是要分配的空间量,这是您问题的关键:这些指令用于为函数的局部变量设置空间。

    通过 EBP 寄存器引用局部变量。让我给你举个例子:

    import core.stdc.stdio;
    void main() {
        int a = 8;
        a += 8;
        printf("%d\n", 8);
    }
    

    (这是 D 代码,但并不真正相关)

    Disassembly of section .text._Dmain:
    
    00000000 <_Dmain>:
       0:       55                      push   ebp
       1:       8b ec                   mov    ebp,esp
       3:       83 ec 04                sub    esp,0x4
       6:       b8 08 00 00 00          mov    eax,0x8
       b:       89 45 fc                mov    DWORD PTR [ebp-0x4],eax
       e:       01 45 fc                add    DWORD PTR [ebp-0x4],eax
      11:       50                      push   eax
      12:       b9 00 00 00 00          mov    ecx,"%d\n"
      17:       51                      push   ecx
      18:       e8 fc ff ff ff          call   printf
      1d:       31 c0                   xor    eax,eax
      1f:       83 c4 08                add    esp,0x8
      22:       c9                      leave
      23:       c3                      ret
    

    让我们把它分解成每个部分:

       0:       55                      push   ebp
       1:       8b ec                   mov    ebp,esp
       3:       83 ec 04                sub    esp,0x4
    

    这是函数prolog,设置ebp。 sub esp, 0x4 将堆栈推开 4 个字节 - 这为我们的本地 int a 变量腾出了空间,它有 4 个字节长。

    enter 指令很少使用,但我相信enter 4,0 会做同样的事情——输入一个具有 4 字节局部变量空间的函数。编辑:另一个 0 是嵌套级别,我从未见过它使用过...输入通常比编译器在此处执行的步骤要慢。 /编辑

       6:       b8 08 00 00 00          mov    eax,0x8
       b:       89 45 fc                mov    DWORD PTR [ebp-0x4],eax
    

    这是a=8 行 - 第二行将值存储在局部变量的内存中。

       e:       01 45 fc                add    DWORD PTR [ebp-0x4],eax
    

    然后,我们在 a+=8 中添加它(编译器在这里重用了 eax,因为它识别出 号码是一样的……)

    之后,它通过将参数压入堆栈来调用 printf,然后将 eax 寄存器 (xor eax, eax) 清零,这就是 D 从函数返回 0 的方式。

      11:       50                      push   eax
      12:       b9 00 00 00 00          mov    ecx,"%d\n"
      17:       51                      push   ecx
      18:       e8 fc ff ff ff          call   printf
      1d:       31 c0                   xor    eax,eax
      1f:       83 c4 08                add    esp,0x8
    

    注意这里的add esp, 0x8是调用printf的一部分:调用者负责在调用函数后清理参数。这是必需的,因为只有调用者知道它实际发送了多少个参数 - 这就是启用 printf 的可变参数的原因。

    无论如何,最后,我们清理我们的局部变量并从函数中返回:

      22:       c9                      leave
      23:       c3                      ret
    

    编辑:leave btw 扩展为mov esp, ebp; pop ebp; - 它与设置说明完全相反,就像 Aki Suihkonen 在另一个答案中所说的那样,这里的一件好事是堆栈恢复到原来的功能入口,无论函数内部发生了什么(好吧,除非函数完全破坏了堆栈的内容,在这种情况下,您的程序很可能很快就会崩溃)。 /编辑

    所以,归根结底,ebp 的东西都是关于你的局部变量的。它使用 esp 开始,所以它有一个很好的内存空间,不会踩到其他函数(它在调用堆栈上),但是将它移动到 ebp 以便你的本地人在整个函数中保持一致的偏移量 - 变量 @ 987654339@ 在此函数中始终为 [EBP-4],即使堆栈已被操作。

    通过反汇编你用 C 或其他东西编写的函数,最容易看到实际操作,就像我们在这里所做的那样。 linux 命令objdump -d -M intel somefile.o 是我使用的(然后我手动修复了一些小问题以使其更具可读性。如果您反汇编一个 .o 文件,并非所有库调用都已解决,因此它看起来有点奇怪,但事实并非如此t 影响局部变量的东西!)

    【讨论】:

      猜你喜欢
      • 2021-02-18
      • 2018-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-07
      • 1970-01-01
      • 2016-04-16
      • 2016-03-06
      相关资源
      最近更新 更多