函数的参数和本地变量都会被存放在stack上,传统来说两个寄存器会存放这个栈地址,其中之一就是ESP(保存当前stack的top,注意是从high address -> low address的从上到下的方向)。
函数参数和本地变量在函数开始的时候会有一段针对这个stack的offset, 但是随着这个stack的grow或者shrink(ESP变化)那么这些offset都要重新计算,这显然不是一个优美的方案。因此Intel推出了base pointer(frame pointer),他就放在了EBP里。 EBP就是当函数第一次调用的时候整个stack的top.
因此return address始终是ebp+4.第一个参数永远是ebp+8(注意这里的+的意思是地址升高,其实对栈来说是之前的变量),第一个本地变量是ebp-4(因此栈是从高到低伸展)。所以在这种情况下,即使stack size生长或者缩小,这些offset都不会改变。这也要反汇编变得更加简单,因为你可以非常容易去拿到对应的parameters和local变量。
同时因为ebp指向了前面一个frame的指针(push ebp; move ebp)来开展新函数,这也让你在做栈回溯的时候提供了可能性,比如我们在调试器里一直用的foo()调用了bar(), bar()调用了zz(), 我们可以通过zz()的ebp回溯到调用栈上的任意一层来查看参数和变量。
但是编译器的作者不同意了,因为他们发现即使在最终生成的代码中不用EBP,他对你的性能和内存都没有任何损失,那为什么我编译器在编译这段程序的时候还要给你提供这个EBP? 作为编译器本身,ESP的改变对于它来查找变量没有任何的负担,因为这是编译器的天生特性释然他可以很轻松的追溯到任何一个变量而不需要去利用ebp. 但是这个对于我们平常如果想使用反汇编来说就是一个很大的问题。所以现在有一些函数如果不用EBP作为frame based,这些函数就有了利用这个EBP做“其他目的”的途径,这也大大增加了函数的优化潜能。可以说这也是一把双刃剑
ESP的改变加上不用EBP的特性,在反汇编的情况下会带来困扰,比如看这段代码
mov eax, [esp+4]
pop ecx
add eax, ecx
mov [esp], eax
你会想当然的以为第一行和最后一行修改的变量是两个不同的变量,但是由于esp会改变的特性,真正的结论是他们是同一个变量。
本页共12段,1124个字符,2504 Byte(字节)