我有一条用 Intel 语法编写的指令(使用 gas 作为我的汇编程序),如下所示:
mov rdx, msg_size
...
msg: .ascii "Hello, world!\n"
.set msg_size, . - msg
但是正如我所期望的那样,该 mov 指令正在组装为mov 0xe,%rdx
,而不是。mov $0xe,%rdx
我应该如何编写第一条指令(或 的定义msg_size
)以获得预期的行为?
我有一条用 Intel 语法编写的指令(使用 gas 作为我的汇编程序),如下所示:
mov rdx, msg_size
...
msg: .ascii "Hello, world!\n"
.set msg_size, . - msg
但是正如我所期望的那样,该 mov 指令正在组装为mov 0xe,%rdx
,而不是。mov $0xe,%rdx
我应该如何编写第一条指令(或 的定义msg_size
)以获得预期的行为?
用于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 使用相同的语法)。symbol
取决于声明的顺序GAS 是一个一次性的汇编器(一旦知道符号值,它就会返回并填充符号值)。
mov rdx, symbol
它决定第一次遇到该行时的操作码和编码。较早的 msize= . - msg
or .equ
/.set
将使其成为 select mov reg, imm32
,但较晚的指令尚不可见。
尚未定义符号的默认假设symbol
是某个部分中的地址(就像您使用标签定义它得到的一样symbol:
,或 from .set symbol, .
)。而且因为 GAS.intel_syntax
就像 MASM 而不是 NASM,所以一个裸符号被视为[symbol]
- 一个内存操作数。
如果您将.set
ormsg_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
(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
。
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 内联汇编)。
[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>