您的位置:首页 > 编程语言

大神洗礼第四讲——函数相关及编程技巧

2012-11-02 22:47 225 查看
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(){}

void Test1()
{
013516E0  push        ebp
013516E1  mov         ebp,esp
013516E3  sub         esp,0C0h
013516E9  push        ebx
013516EA  push        esi
013516EB  push        edi
013516EC  lea         edi,[ebp-0C0h]
013516F2  mov         ecx,30h ;每次移四个字节,30h * 4 = 0C0h
013516F7  mov         eax,0CCCCCCCCh
013516FC  rep stos    dword ptr es:[edi] ;将开辟的内存赋值为cc
}
013516FE  pop         edi
013516FF  pop         esi
01351700  pop         ebx
01351701  mov         esp,ebp
01351703  pop         ebp
01351704  ret    ;--->编译器默认为_cdecl


据此画出内存布局图为:



< 2 >、有参数的情况

int _cdecl Test (int i, int j)
{
return j + i ++;
}

int _cdecl Test (int i, int j)
{
01311690  push        ebp
01311691  mov         ebp,esp
01311693  sub         esp,0C4h
01311699  push        ebx
0131169A  push        esi
0131169B  push        edi
0131169C  lea         edi,[ebp-0C4h]
013116A2  mov         ecx,31h ;31h * 4 = 0C4h
013116A7  mov         eax,0CCCCCCCCh
013116AC  rep stos    dword ptr es:[edi] ;和上面无参的类型一样
return j + i ++;
013116AE  mov         eax,dword ptr [j]
013116B1  add         eax,dword ptr [i]
013116B4  mov         dword ptr [ebp-0C4h],eax
013116BA  mov         ecx,dword ptr [i]
013116BD  add         ecx,1
013116C0  mov         dword ptr [i],ecx
013116C3  mov         eax,dword ptr [ebp-0C4h]
}
013116C9  pop         edi
013116CA  pop         esi
013116CB  pop         ebx
013116CC  mov         esp,ebp
013116CE  pop         ebp
013116CF  ret

34  00D21D88  push        2    ;在调用函数之前先将参数从右往左压入堆栈
35  00D21D8A  mov         eax,dword ptr [i]
36  00D21D8D  push        eax
00D21D8E  call        Test (0D211F4h) ;函数调用
38  00D21D93  add         esp,8           ;平衡堆栈
39  00D21D96  mov         dword ptr [i],eax


内存布布局如下:



4、 编写裸函数(不让系统加汇编的代码,而是人为的加上去)

比如:

int  _declspec (naked) MyFunc()
{
_asm {
push ebp
mov ebp, esp
sub esp, 0C0h
push ebx
push esi
push edi
lea edi, dword ptr [ebp - 0C0h]
mov ecx, 30h
mov eax, 0cccccccch
rep stos dword ptr [edi]
}

_asm {
mov eax, 8      ;返回值为8
}

_asm {
pop edi
pop esi
pop ebx
mov esp, ebp  ;平衡堆栈
pop ebp
ret            ;记得一定要返回
}
}


在main函数调用的结果printf("%d\n", MyFunc());



练习:

< 1 >、无参数的情况(不在堆栈上展开)

void _declspec (naked) BlankFunc(void)
{
_asm {
push ebp
mov ebp, esp
pushad
popad
mov esp, ebp
pop ebp
}
}


< 2 >、有参数的情况(在堆栈上展开)

int Nest (int a, int b)
{
int nValue_1 = a;
int nValue_2 = b;
return nValue_1 + nValue_2;
}
int _declspec (naked) myFunc(int a, int b)
{
_asm {
push ebp
mov ebp, esp
sub esp, 8
push edi
push ebx
push ecx
lea edi, dword ptr [ebp - 8]
mov ecx, 2
mov eax, 0cccccccch
rep stos dword ptr [edi]
}
_asm {
mov eax, dword ptr[ebp + 8]  //有了上面的知识,这里就不难理解了。
mov dword ptr [ebp - 4], eax
mov ebx, dword ptr[ebp + 12]
mov dword ptr [ebp - 8], ebx
add eax, ebx
}
_asm {
pop edi
pop ebx
pop ecx
mov esp, ebp
pop ebp
ret
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: