通过分析函数调用的汇编,解析一下时如何进行函数调用
示例代码
#include <iostream>
using namespace std;
int add(int a, int b)
{
int d = 0;
d = a + b;
return d;
}
int main(int argc, char* argv[])
{
int a = 1;
int b = 2;
int c = 0;
c = add(a, b);
return 0;
}
main函数的主要部分汇编代码
int main(int argc, char* argv[])
{
00642040 push ebp
00642041 mov ebp,esp
00642043 sub esp,0E4h
00642049 push ebx
0064204A push esi
0064204B push edi
0064204C lea edi,[ebp-24h]
0064204F mov ecx,9
00642054 mov eax,0CCCCCCCCh
00642059 rep stos dword ptr es:[edi]
0064205B mov ecx,offset _A7BC9FD1_source@cpp (064F066h)
00642060 call @__CheckForDebuggerJustMyCode@4 (0641393h)
int a = 1;
00642065 mov dword ptr [a],1
int b = 2;
0064206C mov dword ptr [b],2
int c = 0;
00642073 mov dword ptr [c],0
c = add(a, b);
0064207A mov eax,dword ptr [b]
0064207D push eax
0064207E mov ecx,dword ptr [a]
00642081 push ecx
00642082 call add (0641451h)
00642087 add esp,8
0064208A mov dword ptr [c],eax
return 0;
0064208D xor eax,eax
}
0064208F pop edi
00642090 pop esi
00642091 pop ebx
00642092 add esp,0E4h
00642098 cmp ebp,esp
0064209A call __RTC_CheckEsp (064129Eh)
0064209F mov esp,ebp
006420A1 pop ebp
006420A2 ret
在main函数入口处打个断点,看一下当前各个寄存器的状态
执行以下语句后:
00642040 push ebp //将调用函数的栈底压栈
00642041 mov ebp,esp //将栈底指针指向调用函数的栈底
00642043 sub esp,0E4h //开辟0xE4的地址空间给main
栈地址变成这样:
执行这几条语句后,EBP指向原先的栈顶,ESP重新开辟一块地址0xE4H并指向新的栈顶。
0064204C lea edi,[ebp-24h]
0064204F mov ecx,9
00642054 mov eax,0CCCCCCCCh
00642059 rep stos dword ptr es:[edi]
上面把基址以上的0x24h用来存放临时变量,并初始化为0xCCCC。
接下来对临时变量进行初始化。
调用add函数
执行call 指令时,后面的0x0641451H其实时JMP指令的地址
JMP指令后的0x0641F20H时add函数的地址。
同时我们看到这个时候ESP向栈顶压入4个字节,通过下图发现,是将add函数返回后下一条指令地址(0x00642087)(EIP存储着下一条指令的地址)压栈
add函数的调用
int add(int a, int b)
{
push ebp
mov ebp,esp
sub esp,0CCh
push ebx
push esi
push edi
lea edi,[ebp-0Ch]
mov ecx,3
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
mov ecx,offset _A7BC9FD1_source@cpp (047F066h)
call @__CheckForDebuggerJustMyCode@4 (0471393h)
int d = 0;
mov dword ptr [d],0
d = a + b;
mov eax,dword ptr [a]
add eax,dword ptr [b]
mov dword ptr [d],eax
return d;
mov eax,dword ptr [d]
}
pop edi
pop esi
pop ebx
add esp,0CCh
cmp ebp,esp
call __RTC_CheckEsp (047129Eh)
mov esp,ebp
pop ebp
ret
以下初始化d,同时可以看到这个时候取的a和b的地址正是入栈时的地址
以上是整个加法运算过程,同时可以发现,执行return时,会将d的值赋给eax
以下执行返回操作
返回部分
执行ret后,调用栈又回到main函数:
00642087 add esp,8 //将参数出栈
0064208A mov dword ptr [c],eax //将返回值赋值给c
总结:
调用前
1)将参数从右到左将参数压入栈
2)将函数返回后的下一个指令地址入栈
3)先跳到JMP指令,根据JMP指令跳转到函数地址
调用中
1)将调用函数的基址指针入栈,将当前栈顶做为新的基址,同时开辟新的地址给当前函数操作
2)初始化一片地址给临时变量
3)进行函数操作
4)返回时,将操作结果存入相应的寄存器
5)恢复基址指针
调用结束
1)将参数出栈
2)将返回寄存器的值赋值给对应的变量