目的: 本文要做的事就是通过一个最简单的程序学习C代码函数调用的内部实现。

环境:Windows XP + Visual C++ 6.0

C代码如下:

  1. #include "stdafx.h"  
  2.  
  3. void test(int a)  
  4. {  
  5.     int  c = a;  
  6. }  
  7.  
  8. int main(int argc, char* argv[])  
  9. {  
  10.     test(argc);  
  11.  
  12.       
  13.     return 0;  
  14. }  

test函数只传了一个参数,不验证cdecl、stdcall 、fastcall等调用约定的差异。百度一下记录在这: 

cdecl:参数入栈按从右至左的顺序,调用者将参数弹出栈。可以给函数定义个数不定的参数,如print函数。

stdcall:参数入栈按从右至左的顺序。由被调用者将参数弹出栈。WIN32 API遵循此约定。

fastcall:头两个DWORD类型或者占更少字节的参数放入ECX和EDX寄存器,其他剩余参数按从右至左的顺序压入栈。参数由被调用者弹出栈。

 汇编代码如下:

  1. 11:   int main(int argc, char* argv[])  
  2. 12:   {  
  3. 0040B480 55                   push        ebp  
  4. 0040B481 8B EC                mov         ebp,esp  
  5. 0040B483 83 EC 40             sub         esp,40h  
  6. 0040B486 53                   push        ebx  
  7. 0040B487 56                   push        esi  
  8. 0040B488 57                   push        edi  
  9. 0040B489 8D 7D C0             lea         edi,[ebp-40h]  
  10. 0040B48C B9 10 00 00 00       mov         ecx,10h  
  11. 0040B491 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
  12. 0040B496 F3 AB                rep stos    dword ptr [edi]  
  13. 13:       test(argc);  
  14. 0040B498 8B 45 08             mov         eax,dword ptr [ebp+8]  
  15. 0040B49B 50                   push        eax  
  16. 0040B49C E8 69 5B FF FF       call        @ILT+5(test) (0040100a)  
  17. 0040B4A1 83 C4 04             add         esp,4  
  18. 14:  
  19. 15:  
  20. 16:       return 0;  
  21. 0040B4A4 33 C0                xor         eax,eax  
  22. 17:   }  

 

  1. @ILT+0(_main):  
  2. 00401005 E9 76 A4 00 00       jmp         main (0040b480)  
  3. 0040100A E9 01 00 00 00       jmp         test (00401010)  
  4. 0040100F CC                   int         3  

 

  1.     void test(int a)  
  2. 7:    {  
  3. 00401010 55                   push        ebp  
  4. 00401011 8B EC                mov         ebp,esp  
  5. 00401013 83 EC 44             sub         esp,44h  
  6. 00401016 53                   push        ebx  
  7. 00401017 56                   push        esi  
  8. 00401018 57                   push        edi  
  9. 00401019 8D 7D BC             lea         edi,[ebp-44h]  
  10. 0040101C B9 11 00 00 00       mov         ecx,11h  
  11. 00401021 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
  12. 00401026 F3 AB                rep stos    dword ptr [edi]  
  13. 8:        int  c = a;  
  14. 00401028 8B 45 08             mov         eax,dword ptr [ebp+8]  
  15. 0040102B 89 45 FC             mov         dword ptr [ebp-4],eax  
  16. 9:    }  
  17. 0040102E 5F                   pop         edi  
  18. 0040102F 5E                   pop         esi  
  19. 00401030 5B                   pop         ebx  
  20. 00401031 8B E5                mov         esp,ebp  
  21. 00401033 5D                   pop         ebp  
  22. 00401034 C3                   ret  

 

 1、main函数首先是将基址指针寄存器入栈,再将堆栈指针寄存器赋值给基址指针寄存器。

  1. 0040B480 55                   push        ebp  
  2. 0040B481 8B EC                mov         ebp,esp 

 

2、将ESP下移40H+0H(预留40H,局部变量0H,为何预留40H暂时没有搞清楚),用来存储局部变量。

  1. 0040B483 83 EC 40             sub         esp,40h 

 

3、将寄存器入栈,用于函数返回时恢复现场。

  1. 0040B486 53                   push        ebx  
  2. 0040B487 56                   push        esi  
  3. 0040B488 57                   push        edi  

4、循环初始化局部变量部分内存为0xCCCCCCCC。

  1. 0040B489 8D 7D C0             lea         edi,[ebp-40h]  
  2. 0040B48C B9 10 00 00 00       mov         ecx,10h  
  3. 0040B491 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
  4. 0040B496 F3 AB                rep stos    dword ptr [edi] 

5、入参入栈。

  1. 0040B498 8B 45 08             mov         eax,dword ptr [ebp+8]  
  2. 0040B49B 50                   push        eax  

 

6、调用test函数。ILT为 Incremental   Linking   Table 的缩写,关闭Link   Incrementally后,编译器就不会产生ILT。ILT的作用是减少debug版的编译时间。

  1. 0040B49C E8 69 5B FF FF       call        @ILT+5(test) (0040100a) 

7、test函数保存ebp、预留局部变量空间并初始化、保留现场。同main函数。

  1. 00401010 55                   push        ebp  
  2. 00401011 8B EC                mov         ebp,esp  
  3. 00401013 83 EC 44             sub         esp,44h  
  4. 00401016 53                   push        ebx  
  5. 00401017 56                   push        esi  
  6. 00401018 57                   push        edi  
  7. 00401019 8D 7D BC             lea         edi,[ebp-44h]  
  8. 0040101C B9 11 00 00 00       mov         ecx,11h  
  9. 00401021 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
  10. 00401026 F3 AB                rep stos    dword ptr [edi]  

8、int c = a;  ebp+4存的是函数调用前的ebp。ebp+8是入参a,ebp-4是局部变量c。

  1. 00401028 8B 45 08             mov         eax,dword ptr [ebp+8]  
  2. 0040102B 89 45 FC             mov         dword ptr [ebp-4],eax 

9、test执行完成,恢复现场。

  1. 0040102E 5F                   pop         edi  
  2. 0040102F 5E                   pop         esi  
  3. 00401030 5B                   pop         ebx  
  4. 00401031 8B E5                mov         esp,ebp  
  5. 00401033 5D                   pop         ebp  
  6. 00401034 C3                   ret  

10、释放入参占用的栈空间。

  1. 0040B4A1 83 C4 04             add         esp,4  

11、置返回值为0。

  1. 0040B4A4 33 C0                xor         eax,eax 

12、main函数恢复堆栈返回。

  1. 0040B4A6 5F                   pop         edi  
  2. 0040B4A7 5E                   pop         esi  
  3. 0040B4A8 5B                   pop         ebx  
  4. 0040B4A9 83 C4 40             add         esp,40h  
  5. 0040B4AC 3B EC                cmp         ebp,esp  
  6. 0040B4AE E8 0D 00 00 00       call        __chkesp (0040b4c0)  
  7. 0040B4B3 8B E5                mov         esp,ebp  
  8. 0040B4B5 5D                   pop         ebp  
  9. 0040B4B6 C3                   ret