为什么这条MOVSS指令使用RIP相对寻址?

9
我在反汇编器中找到了以下汇编代码(浮点逻辑C ++)。
  842: movss  0x21a(%rip),%xmm0 

我理解当进程rip时,始终会是842,并且这个0x21a(%rip)将是常量。使用这个寄存器似乎有点奇怪。

我想知道使用rip相对地址是否有任何优势,而不是其他寻址方式。

1个回答

19

RIP是指令指针寄存器,这意味着它包含当前指令后紧随其后的指令地址。

例如,考虑以下代码:

mov  rax, [rip]
nop

在这段代码的第一行中,RIP指向下一条指令,所以它指向了NOP。因此,这段代码将NOP指令的地址加载到了RAX寄存器中。

因此,RIP并不是一个简单的常量。你认为这个过程中RIP“总是会是842”是不正确的。RIP的值将根据代码被加载到内存的位置而改变。842只是从调试符号中提取的行号;一旦代码被编译成二进制文件,就不再有行号了。:-)

在你的反汇编中,常量是偏移量(0x21A)。这是相对于RIP当前值的偏移量。另一种写法是:%rip + 0x21A

RIP相对寻址是64位长模式引入的一种新型有效寻址形式。其意义在于,通过使用RIP相对寻址,可以更容易地编写位置无关代码,因为可以使任何内存引用都是RIP相对的。事实上,在64位应用程序中,RIP相对寻址是默认的寻址模式。在64位模式下几乎所有访问内存的指令都是RIP相对的。我会引用Ken Johnson (aka Skywing)的博客,因为我无法更好地表述:

关于x64与x86之间的较大(但经常被忽视的)变化之一是,现在大多数先前只通过绝对寻址引用数据的指令现在都可以通过RIP相对寻址引用数据。

RIP相对寻址是一种地址参考,它以当前指令指针为基础提供一个(有符号的)32位位移量。虽然在x86上通常只在控制转移指令(call、jmp等)中使用这个指令指针寄存器相对寻址,但x64将指令指针寄存器相对寻址的使用扩展到了覆盖更广泛的指令集。

使用RIP相对寻址的优点是什么呢?主要好处是,生成位置无关代码变得更加容易,或者说代码不依赖于其在内存中加载的位置。在今天的(相对)自包含模块(例如DLL或EXE)中同时包含数据(全局变量)和相应代码的情况下,这一点尤其有用。如果在x86上使用平面寻址,则对全局变量的引用通常需要硬编码该全局变量的绝对地址,假设模块在其首选基地址处被加载。如果在运行时不能将模块加载到首选基地址,则加载程序必须执行一组基地址重定位,以实际重写所有具有绝对地址操作数部分的指令,以反映模块的新地址。

[ . . . ]

但是,使用RIP相对寻址的指令通常不需要任何基地址重定位(也称为“修复”),即使包含它的模块被重新定


3
在我看来,RIP相对寻址被忽视的一个原因是SIB字节没有64位位移,并且唯一使用moffs64作为立即数的指令是mov rax, moffs64(A1)和mov moffs64, rax(A3)。我认为,RIP相对寻址的引入是为了避免使用64位立即数,而不是为了追求PIC,这只是一个副作用。毕竟,在RISC中也是通过这种方式解决了这个问题。然而,这只是我的个人看法。 - Margaret Bloom
6
当 AMD 设计AMD64时,i386的PIC开销非常明显(特别是对于Linux,在其中将一个寄存器绑定为指向GOT的指针),我认为在2000年左右,将大量代码放入库中已经开始发生。我相信他们当时考虑到了PIC的好处,以及即使在加载到虚拟地址空间低2或4GB之外时也能让代码和数据高效利用的好处。 - Peter Cordes
1
在64位模式下,几乎所有涉及内存的指令都是相对于RIP的。这适用于直接访问静态数据的指令(全局变量和“static”内容)。但我认为大多数代码中的大多数内存访问都是通过寄存器中的指针进行的,其中包括本地未在寄存器中的指针和指针变量。另外,通过索引静态数组无法使用RIP相对寻址来进行实际的加载或存储。例如,如果“arr”可以是绝对disp32,则为“[arr + rax * 4]”,否则为RIP相对LEA到一个寄存器,然后为“[rcx + rax * 4]”。 - Peter Cordes
1
注意:OP看到的数字不是“从您的调试符号中提取的行号” - 我不知道任何使用这种表示形式甚至默认执行这种检查的反汇编器。显示的数字只是从“.text”部分开头的偏移量(以字节和十六进制表示法)(对于位置无关可执行文件将设置为零)。 - Marco Bonelli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接