Assembly 以下代码中EBP的用途是什么?

Assembly 以下代码中EBP的用途是什么?,assembly,Assembly,我有两个关于EBP注册的问题 我了解ESP和EIP。然而,我真的不明白为什么会使用EBP 在下面的代码中,我将EBP寄存器(实际上是0000000)推送到堆栈中。然后,我将堆栈的内存地址移动到EBP,以便ESP和EBP具有相同的数据。这是序言。有一些代码以syscall结束。然后我做相反的操作(epilog),因为“leave”表示我将EBP移动到ESP(由于prolog,这些值是相同的),然后将堆栈的最后一个值(EBP,00000000)弹出到EBP。这将为EBP提供与序言之前相同的值 为什么

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

我了解ESP和EIP。然而,我真的不明白为什么会使用EBP

在下面的代码中,我将EBP寄存器(实际上是0000000)推送到堆栈中。然后,我将堆栈的内存地址移动到EBP,以便ESP和EBP具有相同的数据。这是序言。有一些代码以syscall结束。然后我做相反的操作(epilog),因为“leave”表示我将EBP移动到ESP(由于prolog,这些值是相同的),然后将堆栈的最后一个值(EBP,00000000)弹出到EBP。这将为EBP提供与序言之前相同的值

为什么会有人这样做?重点是什么?请用简单的方式回答!请记住,我没有掌握EBP(帧指针)的实际功能

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

此外,在AIUI中,我可以使用“回车”替换“推ebp/mov ebp,esp”。然而,当我尝试在nasm中编译时,我得到了“错误:操作码和操作数的组合无效”“离开”“工作正常;”“输入”不适用。正确的语法是什么

谢谢

Example:

    push ebp
    mov, ebp, esp 

    [some code here]
    int 0x80

leave
ret   

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

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

import core.stdc.stdio;
void main() {
    int a = 8;
    a += 8;
    printf("%d\n", 8);
}
(这是D代码,但实际上并不相关)

这是函数prolog,用于设置ebp。
子esp,0x4
将堆栈推开4个字节-这为本地
int a
变量腾出了空间,该变量的长度为4个字节

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

   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
请注意,
addesp,0x8
这里是printf调用的一部分:调用方负责在调用函数后清除参数。这是必需的,因为只有调用者知道它实际发送了多少个参数——这就是启用printf变量参数的原因

总之,最后,我们清理局部变量并从函数返回:

  22:       c9                      leave
  23:       c3                      ret
编辑:
leave
btw扩展到
mov-esp,ebp;pop-ebp-这与设置指令完全相反,正如Aki Suikkonen在另一个答案中所说,这里的一件好事是堆栈恢复到函数入口的状态,不管函数内部发生了什么(除非函数完全破坏了堆栈的内容,否则程序很可能很快就会崩溃)。/edit

因此,归根结底,ebp都是关于局部变量的。它使用esp启动,因此它有一个很好的内存空间,不会占用其他函数(它在调用堆栈上),而是将其移动到ebp,以便在整个函数中,局部变量保持一致的偏移量-变量
a
始终是[ebp-4]在这个函数中,即使在堆栈被操纵的时候


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

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


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

EBP的思想实际上是形成一个固定的参考点。通常,您可能会摆弄堆栈指针(例如,在将参数推到堆栈上准备调用时)找出某个数据段相对于堆栈指针的位置是一件非常痛苦的事情。但是相对于基指针,它总是一样的。现代编译器很容易解决这个问题,但是如果你想(手工)编写一大块汇编代码使用堆栈进行推送和弹出时,您会发现相对于不改变的寄存器(EBP)引用本地变量更容易。

调用您缺少的内容。
enter
的语法是
enter 0,0
。第一个参数是为本地变量保留的字节数(子esp,?)。第二个参数是“lex level”-您不想知道,只需将其设为零即可。可能重复的Never use
enter
  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