为什么在NASM中使用RIP相对寻址?

10

我有一个适用于Mac OS X的汇编Hello World程序,代码如下:

global _main


section .text

_main:
    mov rax, 0x2000004
    mov rdi, 1
    lea rsi, [rel msg]
    mov rdx, msg.len
    syscall

    mov rax, 0x2000001
    mov rdi, 0
    syscall


section .data

msg:    db  "Hello, World!", 10
.len:   equ $ - msg

我对 lea rsi, [rel msg] 这行代码感到困惑。我不明白为什么NASM强制我这样写。据我的理解,msg 只是可执行文件中某些数据的指针,使用 mov rsi, msg 就可以将该地址放入 rsi 中。但如果我用其他方式代替 lea rsi, [rel msg] ,比如 mov rsi, msg,那么NASM会报错(注意:我使用的命令是 nasm -f macho64 hello.asm):

hello.asm:9: fatal: No section for index 2 offset 0 found

为什么会出现这种情况?lea有什么特别之处,mov不能做到的?我该如何知道何时使用它们?

我认为Jester已经回答了这个问题。Mach-O对象文件格式要求所有内容都是位置无关的。这意味着你的代码需要能够在任何地址加载并正常工作。mov rsi,msg使用绝对地址,这将根据程序加载的位置而改变,而Mach-O不支持这种情况。 - Ross Ridge
1
@RossRidge 但是“绝对地址”实际上不是相对于可执行文件的开头吗? - Jerfov2
1
CPU不知道可执行文件从哪里开始。当它执行mov rsi,msg指令时,它会将寄存器加载为编码为立即操作数的值。该立即值需要是“msg”的实际地址。Mach-O不支持这一点。 - Ross Ridge
@RossRidge 可执行文件知道它将要加载到哪里吗?如果不知道,它怎么知道 msg 的地址在哪里? - Jerfov2
使用 Mach-O 格式,可执行文件不知道它将被加载到哪里。它也不知道 msg 将位于何处。但是通过使用 RIP 相对寻址,它就不需要知道这些信息了。 - Ross Ridge
显示剩余2条评论
2个回答

13

lea指令有什么特别之处,mov指令不能实现呢?

mov reg,imm将一个立即数常量加载到目标操作数中。立即常量直接编码在操作码中,例如,如果someVar的地址是0x00ABCDEF,那么mov eax,someVar将被编码为B8 EF CD AB 00。也就是说,要用imm表示msg的地址来编码这样的指令,您需要知道msg的确切地址。在位置无关代码中,您事先并不知道它。

mov reg,[expression]将位于由expression描述的地址上的值加载到寄存器中。由于x86指令的复杂编码方案,可以具有相当复杂的expression:通常为reg1+reg2*s+displ,其中s可以为0、1、2、4,reg1reg2可以是通用寄存器或零,而displ是立即位移量。在64位模式下,expression可以有一种更形式: RIP+displ,即该地址是相对于下一条指令计算的。

lea reg,[expression]使用所有这种复杂的计算地址的方式将地址本身加载到reg中(与mov不同,它对计算出的地址进行了解引用)。因此,在编译时不可用的信息,即在RIP中的绝对地址,可以在不知道其值的情况下编码在指令中。nasm表达式lea rsi,[rel msg]会被翻译为类似于以下内容的东西:

    lea rsi,[rip+(msg-nextInsn)]
nextInsn:

使用相对地址msg-nextInsn而不是msg的绝对地址,从而使汇编器不必知道实际地址但仍然能够编码指令。

使用相对地址msg-nextInsn而不是msg的绝对地址,从而使汇编器不必知道实际地址但仍然能够编码指令。

12
LEA指令可以在运行时访问RIP,而mov指令则不能。立即常数编码进指令的二进制表示中,这意味着如果代码+数据映射到在链接时未知的地址,则无法工作(即它是位置相关代码)。这就是为什么RIP相对寻址对于PIC(位置无关代码)非常好的原因:不需要通过全局偏移表进行间接访问即可访问甚至在同一目标文件中定义的静态数据,只需使用RIP相对地址即可。此外,它还可以有效地提供64位地址,而无需在指令中嵌入完整的64位绝对地址。MacOS X需要64位地址,因为它将“映像基址”映射到虚拟地址空间的低4GiB之外。如果可执行文件(不仅仅是共享库)是PIC,则更安全,因为MacOS可以随机化其基地址(而无需重写任何绝对地址)。
在依赖于位置的Linux可执行文件(而不是MacOS)中,您可以使用 mov esi, msg 作为一种优化。请注意,应该使用 ESI,而不是 RSI。如果使用 mov rsi, msg,效率会更低,因为它会使用一个10字节的 mov rsi, imm64,而不是7字节的 lea rsi, [RIP + rel32]。(有关如何将函数或标签的地址加载到寄存器中,请参见 这里
在x86-64中访问静态数据的“正常”方式是使用RIP相对寻址,例如 mov eax, [rel my_global_var]。只有在将地址放入寄存器时,您可能会利用32位绝对地址,如果目标允许32位绝对地址的话。
其他相关的问题和答案:

更新:OS X确实支持64位绝对地址的文本重定位,但它将可执行文件加载到低32位之外,因此32位绝对寻址是无法使用的。因此,RIP相对寻址并非必需,但在OS X上它几乎总是最好的选择。 - Peter Cordes

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