如何在64位汇编程序中使用RIP相对寻址?

37

如何在Linux AMD64架构的汇编程序中使用RIP相对寻址?我正在寻找一个简单的例子(Hello World程序),演示如何使用AMD64 RIP相对寻址模式。

例如,下面的64位汇编程序用于普通(绝对寻址):

.text
    .global _start

_start:
    mov $0xd, %rdx

    mov $msg, %rsi
    pushq $0x1
    pop %rax
    mov %rax, %rdi
    syscall

    xor %rdi, %rdi
    pushq $0x3c
    pop %rax
    syscall

.data
msg:
    .ascii    "Hello world!\n"

我猜使用RIP相对寻址的相同程序可能是这样的:

.text
    .global _start

_start:
    mov $0xd, %rdx

    mov msg(%rip), %rsi
    pushq $0x1
    pop %rax
    mov %rax, %rdi
    syscall

    xor %rdi, %rdi
    pushq $0x3c
    pop %rax
    syscall

msg:
    .ascii    "Hello world!\n"

正常版本在以下编译条件下能够运行良好:

as -o hello.o hello.s && ld -s -o hello hello.o && ./hello

但是我不能让 RIP 版本正常工作。

有任何想法吗?

--- 编辑 ----

Stephen Canon的答案让RIP版本可以工作。

现在,当我反汇编RIP版本的可执行文件时,我得到:

objdump -d hello

0000000000400078 <.text>:
  400078: 48 c7 c2 0d 00 00 00  mov    $0xd,%rdx
  40007f: 48 8d 35 10 00 00 00  lea    0x10(%rip),%rsi        # 0x400096
  400086: 6a 01                 pushq  $0x1
  400088: 58                    pop    %rax
  400089: 48 89 c7              mov    %rax,%rdi
  40008c: 0f 05                 syscall 
  40008e: 48 31 ff              xor    %rdi,%rdi
  400091: 6a 3c                 pushq  $0x3c
  400093: 58                    pop    %rax
  400094: 0f 05                 syscall 
  400096: 48                    rex.W
  400097: 65                    gs
  400098: 6c                    insb   (%dx),%es:(%rdi)
  400099: 6c                    insb   (%dx),%es:(%rdi)
  40009a: 6f                    outsl  %ds:(%rsi),(%dx)
  40009b: 20 77 6f              and    %dh,0x6f(%rdi)
  40009e: 72 6c                 jb     0x40010c
  4000a0: 64 21 0a              and    %ecx,%fs:(%rdx)

这展示了我试图实现的目标:lea 0x10(%rip),%rsi加载lea指令后17个字节的地址,即地址0x400096,可以找到“Hello world”字符串,从而产生位置无关代码。


1
为什么在0x10之后还有17个字节(0x10是第16个字节)? - fadedbee
4
https://www.tortall.net/projects/yasm/manual/html/nasm-effaddr.html说:“RIP是指令指针寄存器,它包含紧随当前指令之后的位置的地址”,但是“lea”指令长度为七个字节,而不是一个字节。 - fadedbee
相关:如何将函数或标签的地址加载到寄存器中涵盖了RIP相对LEA以及针对非PIE可执行文件优化mov $msg,%esi的方法。(或者在代码模型中使用movabs 64位绝对地址,其中您拥有超过2GiB的代码+静态数据。) - Peter Cordes
1个回答

37

我认为您想要将字符串的地址加载到%rsi中;您的代码尝试从该地址加载四字节而不是地址本身。您需要:

lea msg(%rip), %rsi

如果我没记错的话。不过,我没有Linux系统可以测试。


5
如果使用"lea msg(%rsp), %rsi"代替"lea msg(%rip), %rsi"(或任何寄存器但不是rip),则添加的是mes标签本身的地址,而不是从当前提供的寄存器值的偏移量。例如,如果msg在地址0x1FF,则使用"lea msg(%rsp), %rsi"会导致rsi = *(rsp + 0x1FF),而不是rsi = *((rsp - 0x1FF) + rsp),因为反汇编器给出了0x10(%rip)的距离,因为当前rip和msg之间的距离为0x10字节。但我找不到文档中有关于计算rip和其他寄存器之间差异的说明。 - user2808671
@StephenCanon 这在 x86_64 上可以工作,那么在 32 位汇编中,lea msg(%rip),%rsi 的等效指令是什么? - Zibri
2
@Zibri: 没有独立于位置的方法,这就是为什么AMD64添加了RIP相对寻址。在Linux下,编译器使用相对于GOT的偏移量。当然,在依赖于位置的32位代码中,您可以像在依赖于位置的64位代码中一样使用 mov $msg,%esi(在Linux下,静态符号地址已知在虚拟地址空间的低2GiB中,非PIE可执行文件) 。 - Peter Cordes
2
@user2808671:是的,msg(%rip)是一个特殊情况,它表示相对于RIP的符号,而不是绝对地址+ RIP。https://sourceware.org/binutils/docs/as/i386_002dMemory.html底部记录了这一点。 - Peter Cordes

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