一个程序的调用堆栈的解析

发布时间 2023-10-15 11:11:06作者: YanU-

调用一个函数会执行以下操作

压栈函数参数,压栈返回地址,压栈调用方的栈底,申请局部变量空间,压栈寄存器环境。

 

 

首先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函数并不是真正的程序入口。