目的: 本文要做的事就是通过一个最简单的程序学习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
转载于:https://blog.51cto.com/whocare/849466