目的: 本文要做的事就是通過一個最簡單的程序學習C代碼函數調用的內部實現。
環境:Windows XP + Visual C++ 6.0
C代碼如下:
- #include "stdafx.h"
- void test(int a)
- {
- int c = a;
- }
- int main(int argc, char* argv[])
- {
- test(argc);
- return 0;
- }
test函數只傳了一個參數,不驗證cdecl、stdcall 、fastcall等調用約定的差異。百度一下記錄在這:
cdecl:參數入棧按從右至左的順序,調用者將參數彈出棧。可以給函數定義個數不定的參數,如print函數。
stdcall:參數入棧按從右至左的順序。由被調用者將參數彈出棧。WIN32 API遵循此約定。
fastcall:頭兩個DWORD類型或者佔更少字節的參數放入ECX和EDX寄存器,其他剩餘參數按從右至左的順序壓入棧。參數由被調用者彈出棧。
彙編代碼如下:
- 11: int main(int argc, char* argv[])
- 12: {
- 0040B480 55 push ebp
- 0040B481 8B EC mov ebp,esp
- 0040B483 83 EC 40 sub esp,40h
- 0040B486 53 push ebx
- 0040B487 56 push esi
- 0040B488 57 push edi
- 0040B489 8D 7D C0 lea edi,[ebp-40h]
- 0040B48C B9 10 00 00 00 mov ecx,10h
- 0040B491 B8 CC CC CC CC mov eax,0CCCCCCCCh
- 0040B496 F3 AB rep stos dword ptr [edi]
- 13: test(argc);
- 0040B498 8B 45 08 mov eax,dword ptr [ebp+8]
- 0040B49B 50 push eax
- 0040B49C E8 69 5B FF FF call @ILT+5(test) (0040100a)
- 0040B4A1 83 C4 04 add esp,4
- 14:
- 15:
- 16: return 0;
- 0040B4A4 33 C0 xor eax,eax
- 17: }
- @ILT+0(_main):
- 00401005 E9 76 A4 00 00 jmp main (0040b480)
- 0040100A E9 01 00 00 00 jmp test (00401010)
- 0040100F CC int 3
- void test(int a)
- 7: {
- 00401010 55 push ebp
- 00401011 8B EC mov ebp,esp
- 00401013 83 EC 44 sub esp,44h
- 00401016 53 push ebx
- 00401017 56 push esi
- 00401018 57 push edi
- 00401019 8D 7D BC lea edi,[ebp-44h]
- 0040101C B9 11 00 00 00 mov ecx,11h
- 00401021 B8 CC CC CC CC mov eax,0CCCCCCCCh
- 00401026 F3 AB rep stos dword ptr [edi]
- 8: int c = a;
- 00401028 8B 45 08 mov eax,dword ptr [ebp+8]
- 0040102B 89 45 FC mov dword ptr [ebp-4],eax
- 9: }
- 0040102E 5F pop edi
- 0040102F 5E pop esi
- 00401030 5B pop ebx
- 00401031 8B E5 mov esp,ebp
- 00401033 5D pop ebp
- 00401034 C3 ret
1、main函數首先是將基址指針寄存器入棧,再將堆棧指針寄存器賦值給基址指針寄存器。
- 0040B480 55 push ebp
- 0040B481 8B EC mov ebp,esp
2、將ESP下移40H+0H(預留40H,局部變量0H,爲何預留40H暫時沒有搞清楚),用來存儲局部變量。
- 0040B483 83 EC 40 sub esp,40h
3、將寄存器入棧,用於函數返回時恢復現場。
- 0040B486 53 push ebx
- 0040B487 56 push esi
- 0040B488 57 push edi
4、循環初始化局部變量部分內存爲0xCCCCCCCC。
- 0040B489 8D 7D C0 lea edi,[ebp-40h]
- 0040B48C B9 10 00 00 00 mov ecx,10h
- 0040B491 B8 CC CC CC CC mov eax,0CCCCCCCCh
- 0040B496 F3 AB rep stos dword ptr [edi]
5、入參入棧。
- 0040B498 8B 45 08 mov eax,dword ptr [ebp+8]
- 0040B49B 50 push eax
6、調用test函數。ILT爲 Incremental Linking Table 的縮寫,關閉Link Incrementally後,編譯器就不會產生ILT。ILT的作用是減少debug版的編譯時間。
- 0040B49C E8 69 5B FF FF call @ILT+5(test) (0040100a)
7、test函數保存ebp、預留局部變量空間並初始化、保留現場。同main函數。
- 00401010 55 push ebp
- 00401011 8B EC mov ebp,esp
- 00401013 83 EC 44 sub esp,44h
- 00401016 53 push ebx
- 00401017 56 push esi
- 00401018 57 push edi
- 00401019 8D 7D BC lea edi,[ebp-44h]
- 0040101C B9 11 00 00 00 mov ecx,11h
- 00401021 B8 CC CC CC CC mov eax,0CCCCCCCCh
- 00401026 F3 AB rep stos dword ptr [edi]
8、int c = a; ebp+4存的是函數調用前的ebp。ebp+8是入參a,ebp-4是局部變量c。
- 00401028 8B 45 08 mov eax,dword ptr [ebp+8]
- 0040102B 89 45 FC mov dword ptr [ebp-4],eax
9、test執行完成,恢復現場。
- 0040102E 5F pop edi
- 0040102F 5E pop esi
- 00401030 5B pop ebx
- 00401031 8B E5 mov esp,ebp
- 00401033 5D pop ebp
- 00401034 C3 ret
10、釋放入參佔用的棧空間。
- 0040B4A1 83 C4 04 add esp,4
11、置返回值爲0。
- 0040B4A4 33 C0 xor eax,eax
12、main函數恢復堆棧返回。
- 0040B4A6 5F pop edi
- 0040B4A7 5E pop esi
- 0040B4A8 5B pop ebx
- 0040B4A9 83 C4 40 add esp,40h
- 0040B4AC 3B EC cmp ebp,esp
- 0040B4AE E8 0D 00 00 00 call __chkesp (0040b4c0)
- 0040B4B3 8B E5 mov esp,ebp
- 0040B4B5 5D pop ebp
- 0040B4B6 C3 ret