程序的执行过程可看作连续的函数调用。当一个函数执行完毕时,程序要回到调用指令的下一条指令(紧接call指令)处继续执行。
函数调用过程通常使用堆栈实现,每个用户态进程对应一个调用栈结构(callstack)。编译器使用堆栈传递函数参数、保存返回地址、临时保存寄存器原有值(即函数调用的上下文)以备恢复以及存储本地局部变量就是所谓的保护现场。
为了浅显易懂,直接抛代码看反汇编~
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int sum = 0;
int a = 2;
int b = 3;
sum = Add(a, b);
return 0;
}
我们一步一步通过反汇编来剖析调用过程:
反汇编码:
int sum = 0;
0034532E C7 45 F8 00 00 00 00 mov dword ptr [sum],0
int a = 2;
00345335 C7 45 EC 02 00 00 00 mov dword ptr [a],2
int b = 3;
0034533C C7 45 E0 03 00 00 00 mov dword ptr [b],3
分析:
接下来看到调用Add函数:
sum = Add(a, b);
00345343 8B 45 E0 mov eax,dword ptr [b]
00345346 50 push eax
00345347 8B 4D EC mov ecx,dword ptr [a]
0034534A 51 push ecx
0034534B E8 BE BD FF FF call Add (034110Eh)
00345350 83 C4 08 add esp,8
00345353 89 45 F8 mov dword ptr [sum],eax
具体分析:
函数出栈的反汇编:
return z;
010637BE 8B 45 F8 mov eax,dword ptr [z]
}
010637C1 5F pop edi //edi返回 弹出栈
010637C2 5E pop esi //esi返回
010637C3 5B pop ebx //esi返回
010637C4 8B E5 mov esp,ebp //将原来的ebp的值给esp
010637C6 5D pop ebp
//弹出ebp,使得ebp的值指向之前保存好的main函数的ebp,esp向下指向保存的地址处,此时esp和ebp维护到main函数的栈帧
010637C7 C3 ret
//隐藏的pop,使Add函数栈底的地址弹出。
下面是函数出栈的图解解析(黑色线条标记)
回到call指令的下一条指令
00345350 83 C4 08 add esp,8 //将形参返回弹出栈
00345353 89 45 F8 mov dword ptr [sum],eax
至此,esp,ebp维护到main函数的栈帧,Add函数出栈。。。