您的位置:首页 > 其它

转 函数调用约定

2011-11-19 22:51 239 查看
谈到函数,一般首先要分析一下各种函数调用约定,比如_cdecl、 _stdcall等。这两种调用约定调用时都是最右侧的参数先进栈,栈最上面的就是函数的第一个参数。不同之处在于,_cdecl由调用者清理参数占用的 栈空间,而_stdcall由被调用者清理参数占用的栈空间。很明显,对于接受可变参数的函数,如printf,被调用函数是无法知道到底有几个参数的, 所以只能采用由调用者清理参数栈的方式。_stdcall调用方式生成的代码会小一点。 下面的分析采用VC++6.0进行。

一.函数内部的汇编代码

view plainvoid func()
{
}

int main()
{
func();
return 0;
}
对应的汇编代码是:

1: void func()
2: {
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,40h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-40h]
0040102C mov ecx,10h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
3: }
00401038 pop edi
00401039 pop esi
0040103A pop ebx
0040103B mov esp,ebp
0040103D pop ebp
0040103E ret

5: int main()
6: {
00401050 push ebp
00401051 mov ebp,esp
00401053 sub esp,40h
00401056 push ebx
00401057 push esi
00401058 push edi
00401059 lea edi,[ebp-40h]
0040105C mov ecx,10h
00401061 mov eax,0CCCCCCCCh
00401066 rep stos dword ptr [edi]
7: func();
00401068 call @ILT+0(func) (00401005)
8: return 0;
0040106D xor eax,eax
9: }
0040106F pop edi
00401070 pop esi
00401071 pop ebx
00401072 add esp,40h
00401075 cmp ebp,esp
00401077 call __chkesp (00401090)
0040107C mov esp,ebp
0040107E pop ebp
0040107F ret

一般来说,函数开头的代码如下:

push ebp //保存ebp
mov ebp,esp //将esp的值送ebp,在函数内部可能还会使用push、pop等操作,这时esp的值会不断变化,如果采用esp来寻址局 部变量或者参数的话,可能要不断修正偏移量,而采用ebp寻址变量就方便很多。当然,这样会浪费一个寄存器,编译器可以优化。
sub esp,40h //为局部变量分配空间,这里的40h大小是vc默认分配的。

而结尾代码如下:

mov esp,ebp //恢复esp,其实相当于把栈里局部变量的空间回收了
pop ebp
ret

可以看到,即使函数没有定义任何局部变量,编译器仍然为我们非配了0x40大小的空间,并全部初始化0xcccccccc。有时候我们会见到烫烫€这样的字符串信息,就是这部分内存在作怪。

二 带参数和局部变量的函数

view plain#include <cstdio>
void func(int a,int b)
{
int m = a;
int n = b;
}

int main()
{
func(16,32);
return 0;
}
反汇编代码:

main中调用func的代码是:

00401088 push 20h //32压栈
0040108A push 10h //16压栈
0040108C call @ILT+10(func) (0040100f)
00401091 add esp,8 //调用完毕后清理参数占用的栈空间,这里采用的是_cdecl调用约定。

再看func的代码:

2: void func(int a,int b)
3: {
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,48h //默认分配0x40,现在有两个局部int类型变量,空间大小增大8
……
4: int m = a;
00401038 mov eax,dword ptr [ebp+8]
0040103B mov dword ptr [ebp-4],eax
5: int n = b;
0040103E mov ecx,dword ptr [ebp+0Ch]
00401041 mov dword ptr [ebp-8],ecx
6: }
……
00401047 mov esp,ebp
00401049 pop ebp
0040104A ret

函数中通过ebp加偏移量的方式来寻址局部变量,很容易看出,栈布局如下图所示:


图中,地址从上到下增加。因为这里是near调用,所以没有保存ECS寄存器的内容。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: