C语言是面向过程的计数机语言,其本质就是函数的调用,从汇编的角度深层次剖析函数之间的调用关系,将对C语言的理解更上一层楼。
先来看一段代码
#include<stdio.h>
int myfun(int x, int y)
{
int z = x + y;
return z;
}
int main()
{
int a = 0xAAAAAAAA;
int b = 0xBBBBBBBB;
int c = myfun(a, b);
printf("i am back \n");//查看调用函数是否返回
printf("ret=%d\n", c);
return 0;
}
这个实例实现了myfun()函数的调用,接下来分析myfun是如何被调用的。
首先转到反汇编(我使用的工具是VS2013)
int main()
9: {
010613C0 push ebp
010613C1 mov ebp,esp
010613C3 sub esp,0E4h
010613C9 push ebx
010613CA push esi
010613CB push edi
010613CC lea edi,[ebp+FFFFFF1Ch]
010613D2 mov ecx,39h
010613D7 mov eax,0CCCCCCCCh
010613DC rep stos dword ptr es:[edi]
10: int a = 0xAAAAAAAA;
010613DE mov dword ptr [ebp-8],0AAAAAAAAh
11: int b = 0xBBBBBBBB;
010613E5 mov dword ptr [ebp-14h],0BBBBBBBBh
12: int c = myfun(a, b);
010613EC mov eax,dword ptr [ebp-14h]
010613EF push eax
010613F0 mov ecx,dword ptr [ebp-8]
010613F3 push ecx
010613F4 call 01061096
即看到的是下面三行汇编,其作用是为main开辟栈桢,这里姑且将它命名为main,ebp是栈底,esp是栈顶,自栈顶底向栈顶生长,push是压栈指令,作用是将指令压入栈,压入都是从栈顶压入。
010613C0 push ebp
010613C1 mov ebp,esp
010613C3 sub esp,0E4h
main中int a,int b的初始化,[ebp-8],ebp是栈底,-8自下初始化a,同样,[ebp-14h]在ebp向下14h初始化。
10: int a = 0xAAAAAAAA;
010613DE mov dword ptr [ebp-8],0AAAAAAAAh
11: int b = 0xBBBBBBBB;
010613E5 mov dword ptr [ebp-14h],0BBBBBBBBh
实参到形参,从上一段汇编可以看到,[ebp-14h]-->b,[ebp-8]-->a,而先入栈的是b,最后才是a。即调用函数中临时变量的建立是从右往左的,这里从原理上直接体现了出来
12: int c = myfun(a, b);
010613EC mov eax,dword ptr [ebp-14h]
010613EF push eax
010613F0 mov ecx,dword ptr [ebp-8]
010613F3 push ecx
函数临时变量已经建立好,call语句将计算机要执行的命令从当前的main跳转至myfun,其本质是将目标函数的地址覆盖到EIP中(EIP:程序指针,用来存放正在执行的指令的下一条指令的地址)
010613F4 call 01061096
010613F9 add esp,8
call “0106096”(学过微机原理我们知道call不能直接调用子程序,所以call了jmp间接跳转)这个地址就是用来覆盖EIP的地址,清楚的看到,程序跳转到了“0106096”这个地址,而且图右边的EIP也将指令变为“0106096” 值得注意的是这条指令的地址010613F9 add esp,8,010613F9在下方的内存中查看栈顶esp的写入情况,可以看到010613F9已经被push进去了,至于为什么压入这条指令其实显而易见,call将EIP中当前正在执行指令的下一条指令地址覆盖为010613F9,而函数的调用最终会结束并回到原函数继续执行,所以必须把指令地址保存起来。
进去到函数之后
3: int myfun(int x, int y)
4: {
01061470 push ebp
01061471 mov ebp,esp
01061473 sub esp,0CCh
01061479 push ebx
0106147A push esi
0106147B push edi
0106147C lea edi,[ebp+FFFFFF34h]
01061482 mov ecx,33h
01061487 mov eax,0CCCCCCCCh
0106148C rep stos dword ptr es:[edi]
5: int z = x + y;
0106148E mov eax,dword ptr [ebp+8]
01061491 add eax,dword ptr [ebp+0Ch]
01061494 mov dword ptr [ebp-8],eax
6: return z;
01061497 mov eax,dword ptr [ebp-8]
7: }
0106149A pop edi
0106149B pop esi
0106149C pop ebx
0106149D mov esp,ebp
0106149F pop ebp
010614A0 ret
和主函数一样,首先开辟栈桢
01061470 push ebp
01061471 mov ebp,esp
01061473 sub esp,0CCh
当前栈底是接着esp之前push010613F9开辟的,所以ebp到010613F9为ebp+4(地址int),而010613F9下是a(a是int再加4),所以ebp+8是a,b在a下,即ebp+och(12),add是对取回的ab求和
0106148E mov eax,dword ptr [ebp+8]
01061491 add eax,dword ptr [ebp+0Ch]
ebp-8自然是进入到栈桢空间,将eax内的a+b存入
mov dword ptr [ebp-8],eax
又将a+b的结果存入eax用于返回值
mov eax,dword ptr [ebp-8]
函数的调用大致完成,撤销栈桢
0106149D mov esp,ebp
这里pop弹出函数一开始push的ebp地址,也就是主函数时的栈底
0106149F pop ebp
程序重新回到主函数,之前被push的010613F9重新回到EIP中,到此,函数的调用完成。


743

被折叠的 条评论
为什么被折叠?



