Author:bakari Date:2012.11.2
1、参数传递问题:
< 1 >、堆栈传参
< 2 >、寄存器传参(利用通用寄存器进行函数参数传递的方法)
< 3 >、全局变量或静态变量传参
2、 Call Convention(函数调用约定)
< 1 >、_cdecl
a、 参数从右向左压入堆栈
b、 函数被调用者修改堆栈
c、 在win32应用程序里,宏APIENTRY,WINAPI,都表示_stdcall,非常常见.
d、 C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。
< 2 >、_stdcall
a、 压栈方式与_cdecl一样,与之不一样的是堆栈的平衡不是由函数调用者完成,而是自身完成,在退出时自己清空堆栈。
b、 此种方式在函数返回是以 ret 8 指令来平衡堆栈,此处:ret 8 = add esp , 8。
< 3 >、上两种方式最为常用,此外还有fastcall ,thiscall, naked call,_pascal等 _pascal 入栈方式是从左到右。
下面通过一些例子来深入理解。
3、 跟踪汇编代码看函数参数的调用机制 我们看这样一个简单的函数,
编译器翻译的汇编指令如下:
< 1 >、void Test1(){}
代码语言:javascript复制 1 void Test1()
2 {
3 013516E0 push ebp
4 013516E1 mov ebp,esp
5 013516E3 sub esp,0C0h
6 013516E9 push ebx
7 013516EA push esi
8 013516EB push edi
9 013516EC lea edi,[ebp-0C0h]
10 013516F2 mov ecx,30h ;每次移四个字节,30h * 4 = 0C0h
11 013516F7 mov eax,0CCCCCCCCh
12 013516FC rep stos dword ptr es:[edi] ;将开辟的内存赋值为cc
13 }
14 013516FE pop edi
15 013516FF pop esi
16 01351700 pop ebx
17 01351701 mov esp,ebp
18 01351703 pop ebp
19 01351704 ret ;--->编译器默认为_cdecl
20
据此画出内存布局图为:
< 2 >、有参数的情况
代码语言:javascript复制 1 int _cdecl Test (int i, int j)
2 {
3 return j i ;
4 }
5
6 int _cdecl Test (int i, int j)
7 {
8 01311690 push ebp
9 01311691 mov ebp,esp
10 01311693 sub esp,0C4h
11 01311699 push ebx
12 0131169A push esi
13 0131169B push edi
14 0131169C lea edi,[ebp-0C4h]
15 013116A2 mov ecx,31h ;31h * 4 = 0C4h
16 013116A7 mov eax,0CCCCCCCCh
17 013116AC rep stos dword ptr es:[edi] ;和上面无参的类型一样
18 return j i ;
19 013116AE mov eax,dword ptr [j]
20 013116B1 add eax,dword ptr [i]
21 013116B4 mov dword ptr [ebp-0C4h],eax
22 013116BA mov ecx,dword ptr [i]
23 013116BD add ecx,1
24 013116C0 mov dword ptr [i],ecx
25 013116C3 mov eax,dword ptr [ebp-0C4h]
26 }
27 013116C9 pop edi
28 013116CA pop esi
29 013116CB pop ebx
30 013116CC mov esp,ebp
31 013116CE pop ebp
32 013116CF ret
33
34 00D21D88 push 2 ;在调用函数之前先将参数从右往左压入堆栈
35 00D21D8A mov eax,dword ptr [i]
36 00D21D8D push eax
37 00D21D8E call Test (0D211F4h) ;函数调用
38 00D21D93 add esp,8 ;平衡堆栈
39 00D21D96 mov dword ptr [i],eax
40
内存布布局如下:
4、 编写裸函数(不让系统加汇编的代码,而是人为的加上去)
比如:
代码语言:javascript复制 1 int _declspec (naked) MyFunc()
2 {
3 _asm {
4 push ebp
5 mov ebp, esp
6 sub esp, 0C0h
7 push ebx
8 push esi
9 push edi
10 lea edi, dword ptr [ebp - 0C0h]
11 mov ecx, 30h
12 mov eax, 0cccccccch
13 rep stos dword ptr [edi]
14 }
15
16 _asm {
17 mov eax, 8 ;返回值为8
18 }
19
20 _asm {
21 pop edi
22 pop esi
23 pop ebx
24 mov esp, ebp ;平衡堆栈
25 pop ebp
26 ret ;记得一定要返回
27 }
28 }
29
在main函数调用的结果printf("%dn", MyFunc());
练习:
< 1 >、无参数的情况(不在堆栈上展开)
代码语言:javascript复制 1 void _declspec (naked) BlankFunc(void)
2 {
3 _asm {
4 push ebp
5 mov ebp, esp
6 pushad
7 popad
8 mov esp, ebp
9 pop ebp
10 }
11 }
12
< 2 >、有参数的情况(在堆栈上展开)
代码语言:javascript复制 1 int Nest (int a, int b)
2 {
3 int nValue_1 = a;
4 int nValue_2 = b;
5 return nValue_1 nValue_2;
6 }
7 int _declspec (naked) myFunc(int a, int b)
8 {
9 _asm {
10 push ebp
11 mov ebp, esp
12 sub esp, 8
13 push edi
14 push ebx
15 push ecx
16 lea edi, dword ptr [ebp - 8]
17 mov ecx, 2
18 mov eax, 0cccccccch
19 rep stos dword ptr [edi]
20 }
21 _asm {
22 mov eax, dword ptr[ebp 8] //有了上面的知识,这里就不难理解了。
23 mov dword ptr [ebp - 4], eax
24 mov ebx, dword ptr[ebp 12]
25 mov dword ptr [ebp - 8], ebx
26 add eax, ebx
27 }
28 _asm {
29 pop edi
30 pop ebx
31 pop ecx
32 mov esp, ebp
33 pop ebp
34 ret
35 }
36 }
37