• 转 函数调用约定 规格严格


    谈到函数,一般首先要分析一下各种函数调用约定,比如_cdecl、 _stdcall等。这两种调用约定调用时都是最右侧的参数先进栈,栈最上面的就是函数的第一个参数。不同之处在于,_cdecl由调用者清理参数占用的 栈空间,而_stdcall由被调用者清理参数占用的栈空间。很明显,对于接受可变参数的函数,如printf,被调用函数是无法知道到底有几个参数的, 所以只能采用由调用者清理参数栈的方式。_stdcall调用方式生成的代码会小一点。

         下面的分析采用VC++6.0进行。

    一.函数内部的汇编代码

    1. void func()  
    2. {  
    3. }  
    4.   
    5. int main()  
    6. {  
    7.     func();  
    8.     return 0;  
    9. }  

    对应的汇编代码是:

     1:    void func()
    2:    {
    00401020   push        ebp
    00401021   mov         ebp,esp
    00401023   sub         esp,40h
    00401026   push        ebx
    00401027   push        esi
    00401028   push        edi
    00401029   lea         edi,[ebp-40h]
    0040102C   mov         ecx,10h
    00401031   mov         eax,0CCCCCCCCh
    00401036   rep stos    dword ptr [edi]
    3:    }
    00401038   pop         edi
    00401039   pop         esi
    0040103A   pop         ebx
    0040103B   mov         esp,ebp
    0040103D   pop         ebp
    0040103E   ret

    5:    int main()
    6:    {
    00401050   push        ebp
    00401051   mov         ebp,esp
    00401053   sub         esp,40h
    00401056   push        ebx
    00401057   push        esi
    00401058   push        edi
    00401059   lea         edi,[ebp-40h]
    0040105C   mov         ecx,10h
    00401061   mov         eax,0CCCCCCCCh
    00401066   rep stos    dword ptr [edi]
    7:        func();
    00401068   call        @ILT+0(func) (00401005)
    8:        return 0;
    0040106D   xor         eax,eax
    9:    }
    0040106F   pop         edi
    00401070   pop         esi
    00401071   pop         ebx
    00401072   add         esp,40h
    00401075   cmp         ebp,esp
    00401077   call        __chkesp (00401090)
    0040107C   mov         esp,ebp
    0040107E   pop         ebp
    0040107F   ret
       

         

         一般来说,函数开头的代码如下:

     push        ebp           //保存ebp
     mov         ebp,esp     //将esp的值送ebp,在函数内部可能还会使用push、pop等操作,这时esp的值会不断变化,如果采用esp来寻址局 部变量或者参数的话,可能要不断修正偏移量,而采用ebp寻址变量就方便很多。当然,这样会浪费一个寄存器,编译器可以优化。
     sub          esp,40h     //为局部变量分配空间,这里的40h大小是vc默认分配的。

        而结尾代码如下:

    mov         esp,ebp  //恢复esp,其实相当于把栈里局部变量的空间回收了 
    pop         ebp
    ret

         可以看到,即使函数没有定义任何局部变量,编译器仍然为我们非配了0x40大小的空间,并全部初始化0xcccccccc。有时候我们会见到烫烫€这样的字符串信息,就是这部分内存在作怪。

    二 带参数和局部变量的函数

    1. #include <cstdio>  
    2. void func(int a,int b)  
    3. {  
    4.     int m = a;  
    5.     int n = b;  
    6. }  
    7.   
    8. int main()  
    9. {  
    10.     func(16,32);  
    11.     return 0;  
    12. }  
     

    反汇编代码:

    main中调用func的代码是:

    00401088   push        20h  //32压栈
    0040108A   push        10h  //16压栈
    0040108C   call        @ILT+10(func) (0040100f)
    00401091   add         esp,8  //调用完毕后清理参数占用的栈空间,这里采用的是_cdecl调用约定。

    再看func的代码:

    2:    void func(int a,int b)
    3:    {
    00401020   push        ebp
    00401021   mov         ebp,esp
    00401023   sub         esp,48h  //默认分配0x40,现在有两个局部int类型变量,空间大小增大8
    ……
    4:        int m = a;
    00401038   mov         eax,dword ptr [ebp+8] 
    0040103B   mov         dword ptr [ebp-4],eax
    5:        int n = b;
    0040103E   mov         ecx,dword ptr [ebp+0Ch]
    00401041   mov         dword ptr [ebp-8],ecx
    6:    }
    ……
    00401047   mov         esp,ebp
    00401049   pop         ebp
    0040104A   ret

         函数中通过ebp加偏移量的方式来寻址局部变量,很容易看出,栈布局如下图所示:

         图中,地址从上到下增加。因为这里是near调用,所以没有保存ECS寄存器的内容。

  • 相关阅读:
    uwsgi
    protobuf c++ API
    memcached 第二篇----安装使用
    ice grid配置使用第二篇------实际使用
    ICE BOX 配置,使用----第一篇
    可视化资料收集
    Protocol Buffers
    ice grid 完整部署过程
    django组件之ajax
    关于Django在写小项目的一些小注意事项
  • 原文地址:https://www.cnblogs.com/diyunpeng/p/2255596.html
Copyright © 2020-2023  润新知