我发现ESP寄存器是当前堆栈指针,EBP是当前堆栈帧的基指针。但是,我不理解这些定义(我刚刚开始学习如何用汇编语言编写代码)。我所理解的是ESP指向堆栈本身,EBP指向堆栈顶部的任何东西1。但这些只是我的猜测,它们很可能是不正确的。否则,像下面这样的语句是什么意思?
MOV EBP, ESP
脚注1:编者注:是的,这是不正确的。在标准术语中,“栈顶”是ESP指向的地方,尽管它是栈帧中的最低地址。通过类推,栈数据结构向上增长,尽管x86上的调用栈(像大多数ISA一样)向下增长。
monwx1rj1#
esp是堆栈指针,ebp是/曾经是堆栈帧,因此当你进入函数时,ebp可以获得esp在该点的拷贝、在该点发生之前堆栈上的所有内容、返回地址、传入参数等等以及该函数的全局内容(局部变量)现在将在函数的持续时间内与堆栈帧指针保持静态距离。ESP现在可以按照编译器的要求自由地漫游,并且可以在嵌套到其他函数时使用(每个函数都需要自然地保留EBP)。这是管理堆栈的一种懒惰方式。使编译器调试更容易,使编译器生成的代码更容易理解,但会烧掉一个本来可能是通用的寄存器。
vql8enpb2#
EBP通常用于备份ESP,因此如果ESP被函数中的代码更改,则只需mov esp, ebp即可恢复ESP。由于EBP通常不会被函数中的代码更改,因此它可以用于访问传递的参数或局部变量,而无需调整偏移量。对于“堆栈帧”用法,EBP在任何函数的开始处被压入堆栈,因此压入堆栈的EBP的值是来自调用当前函数的函数的EBP的值。这使得代码或调试器可以“反向跟踪”EBP被压入堆栈的所有示例,并且栈上的EBP值的每个示例可以被认为是栈帧的基指针。注意有些编译器有一个“省略帧指针”选项,在这种情况下,EBP不用于保存ESP或作为堆栈帧指针,允许EBP用于变量。编译器会跟踪ESP,所有的局部偏移量都是ESP当前值的偏移量。
mov esp, ebp
cunj1qz13#
EBP和ESP是那个时代的残余,在那个时代,编译器没有静态分析来检测函数调用中需要多少字节的堆栈。而且,堆栈应该在函数执行期间动态增长和收缩,中断将允许丢弃从0到SP的所有堆栈。实际上中断(以及单独通过寄存器传递参数)是调用内核函数的设计方法。在这种情况下,我们 * 需要 * 有一个栈的固定点,在那里,调用者的返回地址,局部变量和函数的参数总是可以找到的。因此,bp寄存器是合理的。在这种架构中,bp被允许索引([bp -300 h]),但sp不是。那些可能被解释为mov ax, [sp + 1111h]的操作码/指令编码被重用于其他用途。在386+中,通过“E”的引入,ESP获得了偏移的属性。此时EBP从唯一的目的中解放出来,因为esp能够处理两个任务。注意,即使现在EBP也通过stack segment(SS)指向内存,就像ESP一样。其他寻址模式(没有ESP/EBP作为基址)默认为DS段。(16位模式中的绝对、DI、SI和/或BX,以及32位寻址模式中的任何寄存器都可以是寻址模式中的基址)。
bp
sp
mov ax, [sp + 1111h]
EBP
esp
ESP
bttbmeg04#
ESP寄存器是系统堆栈的堆栈指针。它很少被程序直接更改,但在将数据推入堆栈或从堆栈弹出时更改。堆栈的一个用途是在过程调用中。过程调用指令后的指令地址存储在堆栈上。EBP寄存器指向基址。通常堆栈中唯一被访问的数据项是位于堆栈顶部的数据项。虽然EBP寄存器经常被用来标记堆栈中的固定点而不是堆栈顶部,例如,参数是这样的数据。它们是从堆栈EBP顶部的返回地址后的基指针的偏移量。因此,你会看到类似于EBP+0x8,EBP+0xC,这分别是参数1和2。
在汇编语言编程中,理解堆栈是非常重要的,因为它会影响到你将要使用的调用约定,而不管它是什么类型。例如,即使是cdecl或__stdcall也依赖于ESP和EBP寄存器,而其他的也在某种程度上依赖于某些寄存器和堆栈。
4条答案
按热度按时间monwx1rj1#
esp是堆栈指针,ebp是/曾经是堆栈帧,因此当你进入函数时,ebp可以获得esp在该点的拷贝、在该点发生之前堆栈上的所有内容、返回地址、传入参数等等以及该函数的全局内容(局部变量)现在将在函数的持续时间内与堆栈帧指针保持静态距离。ESP现在可以按照编译器的要求自由地漫游,并且可以在嵌套到其他函数时使用(每个函数都需要自然地保留EBP)。
这是管理堆栈的一种懒惰方式。使编译器调试更容易,使编译器生成的代码更容易理解,但会烧掉一个本来可能是通用的寄存器。
vql8enpb2#
EBP通常用于备份ESP,因此如果ESP被函数中的代码更改,则只需
mov esp, ebp
即可恢复ESP。由于EBP通常不会被函数中的代码更改,因此它可以用于访问传递的参数或局部变量,而无需调整偏移量。对于“堆栈帧”用法,EBP在任何函数的开始处被压入堆栈,因此压入堆栈的EBP的值是来自调用当前函数的函数的EBP的值。这使得代码或调试器可以“反向跟踪”EBP被压入堆栈的所有示例,并且栈上的EBP值的每个示例可以被认为是栈帧的基指针。
注意有些编译器有一个“省略帧指针”选项,在这种情况下,EBP不用于保存ESP或作为堆栈帧指针,允许EBP用于变量。编译器会跟踪ESP,所有的局部偏移量都是ESP当前值的偏移量。
cunj1qz13#
EBP和ESP是那个时代的残余,在那个时代,编译器没有静态分析来检测函数调用中需要多少字节的堆栈。而且,堆栈应该在函数执行期间动态增长和收缩,中断将允许丢弃从0到SP的所有堆栈。实际上中断(以及单独通过寄存器传递参数)是调用内核函数的设计方法。
在这种情况下,我们 * 需要 * 有一个栈的固定点,在那里,调用者的返回地址,局部变量和函数的参数总是可以找到的。因此,
bp
寄存器是合理的。在这种架构中,bp
被允许索引([bp -300 h]),但sp
不是。那些可能被解释为mov ax, [sp + 1111h]
的操作码/指令编码被重用于其他用途。在386+中,通过“E”的引入,ESP获得了偏移的属性。此时
EBP
从唯一的目的中解放出来,因为esp
能够处理两个任务。注意,即使现在
EBP
也通过stack segment(SS)指向内存,就像ESP
一样。其他寻址模式(没有ESP/EBP作为基址)默认为DS段。(16位模式中的绝对、DI、SI和/或BX,以及32位寻址模式中的任何寄存器都可以是寻址模式中的基址)。bttbmeg04#
ESP寄存器是系统堆栈的堆栈指针。它很少被程序直接更改,但在将数据推入堆栈或从堆栈弹出时更改。堆栈的一个用途是在过程调用中。过程调用指令后的指令地址存储在堆栈上。EBP寄存器指向基址。通常堆栈中唯一被访问的数据项是位于堆栈顶部的数据项。虽然EBP寄存器经常被用来标记堆栈中的固定点而不是堆栈顶部,例如,参数是这样的数据。它们是从堆栈EBP顶部的返回地址后的基指针的偏移量。因此,你会看到类似于EBP+0x8,EBP+0xC,这分别是参数1和2。
在汇编语言编程中,理解堆栈是非常重要的,因为它会影响到你将要使用的调用约定,而不管它是什么类型。例如,即使是cdecl或__stdcall也依赖于ESP和EBP寄存器,而其他的也在某种程度上依赖于某些寄存器和堆栈。