堆栈是连续的地址空间,且向低地址端生长。
esp 是堆栈指针
ebp 是基址指针
那两条指令的意思是 将栈顶指向 ebp 的地址
以下摘自网上一篇文章:
push ebp ; ebp 入栈
mov ebp, esp ; 因为 esp 是堆栈指针,无法暂借使用,所以得用 ebp 来存取堆栈
sub esp, 4*5 ; 下面的 wsprintf 一共使用了 5 个参数,每个参数占用 4 个字节,所以要入栈 4*5 个字节
push 1111
push 2222
push 3333
push offset szFormat
push offset szOut
call wsprintf ; 调用 wsprintf
add esp, 4*5 ; 堆栈使用完毕,“还” 回 4*5 个字节给系统
...
mov esp, ebp ; 恢复 esp 的值
pop ebp ; ebp 出栈
ret
明白了吗?主要是用来保存 / 恢复堆栈,以便传递参数给函数。 在 MASM 里面,有一条更方便的语句,就是 invoke 使用它后,你就不用自己做这些事情了。
---------------------------------------------------------------
esp 始终指向栈顶,ebp 是在堆栈中寻址用的
我的理解:
调用一个函数时,先将堆栈原先的基址(EBP)入栈,以保存之前任务的信息。然后将栈顶指针的值赋给 EBP,将之前的栈顶作为新的基址(栈底),然后再这个基址上开辟相应的空间用作被调用函数的堆栈。函数返回后,从 EBP 中可取出之前的 ESP 值,使栈顶恢复函数调用前的位置;再从恢复后的栈顶可弹出之前的 EBP 值,因为这个值在函数调用前一步被压入堆栈。这样,EBP 和 ESP 就都恢复了调用前的位置,堆栈恢复函数调用前的状态。
二. 通过 ollydbg 跟踪 esp 和 ebp
发现文字描述还是太没有快感。上几幅图,来说明这个调试过程更好。此文对于深刻理解 ebp,esp 是具有长远意义的
然后我们让它执行前两句,push ebp,mov ebp,esp
接着上图不解释:
所以重点就在 pop 这个语句了。pop ebp 究竟表达神马意思?ebp 的值起初存在了栈中,出栈以后,它的值就恢复了原样。所一句灰常重要啊。pop 的意思也许就是把弹出的值赋给我们的变量,pop ebp,也就是把存在栈中的值弹出来赋给 ebp。
所以我在这里总结几句:
1、两句的 mov ebp,esp 实际上是把 ebp 进栈后的栈顶地址给了 ebp。
2、在 ebp 没有出栈钱,它会一直保存 ebp 进栈以后的栈顶值,也就是 1 的值。
3、在 ebp 出栈前,需要把 esp 恢复到只有 ebp 在栈中时的值。
4、出栈后,esp 自然恢复到 ebp 进栈以前的初始值,而 pop ebp 则恢复了 ebp 的初始值。
5、pop 的语义很重要, pop ebp 的意思是把当前栈顶的元素出栈,送入 ebp 中,而不是让 ebp 出栈,这点必须明确!
这下应该明白了吧~~~~
参考网上资源:
http://blog.csdn.net/running_noodle/article/details/2838679
http://hi.baidu.com/anheizzq/item/1c0899622926c81e7ddecca3
ebp-- 栈底指针
esp-- 栈顶指针
如图所示,简化后的代码调用过程如下:
void Layer02()
{
int b = 2;
}
void Layer01()
{
int a = 1;
Layer02();
}
那么函数执行过程中 ebp 和 esp 是如何变化的呢?如下是反汇编后的代码:
void Layer02()
{
00413700 push ebp
00413701 mov ebp,esp
00413703 sub esp,0CCh
00413709 push ebx
0041370A push esi
0041370B push edi
0041370C lea edi,[ebp-0CCh]
00413712 mov ecx,33h
00413717 mov eax,0CCCCCCCCh
0041371C rep stos dword ptr es:[edi]
int b = 2;
0041371E mov dword ptr [b],2
}
00413725 pop edi
00413726 pop esi
00413727 pop ebx
00413728 mov esp,ebp
0041372A pop ebp
0041372B ret
我们看到函数调用开始执行如下的两行代码:
00413700 push ebp
00413701 mov ebp,esp
返回前执行如下代码:
00413728 mov esp,ebp
0041372A pop ebp
0041372B ret
那么这几行代码到底是什么意思呢?首先,如图上所示:
开始两行代码的意思是先将 ebp1 压栈,然后将现在的栈顶 esp1 作为函数调用时的栈底,所以会执行如下语句:
00413701 mov ebp,esp
那么,返回前的几条语句又是什么意思呢?
我想大家已经猜到了,当函数调用执行结束,我们要执行相反的过程:
00413728 mov esp,ebp
还原栈顶指针
0041372A pop ebp
还原栈底指针
0041372B ret
返回到函数调用前的指令继续执行。待续…
ESP,EBP, 栈回溯基本原理
我们看到,尽管可以使用相对于栈顶(ESP 寄存器)的偏移来引用局部变量,但是因为 ESP寄存器经常变化,所以用这种方法引用同一个局部变量的偏移值是不固定的。这种不确定性对于 CPU 来说不成什么问题,但在调试时,如果要跟踪这样的代码,那么很容易就被转得头晕眼花,因为现实的函数大多有多个局部变量,可能还有层层嵌套的循环,栈指针变化非常频繁。
为了解决以上问题,x86 CPU 设计了另一个寄存器,这就是 EBP 寄存器。EBP 的全称是 Extended Base Pointer,即拓展的基址指针。使用 EBP 寄存器,函数可以把自己将要使用的栈空间的基准地址记录下来,然后使用这个基准地址来引用局部变量和参数。在同一函数内,EBP 寄存器的值是保持不变的,这样函数内的局部变量便有了一个固定的参照物。
通常,一个函数在入口处将当时的 EBP 值压入堆栈,然后把 ESP 值(栈顶)赋给 EBP,这样 EBP 中的地址就是进入本函数时的栈顶地址,这一地址上面(地址值递减方向)的空间便是这个函数将要使用栈空间,它下面(地址值递增方向)是父函数使用的空间。如此设置 EBP 后,便可以使用 EBP 加正数偏移来引用父函数的内容,使用 EBP 加负数便宜来引用本函数的局部变量,比如 EBP+4 指向的是 CALL 指令压入的函数返回地址;EBP+8 是父函数压在栈上的第一个参数,EBP+0xC 是第二个参数,一次类推;EBP-n 是第一个局部变量的起始地址(n 为变量的长度)。
因为在将栈顶地址(ESP)赋给 EBP 寄存器之前先把旧的 EBP 值保存在栈中,所以 EBP 寄存器所指向的栈单元中保存的是前一个 EBP 寄存器的值,这通常也就是父函数的 EBP 的值。类似的父函数的 EBP 所指向的栈单元中保存的是更上一层函数的 EBP 值,以此类推,直到当前线程的最顶层函数。这也正是栈回溯的基本原理。