C语言的函数调用过程
先上一段代码
#include<stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = Add(a, b);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这个函数计算两个数的值。接下来我们通过反汇编,来看看函数被后的过程。
反汇编代码
#include <stdio.h>
int main()
{
01151720 push ebp
01151721 mov ebp,esp
01151723 sub esp,0E4h
01151729 push ebx
0115172A push esi
0115172B push edi
0115172C lea edi,[ebp-0E4h]
01151732 mov ecx,39h
01151737 mov eax,0CCCCCCCCh
0115173C rep stos dword ptr es:[edi]
int a = 10;
0115173E mov dword ptr [a],0Ah
int b = 20;
01151745 mov dword ptr [b],14h
int c = Add(a, b);
0115174C mov eax,dword ptr [b]
0115174F push eax
01151750 mov ecx,dword ptr [a]
01151753 push ecx
01151754 call _Add (01151104h)
01151759 add esp,8
0115175C mov dword ptr [c],eax
return 0;
0115175F xor eax,eax
}
01151761 pop edi
01151762 pop esi
01151763 pop ebx
01151764 add esp,0E4h
0115176A cmp ebp,esp
0115176C call __RTC_CheckEsp (01151122h)
01151771 mov esp,ebp
01151773 pop ebp
}
//Add()
#include<stdio.h>
int Add(int x, int y)
{
001016D0 push ebp
001016D1 mov ebp,esp
001016D3 sub esp,0CCh
001016D9 push ebx
001016DA push esi
001016DB push edi
001016DC lea edi,[ebp-0CCh]
001016E2 mov ecx,33h
001016E7 mov eax,0CCCCCCCCh
001016EC rep stos dword ptr es:[edi]
int z = 0;
001016EE mov dword ptr [z],0
z = x + y;
001016F5 mov eax,dword ptr [x]
001016F8 add eax,dword ptr [y]
001016FB mov dword ptr [z],eax
return z;
001016FE mov eax,dword ptr [z]
}
00101701 pop edi
00101702 pop esi
00101703 pop ebx
00101704 mov esp,ebp
00101706 pop ebp
00101707 ret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
以上就是反汇编代码。现在我们一段一段的进行解读,去看看整个程序是怎么执行的。
main()函数的创建
01151720 push ebp
01151721 mov ebp,esp
01151723 sub esp,0E4h
01151729 push ebx
0115172A push esi
0115172B push edi
0115172C lea edi,[ebp-0E4h]
01151732 mov ecx,39h
01151737 mov eax,0CCCCCCCCh
0115173C rep stos dword ptr es:[edi]
//分割线.........................
int a = 10;
0115173E mov dword ptr [a],0Ah
int b = 20;
01151745 mov dword ptr [b],14h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这里要给读者朋友们简单说一下变量的含义。ebp 和 esp 是两个通用寄存器。它们可以存储 立即数 和 内存地址。esi是源变址寄存器。edi是目的变址寄存器。
它们都可用来储存地址。
在main()函数之前有一个函数CRTStartUp,ebp为这个函数的栈底,esp为这个函数的栈顶。
接下来通过前两句代码mov 赋值移动,它们的状态变成了如下:
第三句话中,sub是减法指令,这句话使得esp向后移动了0E4h个单位。
接下来的三句话,是将ebx,esi,edi压入了栈当中。
第七句话,lea 的意思是load effective address,也就是将[ebp - 0E4h]的地址取出来,赋值给了edi。
第八句话,将39h赋值移动到ecx。第九句话,将内容赋值移动给eax。
第十句话,rep stos 是重复拷贝数据。
注意:
ecx寄存器是用来保存复制粘贴的次数,ecx是计数器。
eax寄存器是用来传送信息的,eax是累加器,通常用来存储数据。
rep 是重复上述指令。
stos 是将eax的值赋值移动给 es:[edi]这个地址的存储单元。
现在其中的空白部分被cc cc cc cc充满了。这里cc cc就是经常遇到的“烫烫”。
分割线
接下来的操作很好理解,其目的是将 a和b创建,并且压入栈中。
Add()函数的调用过程
int c = Add(a, b);
0115174C mov eax,dword ptr [b]
0115174F push eax
01151750 mov ecx,dword ptr [a]
01151753 push ecx
01151754 call _Add (01151104h)
01151759 add esp,8
0115175C mov dword ptr [c],eax
1
2
3
4
5
6
7
8
前四句话,它将a和b找了个寄存器存起来,与此同时,以函数参数列表倒着的方式压入栈中。
方便起见,下面的图将只截取上半部分。
接下来使用了call指令,这个指令是先将 指令 移动到下一句话,然后再操纵call 后面的内容。在call处按F11我们将跳转到Add()函数。
Add()函数
#include<stdio.h>
int Add(int x, int y)
{
001016D0 push ebp
001016D1 mov ebp,esp
001016D3 sub esp,0CCh
001016D9 push ebx
001016DA push esi
001016DB push edi
001016DC lea edi,[ebp-0CCh]
001016E2 mov ecx,33h
001016E7 mov eax,0CCCCCCCCh
001016EC rep stos dword ptr es:[edi]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
这一部分和main函数创建过程非常相似。所以用俩图来表示。
int z = 0;
001016EE mov dword ptr [z],0
z = x + y;
001016F5 mov eax,dword ptr [x]
001016F8 add eax,dword ptr [y]
001016FB mov dword ptr [z],eax
return z;
001016FE mov eax,dword ptr [z]
}
00101701 pop edi
00101702 pop esi
00101703 pop ebx
00101704 mov esp,ebp
00101706 pop ebp
00101707 ret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这段代码非常有意思,请读者朋友们一定要注意。
首先,我们创建了变量 ptr[z]。然后,将ptr[x]的值放在eax里边,将ptr[y]的值与eax相加后再放在eax里,将eax的值赋值移动到 ptr[z]中。最后,我们需要return z。这个地方编译器再次将ptr[z]的值赋值移动到了eax。这么做的原因是:ptr[x]、ptr[y]、ptr[z]将在函数完成后就会销毁,但是寄存器eax不会被销毁,所以在eax的值非常安全。
括号外的第四句话非常重要。它将销毁该函数的所有数据。
将ebp弹出后,该点只有一个指针esp。接下来执行ret,即 return 返回。返回到我们刚才call下面的地方。
main()函数的销毁
01151759 add esp,8
0115175C mov dword ptr [c],eax
return 0;
0115175F xor eax,eax
}
01151761 pop edi
01151762 pop esi
01151763 pop ebx
01151764 add esp,0E4h
0115176A cmp ebp,esp
0115176C call __RTC_CheckEsp (01151122h)
01151771 mov esp,ebp
01151773 pop ebp
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
现在我们跳回到了该段的第一句话,现在esp + 8向高地址进发了。其目的就是消去了形参实例化的a,b。第二句话,我们将eax里保存的数据赋值移动给了c。最后,xor异或,自己和自己异或 其值变为0。
之后,我们出栈了 edi,esi,ebx。现在esp指向了最开始我们红色那个箭头 ebp - 0E4h。
然后,由于add操作,我们继续向高地址进发,到了我们最开始的地方。
现在,我们到了cmp指令,cmp是比较指令。它的操作是拿第一个数减去第二个数,如果相减为ZF=0说明相等。但是cmp并不会真的减。到此位置main()函数就真的执行完毕,然后esp,ebp。回到了原来的位置。至于后面的函数调用,并不需要深究。至此我们就看到了在汇编上函数如何实现的