使用这个来自维基百科的例子,其中DrawSquare()调用DrawLine(),
(请注意,这张图底部是高地址,顶部是低地址。)
谁能解释一下ebp和esp在这里是什么意思?
从我所看到的,我想说堆栈指针总是指向堆栈的顶部,而基指针指向当前函数的开始?还是别的什么?
edit:我指的是在windows程序的上下文中
edit2: eip又是如何工作的呢?
编辑3:我有以下代码从msvc++:
var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
hInstance= dword ptr 8
hPrevInstance= dword ptr 0Ch
lpCmdLine= dword ptr 10h
nShowCmd= dword ptr 14h
它们似乎都是dwords,因此每个都占用4个字节。所以我可以看到从hInstance到var_4有一个4字节的空白。它们是什么?我猜这是回复地址,可以从维基百科的图片中看到?
(编者注:从迈克尔的回答中删除了一段很长的引语,这段引语不属于这个问题,但后面的问题被编辑进去了):
这是因为函数调用的流程是:
* Push parameters (hInstance, etc.)
* Call function, which pushes return address
* Push ebp
* Allocate space for locals
我的问题(我希望是最后一个!)现在是,从我弹出我想要调用的函数的参数到序言结束的那一刻到底发生了什么?我想知道ebp, esp在这些时刻是如何演变的(我已经理解了prolog是如何工作的,我只想知道在我把参数推到堆栈上和在prolog之前发生了什么)。
ESP是当前堆栈指针,当一个单词或地址被推入或弹出堆栈时,它将发生变化。对于编译器来说,EBP是一种比直接使用ESP更方便的跟踪函数参数和局部变量的方法。
通常(这可能因编译器而异),被调用函数的所有实参都被调用函数推入堆栈(通常与它们在函数原型中声明的顺序相反,但这也有所不同)。然后调用函数,将返回地址(EIP)推入堆栈。
Upon entry to the function, the old EBP value is pushed onto the stack and EBP is set to the value of ESP. Then the ESP is decremented (because the stack grows downward in memory) to allocate space for the function's local variables and temporaries. From that point on, during the execution of the function, the arguments to the function are located on the stack at positive offsets from EBP (because they were pushed prior to the function call), and the local variables are located at negative offsets from EBP (because they were allocated on the stack after the function entry). That's why the EBP is called the Frame Pointer, because it points to the center of the function call frame.
在退出时,函数所要做的就是将ESP设置为EBP的值(这将从堆栈中释放局部变量,并在堆栈顶部公开条目EBP),然后从堆栈中弹出旧的EBP值,然后函数返回(将返回地址弹出到EIP中)。
在返回到调用函数时,它可以增加ESP,以便在调用另一个函数之前删除它推入堆栈的函数参数。此时,堆栈回到了调用被调用函数之前的相同状态。
首先,堆栈指针指向堆栈的底部,因为x86堆栈是从高地址值构建到低地址值。堆栈指针是下一个push(或调用)调用将放置下一个值的点。它的操作相当于C/ c++语句:
// push eax
--*esp = eax
// pop eax
eax = *esp++;
// a function call, in this case, the caller must clean up the function parameters
move eax,some value
push eax
call some address // this pushes the next value of the instruction pointer onto the
// stack and changes the instruction pointer to "some address"
add esp,4 // remove eax from the stack
// a function
push ebp // save the old stack frame
move ebp, esp
... // do stuff
pop ebp // restore the old stack frame
ret
基指针位于当前帧的顶部。Ebp通常指向您的寄信人地址。Ebp +4指向函数的第一个参数(或类方法的this值)。ebp-4指向函数的第一个局部变量,通常是ebp的旧值,这样你就可以恢复之前的帧指针。
编辑:有关更好的描述,请参阅维基书中关于x86汇编的x86反汇编/函数和堆栈框架。我试着添加一些你可能对使用Visual Studio感兴趣的信息。
将调用者EBP存储为第一个局部变量称为标准堆栈框架,这可以用于Windows上几乎所有的调用约定。无论调用方还是被调用方释放传递的参数,以及哪些参数在寄存器中传递,都存在差异,但这些与标准堆栈帧问题是正交的。
说到Windows程序,你可能会使用Visual Studio来编译你的c++代码。请注意,微软使用了一种称为帧指针省略的优化,这使得不使用dbghlp库和可执行文件的PDB文件几乎不可能遍历堆栈。
这种帧指针省略意味着编译器不会将旧的EBP存储在标准位置,而是将EBP寄存器用于其他东西,因此在不知道给定函数的局部变量需要多少空间的情况下,您很难找到调用者EIP。当然,即使在这种情况下,Microsoft也提供了允许您执行堆栈遍历的API,但是对于某些用例来说,在PDB文件中查找符号表数据库花费的时间太长了。
为了避免在编译单元中使用FPO,您需要避免使用/O2,或者需要显式地向项目中的c++编译标志添加/Oy-。您可能会链接到在发布配置中使用FPO的C或c++运行时,因此如果没有dbghlp.dll,您将很难执行堆栈遍走。
ESP是当前堆栈指针,当一个单词或地址被推入或弹出堆栈时,它将发生变化。对于编译器来说,EBP是一种比直接使用ESP更方便的跟踪函数参数和局部变量的方法。
通常(这可能因编译器而异),被调用函数的所有实参都被调用函数推入堆栈(通常与它们在函数原型中声明的顺序相反,但这也有所不同)。然后调用函数,将返回地址(EIP)推入堆栈。
Upon entry to the function, the old EBP value is pushed onto the stack and EBP is set to the value of ESP. Then the ESP is decremented (because the stack grows downward in memory) to allocate space for the function's local variables and temporaries. From that point on, during the execution of the function, the arguments to the function are located on the stack at positive offsets from EBP (because they were pushed prior to the function call), and the local variables are located at negative offsets from EBP (because they were allocated on the stack after the function entry). That's why the EBP is called the Frame Pointer, because it points to the center of the function call frame.
在退出时,函数所要做的就是将ESP设置为EBP的值(这将从堆栈中释放局部变量,并在堆栈顶部公开条目EBP),然后从堆栈中弹出旧的EBP值,然后函数返回(将返回地址弹出到EIP中)。
在返回到调用函数时,它可以增加ESP,以便在调用另一个函数之前删除它推入堆栈的函数参数。此时,堆栈回到了调用被调用函数之前的相同状态。