我通常不会花太多时间阅读汇编,所以下面的编译器输出让我有点困惑。
假设我在运行OSX 10.6:的Intel Core 2 Duo上编译了这段C代码
while (var != 69) // var is a global variable
{
printf("Looping!n");
}
"var!=69"比较的程序集看起来像:
cmpl $69, _var(%rip)
我知道这实际上意味着将值"69"与全局变量"var"的内容进行比较,但我很难理解"_var(%rip)"部分。通常,我希望有一个偏移值,比如引用堆栈中的局部变量(例如:-4($ebp))。然而,我不太明白用"_var"声明偏移指令指针将如何获得全局变量"var"的内容。
那条线到底是什么意思?
谢谢。
这与用offset(%ebp)
寻址堆栈中的局部变量几乎相同。在这种情况下,链接器将该指令的偏移量字段设置为var
的地址与该指令执行时%rip
的值之间的差。(如果我没记错的话,这个值就是下一个指令的地址,因为%rip
总是指向当前执行的指令之后的指令。)因此,加法给出了var
的地址。
为什么要这样做?这是位置无关代码的标志。如果编译器生成了
cmpl $69, _var
并且链接器已经填写了var
的绝对地址,那么当运行程序时,可执行映像将始终在一个特定地址加载到内存中,这样所有变量都具有代码期望的绝对地址。通过这种方式,唯一需要修复的是代码和数据之间的距离;代码加数据(即完整的可执行映像)可以在任何地址加载,并且它仍然可以工作。
为什么要麻烦?为什么必须在一个特定地址加载可执行文件是不好的?不一定。共享库必须与位置无关,因为否则可能有两个库要在重叠的地址加载,并且不能在同一程序中同时使用这两个库。(有些系统通过保留所有库及其所需空间的全局注册表来解决这一问题,但显然这并不能扩展。)使可执行文件位置独立在很大程度上是一种安全措施:如果你不知道程序代码在内存中的位置,就更难利用缓冲区溢出(这被称为地址空间布局随机化)。