x86-64 GAS Intel语法中的RIP相对变量引用如"[RIP + _a]"是如何工作的?

17

考虑以下x64 Intel汇编中的变量引用,其中变量a.data部分声明:

mov eax, dword ptr [rip + _a]

我不理解这个变量引用是如何工作的。因为 a 是指向变量运行时地址的符号(带有重定位),那么 [rip + _a] 如何解引用正确的 a 内存位置呢?实际上,rip 保存当前指令的地址,它是一个大正整数,所以加法会得到一个错误的 a 地址。

相反,如果我使用 x86 语法(非常直观):

mov eax, dword ptr [_a]

我遇到了以下错误:在64位模式下不支持32位绝对寻址

有什么解释吗?

  1 int a = 5;
  2 
  3 int main() {
  4     int b = a;
  5     return b;
  6 }   

编译:gcc -S -masm=intel abs_ref.c -o abs_ref

  1     .section    __TEXT,__text,regular,pure_instructions
  2     .build_version macos, 10, 14
  3     .intel_syntax noprefix
  4     .globl  _main                   ## -- Begin function main
  5     .p2align    4, 0x90
  6 _main:                                  ## @main
  7     .cfi_startproc
  8 ## %bb.0:
  9     push    rbp
 10     .cfi_def_cfa_offset 16
 11     .cfi_offset rbp, -16
 12     mov rbp, rsp
 13     .cfi_def_cfa_register rbp
 14     mov dword ptr [rbp - 4], 0
 15     mov eax, dword ptr [rip + _a]
 16     mov dword ptr [rbp - 8], eax
 17     mov eax, dword ptr [rbp - 8]
 18     pop rbp
 19     ret
 20     .cfi_endproc
 21                                         ## -- End function
 22     .section    __DATA,__data
 23     .globl  _a                      ## @a
 24     .p2align    2
 25 _a:
 26     .long   5                       ## 0x5
 27 
 28 
 29 .subsections_via_symbols

2
哪个汇编器接受 mov eax, dword ptr [rip + _a]?MASM?如果是的话,它可能会使用正确的偏移量使 rip + _a 指向 _a(即它不会使用 _a 的地址)。在NASM中,您可以使用 mov eax,DWORD [REL _a](或将其设置为默认值)。在编写汇编代码时,RIP相对位置用于“相对于RIP计算此地址”,而不是“将此特定偏移量添加到RIP”,因为您几乎永远不知道您的代码将在何处。 - Margaret Bloom
@MargaretBloom - 感谢您的回复。请查看我的更新问题及源代码。实际上,我猜测寻址将是相对于rip寄存器的;但是,语法并没有很好地反映出来,不是吗?所以,您的意思是加载程序会在运行时用a的绝对地址替换[rip + _a];还是_a会被替换为指令地址(mov rax, dword ptr [rip + _a])与a的相对偏移量(可能是负数)? - Shuzheng
3
这只是反汇编符号表示法。它既表示正在使用 RIP 相关寻址,又表示 _a 是最终目标。检查操作码即可看到。这的确是一种误导性的表示法。 - Margaret Bloom
@MargaretBloom - 非常感谢您。 - Shuzheng
1个回答

21

GAS语法中的RIP相对寻址看起来像是symbol + current_address(RIP),但实际上它表示的是RIP相对于symbol

数字字面量存在不一致性:

  • [rip + 10]或AT&T 10(%rip) 表示该指令结束后的10个字节。

  • [rip + a]或AT&T a(%rip) 表示计算一个rel32位移来到达a,而不是RIP + 符号值。(GAS手册记录了这种特殊解释)

  • [a]或AT&T a 是一个绝对地址,使用disp32寻址模式。在OS X上不支持这种方式,因为镜像基地址总是在低32位之外。(或者对于mov到/从al/ax/eax/rax,有一个64位绝对moffs编码可用,但您不需要它)。

    Linux位置相关的可执行文件将静态代码/数据放在虚拟地址空间的低31位(2GiB)中,因此您可以/应该在那里使用mov edi, sym,但在OS X上,如果需要在寄存器中获得地址,则最好选择lea rdi, [sym+RIP]无法使用Mac x86 Assembly将变量从.data移动到寄存器

在OS X中,惯例是将C变量/函数名用_作为前缀。在手写汇编中,您不必为不想从C访问的符号添加此前缀{{_}}。

NASM在这方面要简单得多:

  • [rel a] 意味着使用RIP相对寻址访问 [a]
  • [abs a] 意味着使用 [disp32].
  • default reldefault abs 设置用于 [a] 的默认选项。不幸的是,默认设置是 default abs,所以你几乎总是想要一个 default rel

使用.set符号值与标签的示例

.intel_syntax noprefix
mov  dword ptr [sym + rip], 0x11111111
sym:

.equ x, 8 
inc  byte ptr [x + rip]

.set y, 32 
inc byte ptr [y + rip]

.set z, sym
inc byte ptr [z + rip]

gcc -nostdlib foo.s && objdump -drwC -Mintel a.out(在Linux上;我没有OS X):

0000000000001000 <sym-0xa>:
    1000:       c7 05 00 00 00 00 11 11 11 11   mov    DWORD PTR [rip+0x0],0x11111111        # 100a <sym>    # rel32 = 0; it's from the end of the instruction not the end of the rel32 or anywhere else.

000000000000100a <sym>:
    100a:       fe 05 08 00 00 00       inc    BYTE PTR [rip+0x8]        # 1018 <sym+0xe>
    1010:       fe 05 20 00 00 00       inc    BYTE PTR [rip+0x20]        # 1036 <sym+0x2c>
    1016:       fe 05 ee ff ff ff       inc    BYTE PTR [rip+0xffffffffffffffee]        # 100a <sym>

使用objdump -dr反汇编.o文件,你会发现没有任何需要链接器填充的重定位项,它们都在汇编时完成了。
请注意,只有.set z, sym才产生了一个相对于符号的计算。变量xy最初是普通的数字字面值,而不是标签,因此即使指令本身使用了[x + RIP],我们仍然得到了[RIP + 8]
(仅适用于Linux非PIE):要解决相对于RIP的绝对8问题,需要使用AT&T语法incb 8-.(%rip)。 我不知道如何在GAS intel_syntax中编写该代码; [8 - . + RIP]被拒绝,并显示错误:无效操作数(* ABS *和.text部分)“-”。 当然,在OS X上你无法这样做,除非绝对地址处于图像基址范围内。 但是,可能没有重定位可以保存32位rel32计算的64位绝对地址。

相关:


我会在nasm中使用[rel a]来表示[rip + a]或AT&T a(%rip),对于这个[rip + 10],我假设是[rel 10]和[rip + 10]表示这条指令结束后的10个字节。我没有完全理解这个。假设我在.data部分定义了一个名为var的变量,如何使用[rel?]访问var?要使用哪个数字?如何确定它之前有多少个字节?还是这种语法用于其他目的? - srilakshmikanthanp
代码行所需要的内存的数量是否是固定的,比如 mov rax, 100 这一行是一字节,下一行也是一字节? - srilakshmikanthanp
在NASM语法中,mov eax,[rel var]使用RIP相对寻址访问var。或者在文件中的某个地方使用default rel,这样mov eax,[var]就会使用RIP相对寻址。除非您已经有了知道偏移量的特定原因,否则永远不要手动使用数字偏移量。只需在某个地方放置一个标签并引用它即可。当然,您可以查看反汇编或来自nasm -l/dev/stdout -felf64 foo.asm的列表以查看指令长度。 - Peter Cordes

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