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

Why use RIP-relative addressing in NASM?

我有一个 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] 替换为 ,NASM 会抛出此错误(注意:我正在使用命令 nasm -f macho64 hello.asm):

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

为什么会这样? lea 有什么特别之处是 mov 做不到的?我怎么知道什么时候使用每一个?

What is so special about lea that mov can't do?

LEA r, [rel symbol] 可以在 运行 时访问 RIP。 mov r, imm 不能。立即数常量被编码为指令的二进制表示,这意味着如果代码+数据映射到 link 时未知的地址,它将无法工作。 (即它是位置相关的代码。)

这就是为什么 RIP 相对寻址对 PIC(位置无关代码)非常有用:而不需要通过全局偏移 Table 的间接级别来访问同一对象中定义的静态数据文件,您可以只使用 RIP 相对地址。

它还可以有效地为您提供 64 位地址,而无需在指令中嵌入完整的 64 位绝对地址。 MacOS X 需要 64 位地址,因为它将“映像库”映射到虚拟地址的低 4GiB 之外 space.

如果可执行文件(不仅仅是共享库)是 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 位绝对值的寄存器。

其他相关问答:

  • Why is the address of static variables relative to the Instruction Pointer?
  • 32-bit absolute addresses no longer allowed in x86-64 Linux?
  • Mach-O 64-bit format does not support 32-bit absolute addresses. NASM Accessing Array)

What is so special about lea that mov can't do?

mov reg,imm 加载一个 immediate 常量到它的目标操作数。立即数直接在操作码中编码,例如如果 someVar 的地址是 0x00ABCDEFmov eax,someVar 将被编码为 B8 EF CD AB 00。 IE。要使用 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的绝对地址,因此允许汇编程序不知道实际地址但仍然对指令进行编码。