9

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

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

但是正如我所期望的那样,该 mov 指令正在组装为mov 0xe,%rdx,而不是。mov $0xe,%rdx我应该如何编写第一条指令(或 的定义msg_size)以获得预期的行为?

4

1 回答 1

13

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

对于64 位代码中的msg地址(不是汇编时常量),您可能需要静态地址不适合 32 位的 PIE 可执行文件。 如何将函数或标签的地址加载到寄存器中msg_size
lea rdx, [RIP+msg]


在气体.intel_syntax noprefix模式下:

  • OFFSET symbol像 AT&T 一样工作$symbol。这有点像 MASM。
  • symbol像 AT&T symbol(即取消引用)一样用于未知符号。
  • [symbol]在 GAS 和 NASM/YASM 中,它始终是有效地址,而不是直接地址。 LEA不从地址加载,但它仍然使用内存操作数机器编码。(这就是为什么 lea 使用相同的语法)。

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

GAS 是一个一次性的汇编器(一旦知道符号值,它就会返回并填充符号值)。

mov rdx, symbol它决定第一次遇到该行时的操作码和编码。较早 msize= . - msgor .equ/.set将使其成为 select mov reg, imm32,但较晚的指令尚不可见。

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

如果您将.setormsg_length=msg_end - msg指令放在文件的顶部,在引用它的指令之前,它们将组装为mov reg, imm32mov-immediate。(与 AT&T 语法不同,即使对于像 . 这样的数字文字,您也总是需要一个$立即数1234。)

例如:源代码和反汇编代码交错使用objdump -dS
汇编gcc -g -c foo.s和反汇编代码objdump -drwC -S -Mintel foo.o(with 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这样如果未来的程序员移动代码,数据部分和相关指令不再是第一位的,你的代码就不会改变含义。(包括那些忘记了这些与大多数汇编程序不同的奇怪细节的未来的你。)

顺便说一句,.set是 的同义词.equ,并且还有用于设置值的symbol=value语法,这也是 . 的同义词.set


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

mov rdx, OFFSET symbol将组装到mov r/m64, sign_extended_imm32. 除非它是负常数,而不是地址,否则您不希望它的长度很小(远小于 4GiB)。你也不想要movabs r64, imm64地址;那是低效的。

在 GNU/Linux 下编写mov edx, OFFSET symbol依赖于位置的可执行文件是安全的,实际上您应该始终这样做或使用lea rdx, [rip + symbol],永远不要符号扩展 32 位立即数,除非您正在编写将加载到高 2GB 虚拟空间中的代码地址空间(例如内核)。 如何将函数或标签的地址加载到寄存器中

另请参阅x86-64 Linux 中不再允许使用 32 位绝对地址?有关 PIE 可执行文件是现代发行版中默认设置的更多信息。


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

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

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


AT&T 语法中的相同效果,或[RIP + symbol]

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

x86-64 GAS Intel 语法中的“[RIP + _a]”等 RIP 相对变量引用如何工作?-[RIP + symbol]被解释为使用相对寻址来达到symbol,而不是实际添加两个地址。但是[RIP + 4]从字面上理解,作为相对于该指令结尾的偏移量。

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

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

jmp *4(%rip)这汇编到第一次跳转与从当前指令末尾之后的 4 个字节加载指针相同。但是第二次跳转使用符号重定位bar,使用 RIP 相对寻址模式到达符号的绝对地址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>
于 2016-09-07T03:06:51.657 回答