ebp(extended base pointer)寄存器存放当前栈帧的栈底,一般在函数内不会对ebp寄存器做变动
esp(extended stack pointer)寄存器存放当前栈帧的栈顶,会随着当前函数内栈空间的开辟而变动
首先我们要明确,push pop就是对esp的操作。
因为栈是从高地址向低地址出增长的,所以push是对esp进行减法,然后将操作数写到上述寄存器里的指针所指向的内存中。
pop是对esp进行加法:它先从栈指针指向的内存中读取数据,用以备用(通常是写到其他寄存器里),然后再将栈指针的数值加上4或8.
比如push %eax前esp=0x115fc68 那么push后esp=0x115fc64:原来的值减去4
void test(){
int a = 0;
}
int main(){
int a = 0;
test();
}
在上面这段代码的执行中,ebp esp的变动是这样的
首先进入main函数后,push %ebp,保存上一个函数即_start函数的栈底。
此时esp就指向保存ebp的栈顶。如图
然后将esp赋值给ebp做为当前函数新的栈底.此时ebp和esp指向同一个地址
此时执行int a=0;
那么将esp减去4,同时将0赋值给esp指向的地址。
如果调用test时有参数要传递,那么参数是保存在调用者,在这里是main函数的栈帧中的。返回地址也是保存在调用者main函数的栈帧中。压栈顺序是这样:先将最后一个参数入栈,然后倒数第二个,倒数第三个… 参数压栈完毕后将返回地址压栈。返回地址也指示了main函数栈帧的结束处。(见linux内核完全剖析0.12P55页)
进入test函数后,一样的,要压入ebp,将esp赋值给ebp,而且分配内存给a。
执行完后,内存布局如图
执行完test函数后,函数返回。恢复main函数的栈底和栈顶,即将esp 和ebp恢复。函数返回后内存布局如图
自然,在test函数内部的a被随后的执行所覆盖。
小结:
在做操作系统实验时,对其中ebp esp寄存器的处理十分疑惑,网上查资料后有了大概的框架。这里只是简单介绍了下函数调用中esp ebp的变化,对参数的压栈返回地址的压栈没有详细的介绍,其实,在被调用函数内使用参数时,是通过对ebp寄存器执行加法得到的,详细可见linux内核完全剖析0.12中对c和汇编相互调用的论述,图文并茂,写的十分精彩。
总之,函数调用是一个在当前内存开辟栈帧,然后在当前栈帧开辟空间,然后执行完当前函数后返回上一个栈帧,然后覆盖当前栈帧的一个重复过程。
这些在
参考:
linux内核完全剖析0.12
https://zhuanlan.zhihu.com/p/77663680