我们常常会使用函数调用来实现某种操作,只知道从调用函数跳到被调函数,被调函数执行完又返回到主调函数,可能会有以下的疑问: 1.形参开辟空间吗?
2.参数由谁来开辟空间
3.形参的入栈顺序
4.返回值由谁传出来
5.怎么回到调用函数
6.如何知道下一行指令
为了解决以上的问题,以一段简单的代码为例,以下是代码的反汇编:
调用函数:
mov 指移动数值
lea 指移动地址
ebp 指向栈底指针
esp 指向栈顶指针
eax ebx ecx edx 都是存储数据的寄存器
先从这部分汇编指令开始,
int rt = 0;
0123141E mov dword ptr [rt],0
int a = 10;
01231425 mov dword ptr [a],0Ah
int b = 20;
0123142C mov dword ptr [b],14h
这三个汇编指令都是对变量进行初始化,如 Int rt = 0;是根据栈底指针的偏移量确定存储0这个数字的位置
0123141E mov dword ptr [rt],0
0123141E 是指令的虚拟地址,
mov是对数值的移动,
dword ptr [rt] 实际上是dword ptr [ebp-4](栈底指针的偏移量)
简单来讲,意思就是将数值0移动到为ebp-4的地址;int a = 10;与int b = 20亦如此入栈
rt = Sum(a,b);
01231433 mov eax,dword ptr [b]
01231436 push eax
01231437 mov ecx,dword ptr [a]
0123143A push ecx
0123143B call Sum (012311C7h)
01231440 add esp,8
01231443 mov dword ptr [rt],eax
将b的值(ptr[ebp-12])保存在eax寄存器中,然后压栈,将a的值(ptr[ebp-8])保存在ecx的寄存器中,将ecx压栈;我们发现这里的a和b也就是形参,可知,参数是在主调函数中开辟空间的
call:静态相对位移,call是跳转到被调函数的指令地址包含以下两个步骤:
1.将下一条指令压栈;
2.跳转到被调函数;Sum(012311C7h)括号里就是被调函数的指令地址
被调函数
会注意到被调函数中的前面一部分和主调函数中的除了个别数据外,基本一致;其实每个函数前面都会这么一个部分,现在来看这些指令到底是做什么的;
012313D0 push ebp
012313D1 mov ebp,esp
012313D3 sub esp,0C0h
将主调函数的栈底指针的值压栈,然后将ebp移动到esp的位置;sub意思是让esp减等0c0,也就是为新栈开辟了192个字节;这三步主要就是先将主调函数的栈顶指针入栈,然后为新栈开辟空间;
012313D9 push ebx
012313DA push esi
012313DB push edi
分别将寄存器ebx、esi、edi依次入栈
012313DC lea edi,[ebp-0C0h]
使edi指向ebp-0C0h的位置;
012313E2 mov ecx,30h
012313E7 mov eax,0CCCCCCCCh
012313EC rep stos dword ptr es:[edi]
这三行指令的意思就是循环30h十进制是48次(48*4就是新栈开辟的空间大小),从栈顶到栈底将数据赋值为0xcccccccch
012313EE mov eax,dword ptr [a]
012313F1 add eax,dword ptr [b]
在ptr[ebp+8]中将a的值放在寄存器eax中,add是寄存器eax等于eax中的值加上ptr[ebp+c]中的值,也就是计算a+b的值,将结果放在eax中,所以结果是由寄存器带出来的;如果返回值是小于等于四个字节,则由一个寄存器带出来,如果大于4小于8的字节,由两个字节带出来,大于8个字节时,在调用方开辟一个返回值临时量的空间,将return回来的值循环拷贝在临时量区;
销毁栈
013613F4 pop edi
013613F5 pop esi
013613F6 pop ebx
013613F7 mov esp,ebp
013613F9 pop ebp
先将三个寄存器出栈,将esp移动到ebp,这时esp和ebp都是指向新栈的栈底指针,最后将ebp出栈,ebp就再次指向主调函数的栈底;
回到主调函数中 01361440 add esp,8
01361443 mov dword ptr [rt],eax esp指向的还是新栈的栈底,所以add就是将esp向下移动两个字节,指向主调函数的栈顶 将寄存器eax的值赋给rt(ptr[ebp-4])的位置
整个过程就是这个样子了,再回到开始的几个问题,在这个过程中已经解决了
1.形参开辟空间吗?形参开辟空间
2.参数由谁来开辟空间?参数由主调函数开辟
3.形参的入栈顺序?由右向左
4.返回值由谁传出来?由寄存器传出来
5.怎么回到调用函数?将主调函数的栈底指针的值入栈到被调函数中,被调函数的栈底指针释放后,回到主调函数中
6.如何知道下一行指令?call指令的作用就是将下一行指令地址入栈