Feb 10, 2008

函数调用过程

一.环境:

x86/WinXP/VC 6.0

二.用例:
int swap(int a, int b)
{
int v;
v = a;
a = b;
b = v;
return v;
}

void main(void)
{
int a = 7;
int b = 10;
int c = 0;
c = swap(a,b);
return;
}

三.分析:
1: int swap(int a, int b)
2: {
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,44h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-44h]
0040102C mov ecx,11h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
3: int v;
4: v = a;
00401038 mov eax,dword ptr [ebp+8]
0040103B mov dword ptr [ebp-4],eax
5: a = b;
0040103E mov ecx,dword ptr [ebp+0Ch]
00401041 mov dword ptr [ebp+8],ecx
6: b = v;
00401044 mov edx,dword ptr [ebp-4]
00401047 mov dword ptr [ebp+0Ch],edx
7: return v;
0040104A mov eax,dword ptr [ebp-4]
8: }
0040104D pop edi
0040104E pop esi
0040104F pop ebx
00401050 mov esp,ebp
00401052 pop ebp
00401053 ret

swap函数内部参数处理:
1.将基址指针EBP压栈;
2.将堆栈指针ESP拷贝一份到EBP中;
3.将ESP值减0x44(为了将这68个字节填充为0xCC);
4.将EBX/ESI/EDI压栈;
5.将EBP-0x44的地址偏移量存到目标地址指针EDI;
6.将计数器ECX置为0x11(即17个整数);
7.将EDI所开始的大小为17的内存空间填充为0xCCCCCCCC;

8.取出堆栈中被压的a的值(EBP+8所存的值)放到EAX中;
9.将EAX中的值(参数a的值)赋给到v;
10.取出堆栈中被压的b的值(EBP+0CH所存的值)放到ECX中;
11.将ECX中的值(参数b的值)赋给到a;
12.取出堆栈中被压的b的值(EBP-4所存的值)放到EDX中;
13.将EDX中的值(变量v的值)赋给到a;

14.将返回值(v的值)拷贝到EAX中;

15.恢复EDI/ESI/EBX;
16.恢复ESP为EBP(即进入swap时的ESP);
17.恢复EBP为调用swap前的值;
18.swap函数返回;


10: void main(void)
11: {
00401070 push ebp
00401071 mov ebp,esp
00401073 sub esp,4Ch
00401076 push ebx
00401077 push esi
00401078 push edi
00401079 lea edi,[ebp-4Ch]
0040107C mov ecx,13h
00401081 mov eax,0CCCCCCCCh
00401086 rep stos dword ptr [edi]
12: int a = 7;
00401088 mov dword ptr [ebp-4],7
13: int b = 10;
0040108F mov dword ptr [ebp-8],0Ah
14: int c = 0;
00401096 mov dword ptr [ebp-0Ch],0
15: c = swap(a,b);
0040109D mov eax,dword ptr [ebp-8]
004010A0 push eax
004010A1 mov ecx,dword ptr [ebp-4]
004010A4 push ecx
004010A5 call @ILT+5(_swap) (0040100a)
004010AA add esp,8
004010AD mov dword ptr [ebp-0Ch],eax
16: return;
17: }
004010B0 pop edi
004010B1 pop esi
004010B2 pop ebx
004010B3 add esp,4Ch
004010B6 cmp ebp,esp
004010B8 call __chkesp (004010e0)
004010BD mov esp,ebp
004010BF pop ebp
004010C0 ret


15: c = swap(a,b);
0040109D mov eax,dword ptr [ebp-8]
004010A0 push eax
004010A1 mov ecx,dword ptr [ebp-4]
004010A4 push ecx
004010A5 call @ILT+5(_swap) (0040100a)
004010AA add esp,8
004010AD mov dword ptr [ebp-0Ch],eax
分析函数swap调用过程:
1.将参数值b拷到EAX;
2.将EAX值压栈;
3.将参数值a拷到ECX;
4.将ECX值压栈;
5.CALL swap函数
6.将堆栈指针加8,丢弃堆栈中a,b的值;
7.将EAX中的返回值拷贝到变量c;

调用swap时的堆栈情况:

0012FEC8 | EDI |<-- ESP (执行到 v = a语句时堆栈指针)
0012FECC | ESI |
0012FED0 | EBX |
0012FED4 | 0xCCCCCCCC |
| . |
| . |
| . |
0012FF14 | 0xCCCCCCCC |
0012FF18 | EBP |<-- EBP (执行到 v = a语句时堆栈指针)
0012FF1C | 0x004010AA | 执行完调用swap后的返回地址
0012FF20 | ECX | a的值
0012FF24 | EAX | b的值


四.总结:

1.实参压栈过程是从右至左,然后压入函数调用后的返回地址;
2.被调用的函数从堆栈中取实参值;
3.如果采用编译器进行优化,那么被调用的函数内部是从寄存器还是从堆栈中取实参值?具体情况根据CPU架构和参数个数有关(可以继续分析哦!);
4.如果函数参数个数超过较多(如果超过5个参数),函数参数如何传递呢?
5.函数的返回值一般会存在一个寄存器,如EAX.

No comments: