了解Intel汇编中的%rip寄存器

28
关于以下小代码,它在另一篇关于结构体大小和正确对齐数据的帖子中有所说明:
struct
{
 char Data1;
 short Data2;
 int Data3;
 char Data4;
} x;

unsigned fun ( void )
{
    x.Data1=1;
    x.Data2=2;
    x.Data3=3;
    x.Data4=4;
    return(sizeof(x));
}

我得到了相应的反汇编(使用64位)

0000000000000000 <fun>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   c6 05 00 00 00 00 01    movb   $0x1,0x0(%rip)        # b <fun+0xb>
   b:   66 c7 05 00 00 00 00    movw   $0x2,0x0(%rip)        # 14 <fun+0x14>
  12:   02 00 
  14:   c7 05 00 00 00 00 03    movl   $0x3,0x0(%rip)        # 1e <fun+0x1e>
  1b:   00 00 00 
  1e:   c6 05 00 00 00 00 04    movb   $0x4,0x0(%rip)        # 25 <fun+0x25>
  25:   b8 0c 00 00 00          mov    $0xc,%eax
  2a:   5d                      pop    %rbp
  2b:   c3                      retq   

我不知道如何计算右侧出现的术语,这似乎是使用本地变量地址。此外,我不知道如何使用%rip寄存器计算它。

您能否给出一个示例,显示%rip%rsp%rbp之间的联系,即特别是在使用move指令时计算地址时。


7
这并没有什么关联,“rip”是指令指针(因此得名)。你不能相对于它访问本地变量。请注意,x不是本地变量。另外,请注意您在一个中间对象文件上使用了objdump,因此您没有得到正确的偏移量。您可能需要在链接的可执行文件上运行它和/或使用“-r”选项来查看重定位条目。 - Jester
1
movb $0x4,0x0 将字节值 4 存储到绝对地址 0 的内存中。movb $0x4,0x0(%rip) 将字节值 4 存储到绝对地址 rip + 0,即相对于 RIP 地址为 0 的地方。这与使用其他寄存器进行寻址相同,例如 movb $4,0(%edi)。区别在于,rip 在评估时指向下一条指令的开头。因此,使用 rip 进行相对寻址允许编译器生成“PIC”位置无关代码。然后,操作系统需要将数据和代码一起加载以保持它们相对于彼此的位置不变。 - Ped7g
1
如果在PIC代码中没有rip,您将无法确定数据的位置,并且您仍然需要加载rip来查看代码的位置,并通过它来调整寻址。因此,让编译器+链接器使用类似于variable_x(%rip)的助记符自动重新计算所有偏移量,可以使程序员更轻松地使代码与PIC兼容。通常以PIC兼容方式编译x86_64目标代码(在某些操作系统中,如Mac的OS X中是强制性的),而32位x86目标通常使用绝对代码,期望在内存中具有特定位置。 - Ped7g
如果您强制编译器即使针对32位目标也生成类似PIC的代码,它可能也会使用rip(当然是32位的eip变体,64位的rip在32位模式下不可用)。 - Ped7g
1
是的,我忘记了“基础”...在32位模式下,你不能通过eip寻址,所以代码使用call调用本地函数来从堆栈(返回地址)读取代码位置(call执行时的eip值),然后使用这个值相对于代码位置来寻址数据。编译器的好处就在于它们不会忘记基础知识... ;) :D - Ped7g
显示剩余9条评论
1个回答

22

RIP寻址始终相对于RIP(64位指令指针)寄存器。因此,它只能用于全局变量。0偏移量等于在以RIP寻址的指令之后的下一条指令的地址。例如:

   mov  al,[rip+2]                     al=53
   jmp  short next   (length=2 bytes)   
db 53
next:
   mov  bl,[rip-7]   (length=6 bytes)  bl=53

通常情况下,您不会直接将数据混合到代码中,除非它是一个立即数,但这展示了如果您使用非常小的偏移值运行代码会发生什么。

在您的代码中,您无法看到和检查偏移量(您会看到四个零),因为您已对.o进行了反汇编。请使用objdump -drwC在反汇编时显示符号名称/重定位。当您将此对象链接到可执行文件时,链接器将填充它们。


相对于`rbp`存取局部变量的示例:

push rbp      ;save rbp
mov rbp,rsp   ;rbp = pointer to return address (8 bytes)
sub rsp,64    ;reserve 64 bytes for local variables
mov rax,[rbp+8];  rax = the last stack-passed qword parameter (if any)
mov rdx,[rbp];    rdx = return address
mov rcx,[rbp-8];  rcx = first qword local variable (this is undefined now)
mov r8, [rbp-16];  r8  = second qword local variable (this is undefined now)
.
.
mov rsp,rbp
pop rbp
ret

4
实际上,它们将在链接时被填充;看起来提问者反汇编了一个.o文件而不是一个已链接的可执行文件。位置无关代码不需要每次加载时进行运行时修补;这是使用相对于 RIP 的寻址的一个重要优势之一。 - Peter Cordes

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