将内存与 GNU 中的常量区分开来。intel_syntax

Distinguishing memory from constant in GNU as .intel_syntax

我有一条用 Intel 语法编写的指令(使用 gas 作为我的汇编程序),如下所示:

mov rdx, msg_size
...
msg: .ascii "Hello, world!\n"
     .set msg_size, . - msg

但是该 mov 指令被汇编为 mov 0xe,%rdx,而不是我所期望的 mov [=13=]xe,%rdx。我应该如何编写第一条指令(或 msg_size 的定义)以获得预期的行为?

使用mov edx, OFFSET symbol将符号“地址”作为立即数获取,而不是从中加载作为地址。这适用于实际标签地址以及您使用 .set.

设置为整数的符号

对于 64 位代码中的 msg 地址(不是 msg_size assemble-时间常数),您可能需要
lea rdx, [RIP+msg] 对于静态地址不适合 32 位的 PIE 可执行文件。


在 GAS .intel_syntax noprefix 模式下:

  • OFFSET symbol 的工作方式类似于 AT&T $symbol。这有点像MASM。
  • symbol 对于未知符号的工作方式类似于 AT&T symbol(即取消引用)。
  • [symbol] 在 GAS 和 NASM/YASM 中始终是一个有效地址,从不是立即数。 LEA 不从地址加载,但它仍然使用内存操作数机器编码。 ().

bare symbol 的解释取决于声明的顺序

GAS 是单程 assembler(回溯并填充 已知符号值)。

它在第一次遇到该行时决定 mov rdx, symbol 的操作码和编码。 earlier msize= . - msg.equ / .set 将使其选择 mov reg, imm32,但稍后的指令尚不可见。

尚未定义的符号的默认假设是 symbol 是某个部分中的地址(就像您使用 symbol: 或 [=34 这样的标签定义它一样=]).并且因为 GAS .intel_syntax 就像 MASM 而不是 NASM,所以裸符号被视为 [symbol] - 内存操作数。

如果您在文件顶部放置 .setmsg_length=msg_end - msg 指令,在引用它的指令之前,它们会 assemble 到 mov reg, imm32 mov-即时。 (不像在 AT&T 语法中,你 总是 需要一个 $ 作为立即数,即使对于像 1234 这样的数字文字也是如此。)

例如:源代码和反汇编交错 objdump -dS:
gcc -g -c foo.s 和 disassembled 用 objdump -drwC -S -Mintel foo.o 组装(用 as --version = GNU assembler (GNU Binutils) 2.34)。我们得到这个:

0000000000000000 <l1>:
.intel_syntax noprefix

l1:     
mov eax, OFFSET equsym
   0:   b8 01 00 00 00          mov    eax,0x1
mov eax, equsym            #### treated as a load
   5:   8b 04 25 01 00 00 00    mov    eax,DWORD PTR ds:0x1
mov rax, big               #### 32-bit sign-extended absolute load address, even though the constant was unsigned positive
   c:   48 8b 04 25 aa aa aa aa         mov    rax,QWORD PTR ds:0xffffffffaaaaaaaa
mov rdi, OFFSET label
  14:   48 c7 c7 00 00 00 00    mov    rdi,0x0  17: R_X86_64_32S        .text+0x1b

000000000000001b <label>:

label:
nop
  1b:   90                      nop

.equ equsym, . - label            # equsym = 1
big = 0xaaaaaaaa

mov eax, OFFSET equsym
  1c:   b8 01 00 00 00          mov    eax,0x1
mov eax, equsym           #### treated as an immediate
  21:   b8 01 00 00 00          mov    eax,0x1
mov rax, big              #### constant doesn't fit in 32-bit sign extended, assembler can see it when picking encoding so it picks movabs imm64
  26:   48 b8 aa aa aa aa 00 00 00 00   movabs rax,0xaaaaaaaa

使用 mov edx, OFFSET msg_size 将任何符号(甚至数字文字)视为立即数总是安全的,无论它是如何定义的。所以它与 AT&T $ 完全一样,除了当 GAS 已经知道符号值只是一个数字而不是某个部分中的地址时它是可选的。 为了保持一致性,始终使用 OFFSET msg_size 可能是个好主意,这样您的代码就不会改变含义 如果未来的某些程序员移动代码,那么数据部分和相关指令将不再存在第一的。 (包括未来的你,你忘记了这些不同于大多数 assemble 的奇怪细节。)

顺便说一句,.set is a synonym for .equ, and there's also symbol=value syntax 用于设置与 .set 同义的值。


操作数大小:一般使用32位,除非一个值需要64位

mov rdx, OFFSET symbol 将 assemble 变为 mov r/m64, sign_extended_imm32。你不希望它的长度很小(远小于 4GiB),除非它是一个负常数,而不是地址。您也不希望 movabs r64, imm64 作为地址;那是低效的。

在 GNU/Linux 下在位置相关的可执行文件中写入 mov edx, OFFSET symbol 是安全的,事实上你应该总是这样做或使用 lea rdx, [rip + symbol],永远不要符号扩展 32 位立即执行,除非您编写的代码将加载到高 2GB 的虚拟地址 space(例如内核)中。

另请参阅 32-bit absolute addresses no longer allowed in x86-64 Linux? 以了解更多有关 PIE 可执行文件成为现代发行版默认值的信息。


提示:如果您了解 AT&T 或 NASM 语法,或 NASM 语法,请使用它来生成您想要的编码,然后使用 objdump -Mintel 找出 .intel_syntax noprefx.

的正确语法

但这对这里没有帮助,因为反汇编只会显示 mov edx, 123 之类的数字文字,而不是 mov edx, OFFSET name_not_in_object_file。查看 gcc -masm=intel 编译器输出也有帮助,但编译器再次进行自己的常数传播,而不是使用 assemble 时间常数的符号。

顺便说一句,据我所知,没有开源项目包含 GAS intel_syntax 源代码。如果他们使用 gas,他们使用 AT&T 语法。否则他们使用 NASM/YASM。 (您有时还会在开源项目中看到 MSVC 内联 asm)。


在 AT&T 语法或 [RIP + symbol]

中效果相同

这更加人为,因为您通常不会使用不是地址的整数常量来执行此操作。我将其包含在这里只是为了展示 GAS 行为的另一个方面,这取决于在其 1 遍期间的某个点是否定义了一个符号。

How do RIP-relative variable references like "[RIP + _a]" in x86-64 GAS Intel-syntax work? - [RIP + symbol]被解释为使用相对寻址到达symbol,而不是实际添加两个地址。但是 [RIP + 4] 是从字面上理解的,作为相对于该指令末尾的偏移量。

因此,当 GAS 到达引用它的指令时,GAS 对符号的了解很重要,因为它是 1-pass。如果未定义,它假定它是一个普通符号。如果定义为没有关联部分的数值,则它的作用类似于文字数字。

_start:
foo=4
jmpq *foo(%rip)
jmpq *bar(%rip)
bar=4

第一个跳转的 assemble 与 jmp *4(%rip) 从当前指令末尾后 4 个字节加载指针相同。但是第二次跳转使用符号重定位bar,使用RIP-relative寻址模式到达符号bar的绝对地址,无论结果如何。

0000000000000000 <.text>:
   0:   ff 25 04 00 00 00       jmp    QWORD PTR [rip+0x4]        # a <.text+0xa>
   6:   ff 25 00 00 00 00       jmp    QWORD PTR [rip+0x0]        # c <bar+0x8> 8: R_X86_64_PC32        *ABS*

ld foo.o 链接后,可执行文件具有:

  401000:       ff 25 04 00 00 00       jmp    *0x4(%rip)        # 40100a <bar+0x401006>
  401006:       ff 25 f8 ef bf ff       jmp    *-0x401008(%rip)        # 4 <bar>