调用一个函数会执行以下操作:
压栈函数参数,压栈返回地址,压栈调用方的栈底,申请局部变量空间,压栈寄存器环境。
首先push ebp,这里调用main函数的函数的局部变量首地址,后面会有更直观的
保存main的回合中这三个寄存器的值,需要注意的是,这里已经开辟完空间main函数局部变量的空间了
这里把开辟的空间遍历设置为CC
这里是在保存的三个寄存器之后压栈在main中调用的foo函数的第一个参数,需要注意的是c的调用约定是从右往左压栈的
这里可以看到call指令先是将call的下一条指令的地址(00401301)压栈然后才jmp到foo的地址,这是个复杂指令
这里可以发现这个ebp(0019FF30)是main的局部变量的首地址(也是main一开始压栈ebp的地方)
这里是为foo申请空间和保存foo回合中的三个寄存器的值
为申请的空间初始化为CC
这里由于又嵌套调用了一个foo2函数,所以要把函数的参数压栈然后调用foo2
同样的保存下一条指令的地址(函数返回地址)
同样的push ebp,可以发现这里的ebp就是刚才的foo的局部变量首地址
相同的保存foo2回合下的寄存器的值
为foo2申请局部变量空间,然后为局部变量内存初始化为CC
计算a+b的值(函数功能),并将值通过eax返回
这里退出foo2函数的时候要先将开辟的空间还回去,也就是CC的部分,可以发现这里的ebp就是上一个函数foo的局部变量首地址
然后将ebp给pop回去,esp就停在了返回地址上(call的下一条指令的地址)
运行F11发现ret先是将这个返回地址给pop给EIP
然后还要将函数的两个参数push借用的栈平回去
然后将保存的三个寄存器的值pop回去
然后将foo开辟的空间还回去,也就是恢复esp的值
然后将ebp pop回去
然后还是将返回地址赋值给EIP,再将函数参数占用的堆栈还回去
这里是将eax(返回值)赋值给接收foo返回值的局部变量(main的局部变量)可以看到整个局部变量右边四个字节就是上个EBP的值(这个回合的EBP就是调用main的函数的局部变量的起始地址)
然后将main函数申请的局部变量还回去
这里是pop ebp顺便将返回地址的栈露出来(因为pop会改变esp)
然后ret修改EIP再将main函数的参数还回去
最后,调用exit来退出程序
所以,其实我们写的main函数,也只是被真正的入口调用的一个小部分,只是语言中定义好的一个函数,然后我们构造他,来实现我们想实现的。
main函数并不是真正的程序入口。