浅谈c程序函数调用过程

关键词:

栈区:就是一个内存地址空间,每调用一次函数就会在栈区为此函数分配一段空间(主要用于存储局部变量,

此段空间下面就直接定义为函数栈)

 

ebp :用于存放函数栈的栈顶地址

esp:用于存放此函数栈的栈底地址

注意:栈顶地址大于栈底地址,栈是从栈顶向栈底增长。即ebp-->esp;

下面我们分析如下代码例子,看看它的调用机制

 

#include<stdio.h>
int fun(int c)
{
 int d=c+1;
 return d;
}
int main()
{
    int a=1;
    int b=fun(a);
    return 0;
}

                 

  int a=1;

 汇编代码: mov         dword ptr [ebp-4],1

很显然,ebp是用作的了基址寄存器,注意ebp的值是不会变的,它会固定指向函数栈的栈顶,在这里ebp此时的值是0x0013FF80

              

 

 int b=fun(a);
 汇编代码:

   mov         eax,dword ptr [ebp-4]                      

   push        eax                                             
   call        @ILT+0(fun) (00401005)               
   add         esp,4                                           
   mov         dword ptr [ebp-8],eax                

 

 

注意前面两句, [ebp-4]显然是a的地址,这两句是将参数a压入esp指向的栈中(即栈底)在这里esp此时的值是0x0013FF2C(

即编译器给main函数局部变量分配的栈空间为0x0013FF80-0x0013FF2C=54H的栈空间)

当执行完了第二条指令之时 esp的值减4H变为0x0013FF28,所以可以看出函数参数是被放在了main函数栈的下面,紧挨栈底。

然后系统自动继续将函数返回地址(即上面第四条语句的地址)压入栈中,此时esp的值0x0013FF24.

 

 call指令调用fun函数,在fun函数开始执行自己代码之前会先执行如下代码(这在函数调用中是必须的)

   push         ebp
   mov         ebp,esp
   sub          esp,44h
   push        ebx
   push        esi
   push        edi
   lea          edi,[ebp-44h]
   mov        ecx,11h

   mov        eax,0CCCCCCCCh
   rep stos   dword ptr [edi]

 

具体工作如下:

现将main函数栈顶指针ebp压入栈中(这很重要,在函数返回时有用),此时esp为0x0013FF20,然后将此值再存入ebp中,

即下一个fun函数栈的栈顶。fun函数栈底则变为0x0013FF20-44h即,编译器为fun函数局部变量分配了44h的栈空间,

此时esp指向了0x0013FEDC。然后再将ebx,esi,edi中的值压入栈中(在这个简单的例子中,这些寄存器都没用到)然后esp减去CH,

变为了0x0013FED0。后面四条指令与我们分析的主题无直接关系,就不说了,而且在此例中无用。

下面将注意力放在fun函数是如何取参数的:

 

 

     int d=c+1;
  
mov         eax,dword ptr [ebp+8]
   add          eax,1
   mov         dword ptr [ebp-4],eax 

  从第一条指令可以看出fun函数的参数是在[ebp+8]位置的,即在fun函数栈顶的上面,这种机制可以很好的区分函数参数与局部

 变量,之所以加8是因为在ebp的上面还有main函数返回地址。然后才是函数参数。

 

现在可以将注意力集中在fun函数是如何返回的:

返回代码如下:

   mov        eax,dword ptr [ebp-4]
   pop         edi
   pop         esi
   pop         ebx
   mov        esp,ebp
   pop         ebp
   ret

 

 

 

第一条指令就是return d;将d的值保存在eax寄存器中。

 

后面三条POP分别对应前面三条push,注意三条pop之后,在fun函数栈底一端任务是完成了。

后面的mov指令将esp重新指向了0x0013FF20,此地址中的值正好是main函数栈的栈顶的地址。

然后执行 POP ebp将ebp重新指向main函数栈顶。

注意ret返回指令不是这么简单的,是由硬件自动执行了隐藏的指令,我的分析是这样的:

在执行POP ebp之后,esp则正好指向了原来保存的main函数返回地址。即相当于这条指令:pop  eip;

这就是整个调用过程了。

 

但是不要忘了esp还没有恢复原值呢!这时候esp的上面还有上次传参时保留的参数值a。所以这也就解释了前面第一段汇编代码的

add        esp,4指令。这时整个调用过程才彻底结束!             

            

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值