子函数调用——对栈帧的理解
2012-03-25 21:06
381 查看
首先要知道,EBP中存储的值始终指向栈底,ESP则始终指向栈顶。
汇编如下
main:
....
call fun
....
....
....
fun:
push ebp
mov ebp,esp
call fun1
....
leave
ret
一图胜千言,图中的是一条指令执行完后栈的状态
初始状态 call fun push ebp mov ebp,esp
![](http://my.csdn.net/uploads/201203/25/1332680607_3665.png)
![](http://my.csdn.net/uploads/201203/25/1332680707_3239.png)
![](http://my.csdn.net/uploads/201203/25/1332680763_8757.png)
![](http://my.csdn.net/uploads/201203/25/1332680794_3046.png)
push 0x0012 push 0x0034 call fun1 push ebp mov ebp,esp
![](http://my.csdn.net/uploads/201203/25/1332680814_4177.png)
![](http://my.csdn.net/uploads/201203/25/1332680826_1820.png)
![](http://my.csdn.net/uploads/201203/25/1332680841_2650.png)
![](http://my.csdn.net/uploads/201203/25/1332680879_8166.png)
接下来是函数依次返回
mov esp,ebp pop ebp ret (fun1) pop pop
![](http://my.csdn.net/uploads/201203/25/1332680879_8166.png)
![](http://my.csdn.net/uploads/201203/25/1332684139_3375.png)
![](http://my.csdn.net/uploads/201203/25/1332684157_8895.png)
![](http://my.csdn.net/uploads/201203/25/1332684168_8840.png)
mov esp,ebp (这里ebp的值是0xF0) pop ebp ret (main)
![](http://my.csdn.net/uploads/201203/25/1332684168_8840.png)
![](http://my.csdn.net/uploads/201203/25/1332684182_5864.png)
![](http://my.csdn.net/uploads/201203/25/1332684191_2093.png)
指令分析如下
1.call fun时,cpu将call fun的下一条指令内存地址eip+n(n视指令长度而定)入栈,之后esp-4->esp,(一个内存地址为4B)
2.push ebp, ebp内容入栈,之后esp<-(esp-4),(一个内存地址为4B)
3.mov ebp,esp, ebp<-esp,开始此函数的栈帧,这条汇编执行完之后,下条汇编之前,ebp中的值是和esp中的一样的,但是esp的值会随着后面的对栈的操作push、pop而变化。
这里的ebp就可以理解成是esp的一个拷贝。可以这么理解,ebp中存储的内存地址是此函数的栈底(不能在这个地址上存数据,要从ebp-4开始存数据)。
接下来,我们可以向这个栈中存储函数中的参数了,或者再调用子函数call fun1。push,pop.....
4.leave的作用相当于以下语句:
mov esp,ebp
pop ebp
a. mov esp,ebp,esp<-ebp,恢复之前拷贝的esp值,一般来说在这一句之前esp==ebp,不排除可能push和pop不对称导致esp,ebp寄存器中内容不一致。
b. pop ebp,呃,恢复之前的存储的ebp值。
5.ret 函数返回,cpu从栈中弹出一个4B的数并装入eip中,并执行。对应于步骤1,这就是最刚开始压入的下条指令地址。
总结:
从call fun 开始,栈中先后压入了下条指令地址,ebp的值,以及函数执行时压入的各个值。
函数结束时,栈中弹出的依次是函数执行时压入的值,ebp的值,下条指令地址。
push ebp
mov ebp,esp
这两句和
leave
就构成了栈框架的建立和释放。
问:为什么ebp的值显得如此重要呢?
答:
从第3步可知,ebp构成了一个函数的栈底,我们可以使用$(ebp-n)来方便的引用函数fun中的变量。
而且$(ebp)是调用此函数fun的函数的栈底,我们可以想象再fun函数中又执行了一个子函数foo,同样会建立栈帧....
通过ebp我们可以方便的回溯查看各个函数的详细情况。
汇编如下
main:
....
call fun
....
....
....
fun:
push ebp
mov ebp,esp
call fun1
....
leave
ret
一图胜千言,图中的是一条指令执行完后栈的状态
初始状态 call fun push ebp mov ebp,esp
![](http://my.csdn.net/uploads/201203/25/1332680607_3665.png)
![](http://my.csdn.net/uploads/201203/25/1332680707_3239.png)
![](http://my.csdn.net/uploads/201203/25/1332680763_8757.png)
![](http://my.csdn.net/uploads/201203/25/1332680794_3046.png)
push 0x0012 push 0x0034 call fun1 push ebp mov ebp,esp
![](http://my.csdn.net/uploads/201203/25/1332680814_4177.png)
![](http://my.csdn.net/uploads/201203/25/1332680826_1820.png)
![](http://my.csdn.net/uploads/201203/25/1332680841_2650.png)
![](http://my.csdn.net/uploads/201203/25/1332680879_8166.png)
接下来是函数依次返回
mov esp,ebp pop ebp ret (fun1) pop pop
![](http://my.csdn.net/uploads/201203/25/1332680879_8166.png)
![](http://my.csdn.net/uploads/201203/25/1332684139_3375.png)
![](http://my.csdn.net/uploads/201203/25/1332684157_8895.png)
![](http://my.csdn.net/uploads/201203/25/1332684168_8840.png)
mov esp,ebp (这里ebp的值是0xF0) pop ebp ret (main)
![](http://my.csdn.net/uploads/201203/25/1332684168_8840.png)
![](http://my.csdn.net/uploads/201203/25/1332684182_5864.png)
![](http://my.csdn.net/uploads/201203/25/1332684191_2093.png)
指令分析如下
1.call fun时,cpu将call fun的下一条指令内存地址eip+n(n视指令长度而定)入栈,之后esp-4->esp,(一个内存地址为4B)
2.push ebp, ebp内容入栈,之后esp<-(esp-4),(一个内存地址为4B)
3.mov ebp,esp, ebp<-esp,开始此函数的栈帧,这条汇编执行完之后,下条汇编之前,ebp中的值是和esp中的一样的,但是esp的值会随着后面的对栈的操作push、pop而变化。
这里的ebp就可以理解成是esp的一个拷贝。可以这么理解,ebp中存储的内存地址是此函数的栈底(不能在这个地址上存数据,要从ebp-4开始存数据)。
接下来,我们可以向这个栈中存储函数中的参数了,或者再调用子函数call fun1。push,pop.....
4.leave的作用相当于以下语句:
mov esp,ebp
pop ebp
a. mov esp,ebp,esp<-ebp,恢复之前拷贝的esp值,一般来说在这一句之前esp==ebp,不排除可能push和pop不对称导致esp,ebp寄存器中内容不一致。
b. pop ebp,呃,恢复之前的存储的ebp值。
5.ret 函数返回,cpu从栈中弹出一个4B的数并装入eip中,并执行。对应于步骤1,这就是最刚开始压入的下条指令地址。
总结:
从call fun 开始,栈中先后压入了下条指令地址,ebp的值,以及函数执行时压入的各个值。
函数结束时,栈中弹出的依次是函数执行时压入的值,ebp的值,下条指令地址。
push ebp
mov ebp,esp
这两句和
leave
就构成了栈框架的建立和释放。
问:为什么ebp的值显得如此重要呢?
答:
从第3步可知,ebp构成了一个函数的栈底,我们可以使用$(ebp-n)来方便的引用函数fun中的变量。
而且$(ebp)是调用此函数fun的函数的栈底,我们可以想象再fun函数中又执行了一个子函数foo,同样会建立栈帧....
通过ebp我们可以方便的回溯查看各个函数的详细情况。
相关文章推荐
- 函数调用入栈基本步骤(感觉和进程的栈帧结构一块看会比较容易理解)
- 函数调用入栈基本步骤(感觉和进程的栈帧结构一块看会比较容易理解)
- 深入理解函数的调用过程——栈帧
- 函数调用入栈基本步骤(感觉和进程的栈帧结构一块看会比较容易理解)
- 【栈帧】深入理解函数的调用(栈帧)
- 函数调用过程-栈帧 和 进程的关系
- 深入理解:立即调用的函数表达式
- 函数调用返回与栈帧空间开辟回收
- 函数的调用过程——栈帧
- 函数参数与函数调用(彻底理解值传递与引用传递)转别人
- 深入理解JavaScript系列(4) 立即调用的函数表达式
- 从函数调用过程中的堆栈变化理解缓冲区溢出
- 函数调用修饰符__stdcall/__cdecl的理解
- 深入理解JavaScript系列(4):立即调用的函数表达式
- [置顶] Python 模块里函数的调用方法和import语句的作用(适合初学者理解函数的调用)
- 谈谈函数的调用过程,栈帧的创建和销毁。
- 函数调用时的栈帧结构以及临时变量的深入研究
- 函数的调用过程,栈帧的创建和销毁
- cocos2d-x 中LUA和平台之间的函数调用理解
- c函数调用过程原理及函数栈帧分析