分析函数的调用过程,以一段代码为例:
#include<stdio.h> int sum(int a,int b) { int tmp=0; tmp=a+b; return tmp; } int main() { int a=10; int b=20; int ret=0; ret=sum(a,b); printf("ret=%d\n",ret); return 0; }反汇编如下所示:
解析后的反汇编:
画出在栈中的整个过程:
总结:
总结一下吧~
1、函数的运行都是在栈上开辟内存。
2、栈是通过esp(栈顶指针)、ebp(栈底指针)两个指针来标识的。
3、对于栈上的访问都是通过栈底指针的偏移来访问的。
4、在call一个函数时,有两件事情要做:先将调用函数的下一行指令的地址压入栈中;再进行跳转。
5、在函数调用时检查函数是否申明、函数名是否相同、函数的参数列表是否匹配、函数的返回值多大。
①如果 【函数的返回值<=4个字节】,则返回值通过寄存器eax带回。
②如果 【4<函数的返回值<=8个字节】,则返回值通过两个寄存器eax和edx带回。
③如果 【函数的返回值>8个字节】,则返回值通过产生的临时量带回。
6、函数结束ret指令干了两件事:先出栈;再将出栈的值放到CPU的PC寄存器中。因为PC寄存器中永远放的是下一次执行指令的地址,所以就顺理成章的在函数调用完之后依旧接着原来的代码继续执行。
函数调用:
#include<stdio.h> int sum(int a,int b) { /* push ebp mov ebp,esp sub esp,44h push ebx push esi push edi lea edi,[ebp-44h] mov ecx,11h mov eax,0xccccccch rep stos ===>[esp,ebp]=0xcccccccc */ int tmp=0;//mov dword ptr[ebp-4],0 tmp=a+b; /* mov eax,dword ptr[ebp+8] add eax,dword ptr[ebp+0ch] mov dword ptr[ebp-4],eax */ return tmp;//mov dword ptr[ebp-4],eax } /* mov eax,dword ptr[ebp-4] mov esp,ebp pop ebp ret */ int main() { /* push ebp mov ebp,esp sub esp,44h push ebx push esi push edi lea edi,[ebp-44h] mov ecx,11h mov eax,0xccccccch rep stos ===>[esp,ebp]=0xcccccccc */ int a=10;//mov dword ptr[ebp-4],0Ah int b=20;//mov dword ptr[ebp-8],14h int ret=0;//mov dword ptr[ebp-0Ch],0 ret=sum(a,b); /* mov eax,ptr[ebp-8] push eax mov ebx,ptr[ebp-4] push ebx push ecx call sum add esp,8 mov dword ptr[ebp-0ch],eax */ printf("ret=%d\n",ret); return 0; }简单过程:
压入实参 形参初始化 自右向左压下一行指令地址压调用方栈底地址跳转到被调用方函数栈帧被调用方函数活动开辟空间
