对于ESP、EBP寄存器的理解

转载地址:

http://blog.csdn.net/yeruby/article/details/39780943

esp是栈指针,是cpu机制决定的,push、pop指令会自动调整esp的值;

ebp只是存取某时刻的esp,这个时刻就是进入一个函数内后,cpu会将esp的值赋给ebp,此时就可以通过ebp对栈进行操作,比如获取函数参数,局部变量等,实际上使用esp也可以;

既然使用esp也可以,那么为什么要设定ebp呢?

答案是为了方便程序员。

因为esp在函数运行时会不断的变化,所以保存一个一进入某个函数的esp到ebp中会方便程序员访问参数和局部变量,而且还方便调试器分析函数调用过程中的堆栈情况。前面说了,这个ebp不是必须要有的,你非要使用esp来访问函数参数和局部变量也是可行的,只不过这样会麻烦一些。


通过一段程序理解esp和ebp:

main() {

//执行test前

print(int p1,int p2);

//执行test后

}

分析下上面程序的调用原理,假设执行print前esp=Q:

push p2; //函数参数p2入栈,esp=Q-4H

push p1; //函数参数p1入栈,esp=Q-8H

call print; //函数返回地址入栈,esp=Q-0CH

//现在进入print内,做些准备工作:

push ebp; //保护先前ebp指针,ebp入栈,esp=Q-10H

mov ebp,esp; //设置ebp等于当前的esp

// 此时,ebp+0CH=Q-4H,即p2的位置

// 同样,ebp+08H=Q-8H,即p1的位置

// 下面是print内的一些操作:

sub esp,20H; //设置长度为10H大小的局部变量空间,esp=Q-20H

// ... ...

// 一系列操作

// ... ...

add esp,20H; //释放局部变量空间,esp=Q-10H

pop ebp; //出栈,恢复原先的ebp的值,esp=Q-0CH

ret 8; //ret返回,弹出先前入栈的返回地址,esp=Q-08H,后面加操作数8H为平衡堆栈

// 之后,弹出函数参数,esp=Q,恢复执行print函数前的堆栈;


图示,注意栈在内存中的生长方向是逆向:


执行push p2;前,esp=Q;

执行push p2;过程中,esp-=4H,p2入栈;

执行push p2;后,esp=Q-4H;


参考:

http://www.360doc.com/content/13/0309/22/9290626_270468335.shtml   ---ESP EBP讲解

http://blog.csdn.net/hp_truth/article/details/45039933  ----函数调用栈原理,栈回溯

http://www.cnblogs.com/syw-casualet/p/5223595.html  ----C语言程序运行时的栈与寄存器的变化

http://blog.csdn.net/wangyezi19930928/article/details/16921927   ----函数调用栈 剖析+图解


****推荐计算机原理系列博文;

http://www.cnblogs.com/swiftma/p/5467964.html   ---计算机原理系列


理解:


       总体上,ESP就是一直指向当前的调用栈的栈指针,会随着程序运行一直不断变化,可以将栈想象成一段内存,并且这个内存是有大小限制的,例如: windows的内核栈默认大概是4M的大小,程序编译好后反汇编后可以看到很多的代码段,这些代码最终会进入到栈中执行,call就是调用函数的指令,ret就是返回函数的指令。

那么一旦我们进入到main后,其实在进入main之前,也有很多调用,然后执行到Main的时候,有个前提条件,就是调用Main之前,假设此时的返回的基地址指针EBP(假设为0x11111111),然后ESP是0x2222222;

       

        第一步,此时进入到Main的时候,我们需要push EBP,  也就是说保存原先的基地址0x11111111(old EBP),这个原先的地址就推入到main的栈空间中了(其实就是一个系统申请的内存地址,这个地址存放了main的汇编代码),此时ESP就会向下移动,32位下,ESP = ESP-4( 执行一条指令); 

        第二步 , 这个时候, 将ESP的值,赋值给EBP, mov  ebp, esp; 也就是说,我们将该ESP的地址,存放到了EBP里,这个时候EBP就很重要了,因为它存放的是我们认为的main函数的要准备进入执行阶段的一个基地址,也就是说,EBP的地址就是0x2222222+4的地址了(这个EBP,也叫做入栈后的栈顶), 此时ESP又向栈空间移动了4个字节的地址(增长了,该程序的栈空间又减少了,不要把栈空间撑满了,撑满了就栈溢出了!)

第三步,sub esp, 0x0Ch; 减去一个空间,继续向下移动ESP(ESP=ESP-4, 执行了sub指令),这个动作是为了开辟一块给这个main函数存放局部临时变量的内存空间

从上面这个三步,我们可以看到,EBP重要性是,通过这个地址,我们向上,可以取到返回地址,也就是说通过pop EBP+4, 这样,就可以获取到执行完main函数返回后,需要继续执行的地址,通过pop EBP+8,这样也可以获取参数(_ctcall,支持可变参数,_stacll,不支持可变参数?) ; 而通过EBP-4,这样就可以获取局部变量---这个EBP在函数运行中还是比较重要的(当然设计上也可以不需要EBP,通过ESP也可以算出来,但是非常不方便!)。

         中间运行省略...

 第四步, mov esp , ebp;---恢复esp的值, 也就是第二步,一开始的EBP的值, 往回看看,一开始是不是将ESP的值存放为EBP的,ESP的值是上一个函数运行下来的ESP值,要恢复回去, 因为你的函数马上要执行完了, 马上要开始返回到上一个函数了; 因为这个时候,这个函数要执行完成了,所以要将一开始的ESP的值给恢复回去,这样就可以去继续执行上一个函数了。

第五步, pop ebp, ----这个pop也很重要,将之前push ebp的(old)EBP, 给pop出来,所以,这一步pop的是(old)EBP.

第六步,ret ---返回来刚才压栈保存的位置,继续运行ESP;

这样一个函数就调用完成了,ESP也恢复到一开始入栈时候的ESP了



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值