目录
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;
}
这个函数计算两个数的值。接下来我们通过反汇编,来看看函数被后的过程。
反汇编代码
#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
以上就是反汇编代码。现在我们一段一段的进行解读,去看看整个程序是怎么执行的。
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
这里要给读者朋友们简单说一下变量的含义。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
前四句话,它将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]
这一部分和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
这段代码非常有意思,请读者朋友们一定要注意。
首先,我们创建了变量 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
}
现在我们跳回到了该段的第一句话,现在esp + 8向高地址进发了。其目的就是消去了形参实例化的a,b。第二句话,我们将eax里保存的数据赋值移动给了c。最后,xor异或,自己和自己异或 其值变为0。
之后,我们出栈了 edi,esi,ebx。现在esp指向了最开始我们红色那个箭头 ebp – 0E4h。
然后,由于add操作,我们继续向高地址进发,到了我们最开始的地方。
现在,我们到了cmp指令,cmp是比较指令。它的操作是拿第一个数减去第二个数,如果相减为ZF=0说明相等。但是cmp并不会真的减。到此位置main()函数就真的执行完毕,然后esp,ebp。回到了原来的位置。至于后面的函数调用,并不需要深究。至此我们就看到了在汇编上函数如何实现的
谢谢您的阅读,如有问题,请多指教。