[软件调试].张银奎

          我们看到,尽管可以使用相对于栈顶(ESP寄存器)的偏移来引用局部变量,但是因为ESP寄存器经常变化,所以用这种方法引用同一个局部变量的偏移值是不固定的。这种不确定性对于CPU来说不成什么问题,但在调试时,如果要跟踪这样的代码,那么很容易就被转得头晕眼花,因为现实的函数大多有多个局部变量,可能还有层层嵌套的循环,栈指针变化非常频繁。

       为了解决以上问题,x86 CPU 设计了另一个寄存器,这就是EBP寄存器。EBP的全称是 Extended Base Pointer,即拓展的基址指针。使用EBP寄存器,函数可以把自己将要使用的栈空间的基准地址记录下来,然后使用这个基准地址来引用局部变量和参数。在同一函数内,EBP寄存器的值是保持不变的,这样函数内的局部变量便有了一个固定的参照物。

       通常,一个函数在入口处将当时的EBP值压入堆栈,然后把ESP值(栈顶)赋给EBP,这样EBP中的地址就是进入本函数时的栈顶地址,这一地址上面(地址值递减方向)的空间便是这个函数将要使用栈空间,它下面(地址值递增方向)是父函数使用的空间。如此设置EBP后,便可以使用EBP加正数偏移来引用父函数的内容,使用EBP加负数便宜来引用本函数的局部变量,比如EBP+4 指向的是 CALL指令压入的函数返回地址;EBP+8是父函数压在栈上的第一个参数,EBP+0xC是第二个参数,一次类推;EBP-n是第一个局部变量的起始地址(n为变量的长度)。

       因为在将栈顶地址(ESP)赋给EBP寄存器之前先把旧的EBP值保存在栈中,所以EBP寄存器所指向的栈单元中保存的是前一个EBP寄存器的值,这通常也就是父函数的EBP的值。类似的父函数的EBP所指向的栈单元中保存的是更上一层函数的EBP值,以此类推,直到当前线程的最顶层函数。这也正是栈回溯的基本原理。