这次讲解一下C++函数调用,学了这么久C语言,肯定听说过栈(数据结构啊,地址空间的栈啊之类的),函数调用就和栈密切相关。
因为地址空间内的栈是从高地址向低地址生长的,也就是说压栈顺序靠后的反而地址比较低,栈底的地址高于栈顶的地址,下面贴上一段测试代码
#include<stdio.h>
#include<stdlib.h>
void bug()
{
printf("haha I ma a bug!!");
exit(100);
}
int func(int x, int y)
{
int *p = &x;
p--;
*p = (int)bug;
printf("x:%d,y:%d\n", x, y);
int c = 0xcccc;
return c;
}
int main()
{
printf("I am main\n");
int a = 0xaaaa;
int b = 0xbbbb;
func(a, b);
printf("I should run here\n");
return 0;
}
这段代码的运行结果,并没有执行main函数的第二个printf,而是跑到了bug函数中执行,这是因为我修改了函数栈帧中的返回地址部分
本来是打算通过linux系统来看的,但是CentOS7的栈帧实现似乎有些不同,同样的代码在centos7上面跑不通。
下面是反汇编
int main()
{
00A118E0 push ebp
00A118E1 mov ebp,esp
00A118E3 sub esp,0D8h
00A118E9 push ebx
00A118EA push esi
00A118EB push edi
00A118EC lea edi,[ebp-0D8h]
00A118F2 mov ecx,36h
00A118F7 mov eax,0CCCCCCCCh
00A118FC rep stos dword ptr es:[edi]
printf("I am main\n");
00A118FE push offset string "I am main\n" (0A16CF0h)
00A11903 call _printf (0A1132Ah)
00A11908 add esp,4
int a = 0xaaaa;
00A1190B mov dword ptr [a],0AAAAh
int b = 0xbbbb;
00A11912 mov dword ptr [b],0BBBBh
func(a, b);
00A11919 mov eax,dword ptr [b]
00A1191C push eax
00A1191D mov ecx,dword ptr [a]
00A11920 push ecx
00A11921 call func (0A11366h)
00A11926 add esp,8
printf("I should run here\n");
00A11929 push offset string "I should run here\n" (0A16CFCh)
00A1192E call _printf (0A1132Ah)
00A11933 add esp,4
return 0;
00A11936 xor eax,eax
}
因为main函数本身真的是个函数!所以在执行我们编写的程序之前操作系统需要保存当前它运行的状态,就跟函数调用很类似
1 00A118E0 push ebp 这句话就是把操作系统的状态压栈
2 00A118E1 mov ebp,esp 然后把栈底指针挪到新的位置
3 00A118E3 sub esp,0D8h 扩展新的栈帧,你总不能让新的栈底和栈顶挨在一起吧?
过程图我会在讲到func函数的时候给出来,更容易理解,之后的push之类的就是为了保存现场和执行前准备
1 printf("I am main\n"); 2 00A118FE push offset string "I am main\n" (0A16CF0h) 3 00A11903 call _printf (0A1132Ah) 4 00A11908 add esp,4
这部分就是调用printf的系统调用,因为库函数更多是对操作系统调用的再一次调用(封装?的说法也可以),因为我不是很懂这部分,也就不详细解释其中_printf的系统调用究竟怎么工作了