一、前提知识
1、寄存器空间和内存空间是两个不同存储部分,程序指令和栈一般认定在内存中,而寄存器可以存储数值和某些内存空间地址。
2、栈空间是由高地址向低地址增长的,栈顶就是地址最低的。
3、ESP寄存器往往存储栈顶地址,是动态变化的,PUSH、POP、CALL、RETN汇编指令都会造成ESP存储的值发生变化,ESP始终指向栈顶元素,而不是栈顶下一个元素。
4、EIP寄存器是存放CPU要读取的指令地址,相当于PC计数器。
5、EBP表示一个栈空间的基地址,不同函数的基地址不一样,比如main函数和main函数里调用的函数的EBP值不一样,可能差很多。
二、函数调用过程寄存器变化
假如在main函数中调用一个fun函数,fun包含输入实参para和局部变量var,那么栈空间的变化过程如下:
地址1 | para |
地址2 | EIP值 |
地址3 | EBP值(main的) |
地址4 | var1 |
地址5 | var2 |
1、输入参数会通过PUSH压栈,供fun使用,ESP值变小,EBP值是main的栈基地址。
2、CALL调用fun,此时会将EIP的值入栈,即保存从函数返回之后要执行的指令,因为EIP在fun内可能被修改。
3、fun函数中首先会PUSH EBP的值,即保存main函数的栈基地址,然后把ESP的值赋值给EBP,此时ESP的值和EBP的值相同。
4、然后SUB ESP的值,为局部变量var开辟栈空间,此时ESP始终保持栈顶,EBP保存地址3的值(不是地址3里的EBP值),会通过EBP和偏移地址来操作局部变量(其实只用ESP也能操作,只是这样会不停改变ESP值,会增加指令数目,所以多了EBP事实上是对程序的优化)。
5、fun完成后,首先会把EBP的值赋给ESP(和3相反,此时ESP是地址3),然后POP main的EBP值给EBP寄存器(此时ESP是地址2),EBP寄存器恢复调用前。
6、RETN会POP EIP值至EIP寄存器(此时ESP是地址1),EIP寄存器恢复调用前。
7、然后会对para值进行清理,使得ESP寄存器恢复调用前。
三、总结
1、一个函数调用过程充满了对栈的操作,而这三个寄存器是栈操作相关的,核心思想就是保存原来状态,使用后再恢复。
2、关于7中对参数的回收具体操作,可能在fun内完成,也可能由调用者main完成,具体跟函数的调用方式有关,在C语言中是可以控制的,具体可参考文档:https://www.cnblogs.com/yenyuloong/p/9626658.html。