15

考虑 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
4

1 回答 1

17

RIP 相对寻址的 GAS 语法看起来像symbol + current_address(RIP),但它实际上意味着symbol 相对于 RIP.

与数字文字不一致:

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

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

  • [a]或 AT&Ta是绝对地址,使用 disp32 寻址模式。这在 OS X 上不受支持,其中图像基地址始终在低 32 位之外。(或者对于mov到/从 al/ax/eax/rax,可以使用 64 位绝对moffs编码,但您不希望这样)。

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

(在 OS X 中,约定是 C 变量/函数名称_以 asm 开头。在手写 asm 中,您不必不想从 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>

(反汇编.owithobjdump -dr会告诉你没有任何重定位供链接器填写,它们都是在汇编时完成的。)

请注意,这只.set z, sym导致了相对于计算。 x并且y是原始数字文字,而不是标签,所以即使指令本身使用[x + RIP]了 ,我们仍然得到[RIP + 8].


(仅限 Linux 非 PIE):解决绝对问题8。RIP,你需要 AT&T 语法incb 8-.(%rip)。我不知道如何在 GAS 中编写它intel_syntax[8 - . + RIP]被拒绝Error: invalid operands (*ABS* and .text sections) for '-'

当然,你不能在 OS X 上这样做,除非绝对地址在图像库的范围内。但是可能没有重定位可以容纳要为 32 位 rel32 计算的 64 位绝对地址。


有关的:

于 2019-02-18T12:53:06.220 回答