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

Posted

技术标签:

【中文标题】x86-64 GAS Intel 语法中的 RIP 相对变量引用(如“[RIP + _a]”)如何工作?【英文标题】:How do RIP-relative variable references like "[RIP + _a]" in x86-64 GAS Intel-syntax work? 【发布时间】:2019-07-11 18:05:55 【问题描述】:

考虑 x64 英特尔程序集中的以下变量引用,其中变量 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

【问题讨论】:

哪个汇编程序接受mov eax, dword ptr [rip + _a]?马斯姆?如果是这样,它可能会使用正确的偏移量使rip + _a 指向_a(即它不会使用_a 的地址)。在 NASM 中,您使用 mov eax, DWORD [REL _a](或者您将其设置为默认值)。在编写程序集时,RIP 相关的东西用于“计算相对于 RIP 的地址”而不是“将这个特定的偏移量添加到 RIP”中,因为您几乎永远不知道您的代码将在哪里。 @MargaretBloom - 感谢您的回复。请参阅我更新的源代码问题。确实,我猜地址是相对于rip 寄存器的;但是,语法并没有反映出那么好,不是吗?所以,你说的是加载器在运行时将[rip + _a]替换为a的绝对地址;还是将_a 替换为a(可能为负)w.r.t 相对于指令地址(mov rax, dword ptr [rip + _a])的相对偏移量? 编辑后:这只是反汇编符号。它承载了使用 RIP 相对寻址和_a 是最终目标的事实。检查操作码,你会看到。这确实是一种误导性的符号。 @MargaretBloom - 非常感谢。 【参考方案1】:

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

数字字面量不一致:

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

[rip + a] 或 AT&T a(%rip) 表示计算rel32 位移以达到a不是 RIP + 符号值。 (GAS手册documents this特别解读)

[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]。 Unable to move variables in .data to registers with Mac x86 Assembly.

(在 OS X 中,约定是 C 变量/函数名称在 asm 中以 _ 开头。在手写 asm 中,您没有必须为您不使用的符号执行此操作'不想从 C 访问。)


NASM 在这方面的困惑要少得多:

[rel a] 表示[a] 的 RIP 相对寻址 [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):使用绝对地址 8 wrt。 RIP,你需要 AT&T 语法incb 8-.(%rip)。我不知道如何在 GAS intel_syntax 中写这个; [8 - . + RIP]Error: invalid operands (*ABS* and .text sections) for '-' 拒绝。

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


相关:

How to load address of function or label into registerAT&T 版本 32-bit absolute addresses no longer allowed in x86-64 Linux? PIE 与非 PIE 可执行文件,当您必须使用与位置无关的代码时。

【讨论】:

我会在 nasm [rel a] 中使用 [rip + a] 或 AT&T a(%rip) 并且对于这个 [rip + 10] 我假设 [rel 10]' and [rip + 10] 表示该指令结束后的 10 个字节。我没有清楚地理解这一点。假设我在名为var 的.data 部分中定义了一个变量,如何使用这个[rel ?] 访问var 使用什么数字?如何识别它是多少个字节之前?或此语法用于任何其他目的。 或者对于像 mov rax 这样的代码行有任意数量的内存,100 是一个字节,下一行是一个字节?

以上是关于x86-64 GAS Intel 语法中的 RIP 相对变量引用(如“[RIP + _a]”)如何工作?的主要内容,如果未能解决你的问题,请参考以下文章

将 scanf 与 x86-64 GAS 程序集一起使用

rel8 立即操作数的 x86-64 英特尔语法?

如何对 AT&T 语法中的标签进行相对 jmp?

linux发行版中的i386/i686/x86-64/有啥区别?

如何将此代码从 Intel(nasm) 转换为 AT&T(gas) 语法?

X.86 X64 汇编器中的正确堆栈操作