使用mov edx, OFFSET symbol 获取符号“地址”作为立即数,而不是从它作为地址加载。这适用于实际标签地址以及您使用.set 设置为整数的符号。
对于 64 位代码中的 msg 地址(不是 msg_size 汇编时间常量),您可能需要
lea rdx, [RIP+msg] 用于静态地址不具有的 PIE 可执行文件' t 适合 32 位。 How to load address of function or label into register
在 GAS .intel_syntax noprefix 模式下:
-
OFFSET symbol 的工作方式类似于 AT&T $symbol。这有点像 MASM。
-
symbol 的作用类似于 AT&T symbol(即取消引用),用于未知符号。
-
[symbol] 在 GAS 和 NASM/YASM 中始终是有效地址,而不是直接地址。 LEA 不会从地址加载,但它仍然使用内存操作数机器编码。 (That's why lea uses the same syntax)。
对裸symbol的解释取决于声明的顺序
GAS 是一次性汇编器(回过头来填充
已知的符号值)。
它在第一次遇到该行时决定mov rdx, symbol 的操作码和编码。 较早的 msize= . - msg 或 .equ / .set 将使其选择 mov reg, imm32,但稍后的指令将不可见。
未定义符号的默认假设是 symbol 是某个部分中的地址(就像您使用 symbol: 之类的标签定义它或从 .set symbol, . 中获得的一样)。而且因为 GAS .intel_syntax 类似于 MASM 而不是 NASM,所以将裸符号视为 [symbol] - 一个内存操作数。
如果您将.set 或msg_length=msg_end - msg 指令放在文件顶部,在引用它的指令之前,它们将汇编为mov reg, imm32 mov-immediate。 (与 AT&T 语法不同,您总是需要一个 $ 来表示立即数,即使对于像 1234 这样的数字文字也是如此。)
例如:源代码和反汇编代码与objdump -dS 交错:
用gcc -g -c foo.s 组装并用objdump -drwC -S -Mintel foo.o 反汇编(as --version = GNU 汇编器 (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 syntax 用于设置与 .set 同义的值。
Operand-size:一般使用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],除非您编写的代码将被加载到高 2GB 的虚拟地址空间(例如内核)中。 How to load address of function or label into register
另请参阅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 编译器输出也有帮助,但编译器再次执行自己的常量传播,而不是使用符号作为汇编时常量。
顺便说一句,据我所知,没有任何开源项目包含 GAS intel_syntax 源代码。如果他们使用天然气,他们使用 AT&T 语法。否则他们使用 NASM/YASM。 (您有时还会在开源项目中看到 MSVC 内联汇编)。
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 到达引用它的指令时,它对符号的了解很重要,因为它是 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>