pop
命令)。 我如何引用内存位置的内容,以便可以进行比较?基本上,语法上我该如何操作。
pop
命令)。 我如何引用内存位置的内容,以便可以进行比较?当然,英特尔和AMD的手册都有关于ModRM编码(以及可选的SIB和disp8/disp32字节)的细节部分,这使得可编码的内容和为什么存在限制非常清晰。
另请参阅:AT&T(GNU)语法与NASM语法在不同寻址模式下的表格,包括间接跳转/调用。此外,请参阅本答案底部的链接收藏。
x86(32位和64位)有几种可供选择的寻址模式。它们都采用以下形式:
[base_reg + index_reg*scale + displacement] ; or a subset of this
[RIP + displacement] ; or RIP-relative: 64bit only. No index reg is allowed
[rsi]
。[5 + rax + rsp + 15 * 4 + MY_ASSEMBLER_MACRO * 2]
可以正常工作。(所有常量上的数学运算在汇编时发生,结果为单个常量位移。)movsxd
将32位可能为负的偏移量扩展为64位指针宽度。al
作为数组索引},则需要将其零扩展或符号扩展到指针宽度。(在操作字节寄存器之前已经将rax
的上位比特清零有时是可能的,并且是实现这一目标的好方法。)
[rsp]
总是需要一个SIB字节。
除了使用e/rsp*scale
的情况(在“正常”代码中始终将指向堆栈内存的指针保留在esp
中)之外,每种可能的一般情况子集都是可编码的。
通常,编码的代码大小为:
[-128到+127]
的位移可以使用更紧凑的disp8
编码,与disp32
相比节省3个字节。ModRM始终存在,其位表示SIB是否也存在。 disp8 / disp32同理。 代码大小异常:
[reg*scale]
本身只能使用32位位移进行编码(当然可以为零)。聪明的汇编器通过将 lea eax, [rdx*2]
编码为 lea eax, [rdx + rdx]
来解决这个问题,但这个技巧只适用于以2为倍数缩放。无论哪种方式,都需要一个SIB字节,除了ModRM。
如果没有位移字节,就不可能将 e/rbp
或 r13
编码为基础寄存器,因此 [ebp]
被编码为 [ebp + byte 0]
。没有位移的编码中,ebp
作为基础寄存器的意思是根本没有基础寄存器(例如对于 [disp + reg*scale]
)。
[e/rsp]
即使没有索引寄存器也需要一个SIB字节。(无论是否有位移)。指定 [rsp]
的mod / rm编码表示有一个SIB字节。
(此问题标记为MASM,但部分答案涉及NASM的Intel语法版本,特别是在x86-64 RIP相对寻址方面的区别。未涵盖AT&T语法,但请记住这只是同一机器码的另一种语法,因此限制是相同的。)
此表并不完全匹配可能的寻址模式的硬件编码,因为我区分了使用标签(例如全局或静态数据)与使用小常量位移。因此,我涵盖了硬件寻址模式+链接器支持符号。
(注意:通常情况下,当源为字节时,您会想要使用movzx eax,byte [esi]
或movsx
,但mov al,byte_src
可以组装,并且在旧代码中很常见,合并到EAX / RAX的低字节中。请参见为什么GCC不使用部分寄存器?和如何分离64位寄存器中的字节和字数组元素)
mov al, esi
:无效,不会汇编。没有方括号,根本不是加载操作。这是一个错误,因为寄存器的大小不同。
mov al, [esi]
加载指向的字节,即array[0]
或*array
。
mov al, [esi + ecx]
加载array[ecx]
。
mov al, [esi + 10]
加载array[10]
。
mov al, [esi + ecx*8 + 200]
加载array[ecx*8 + 200]
mov al, [global_array + 10]
从global_array[10]
中加载。在64位模式下,这可以且应该是一个RIP相对地址。建议使用NASM的DEFAULT REL
,默认生成RIP相对地址,而不必总是使用[rel global_array + 10]
。我认为MASM默认这样做。没有直接使用索引寄存器的RIP相对地址的方法。正常的方法是lea rax, [global_array]
mov al, [rax + rcx*8 + 10]
或类似的方法。
有关更多详细信息和GAS .intel_syntax
,NASM和GAS AT&T语法的语法,请参见How do RIP-relative variable references like "[RIP + _a]" in x86-64 GAS Intel-syntax work?。
mov al, [global_array + ecx + edx*2 + 10]
从global_array[ecx + edx*2 + 10]
中加载。显然,您可以使用单个寄存器索引静态/全局数组。即使使用两个单独的寄存器来处理二维数组也是可能的(通过额外指令进行预缩放,以获得比2、4或8更大的比例因子)。请注意,global_array + 10
的计算是在链接时完成的。对象文件(汇编器输出,链接器输入)通知链接器要添加到最终绝对地址中的+10,以将正确的4字节位移放入可执行文件(链接器输出)。这就是you can't use arbitrary expressions on link-time constants that aren't assemble-time constants(例如符号地址)的原因。
在64位模式下,这仍然需要将global_array
作为32位绝对地址用于disp32
部分,该部分仅适用于position-dependent Linux executable或largeaddressaware=no Windows。
mov al, 0ABh
根本不是加载操作,而是存储在指令内的立即常量。(请注意,您需要在前面加上0
,以便汇编器知道它是一个常量,而不是符号。一些汇编器也将接受0xAB
,其中一些不会接受0ABh
:see more)。
您可以使用符号作为立即常量,将地址放入寄存器:
mov esi, global_array
汇编成将地址放入esi的mov esi, imm32
。mov esi, OFFSET global_array
来做同样的事情。mov
任何一种寻址模式都可以与LEA
一起使用,进行整数运算而不影响标志位,无论它是否是一个有效的地址。 在非地址/指针上使用LEA?[esi*4 + 10]
通常只有在LEA中才有用(除非位移是符号,而不是小常量)。在机器代码中,没有单独编码缩放寄存器,因此[esi*4]
必须汇编为[esi*4 + 0]
,用于32位位移的4个字节零。尽管如此,通常复制+移位只需一条指令,而不是更短的mov + shl,因为通常uop吞吐量更受限于代码大小,特别是在具有解码的uop缓存的CPU上。
你可以像这样指定段覆盖:mov al, fs:[esi]
(NASM语法)。 段覆盖只是在通常的编码前面添加一个前缀字节。 其他所有内容都保持不变,具有相同的语法。
您甚至可以在RIP相对寻址中使用段覆盖。 32位绝对寻址需要比RIP相对寻址多一个字节进行编码,因此mov eax, fs:[0]
可以使用生成已知绝对地址的相对位移来最有效地编码。 也就是说,选择rel32使RIP + rel32 = 0。 YASM将使用mov ecx,[fs:rel 0]
执行此操作,但是NASM始终使用disp32绝对寻址,并忽略rel
说明符。 我没有测试过MASM或gas。
如果操作数大小不明确(例如,在具有立即数和内存操作数的指令中),请使用 byte
/ word
/ dword
/ qword
来指定:
mov dword [rsi + 10], 123 ; NASM
mov dword ptr [rsi + 10], 123 ; MASM and GNU .intex_syntax noprefix
movl $123, 10(%rsi) # GNU(AT&T): operand size from mnemonic suffix
请参阅yasm文档,有关NASM-syntax有效地址的部分,和/或wikipedia x86条目中有关寻址模式的部分。维基页面说明了16位模式下允许的内容。这里是另一份32位寻址模式的“作弊表”。
16位寻址模式
16位地址大小不能使用SIB字节,因此所有单寄存器和双寄存器寻址模式都被编码到单个mod/rm字节中。 reg1
可以是BX或BP,reg2
可以是SI或DI(或者您可以单独使用这4个寄存器中的任何一个)。缩放不可用。由于许多原因,包括这个原因,16位代码已经过时,如果您不需要学习,就不值得去学。
请注意,在使用地址大小前缀时,16位限制适用于32位代码,因此16位LEA数学非常受限制。但是,您可以解决这个问题:lea eax,[edx + ecx * 2]
设置 ax = dx + cx * 2
,因为源寄存器的高位垃圾不会产生影响。
还有一个更详细的16位寻址模式指南。 16位有限的寻址模式(仅几个寄存器有效,没有比例因子),但您可能想阅读它以了解有关x86 CPU如何使用地址的一些基本原理,因为其中一些内容在32位模式下没有改变。
相关主题:
其中许多主题已在上文中链接,但并非全部。
请参阅SO x86 tag wiki页面,其中包含文档和参考手册的链接,包括英特尔的手册。
Intel-syntax 和 AT&T syntax 标签维基介绍了它们之间的区别,以及(对于英特尔)不同版本的英特尔语法。
Micro fusion and addressing modes 索引寻址模式在Sandybridge系列上的性能影响:除少数情况外,未解开。
Mach-O 64-bit format does not support 32-bit absolute addresses. NASM Accessing Array MacOS 64位寻址
32-bit absolute addresses no longer allowed in x86-64 Linux?(Linux PIE vs. position-dependent executables)
How do RIP-relative variable references like "[RIP + _a]" in x86-64 GAS Intel-syntax work?(也涵盖NASM和GAS AT&T)
How to load address of function or label into register in GNU Assembler 如何高效地将符号地址放入寄存器中,而不是直接在寻址模式中使用它们。
Why is the address of static variables relative to the Instruction Pointer?
and Why does this MOVSS instruction use RIP-relative addressing? - RIP相对寻址是从静态数据中加载/存储的标准有效方法,即使数据与代码不在同一部分(由于链接器/程序装载器的工作方式,相对偏移量保持不变,即使整个程序/库都是位置无关的)。
+------------------------+----------------------------+-----------------------------+
| Mode | Intel | AT&T |
+------------------------+----------------------------+-----------------------------+
| Absolute | MOV EAX, [0100] | movl 0x0100, %eax |
| Register | MOV EAX, [ESI] | movl (%esi), %eax |
| Reg + Off | MOV EAX, [EBP-8] | movl -8(%ebp), %eax |
| Reg*Scale + Off | MOV EAX, [EBX*4 + 0100] | movl 0x100(,%ebx,4), %eax |
| Base + Reg*Scale + Off | MOV EAX, [EDX + EBX*4 + 8] | movl 0x8(%edx,%ebx,4), %eax |
+------------------------+----------------------------+-----------------------------+
针对您的情况,如果该项位于栈基地址 EBP
的偏移量为 4
的位置,则应使用 Reg + Off
表示法:
MOV EAX, [ EBP - 4 ]
EAX
中。
[fs: 0]
可以使用一个32位的绝对模式编码,其中disp32 = 0,或者(比较短的1个字节)使用RIP + rel32,使得rel32被选择为RIP+rel32=0。仅在位置相关代码中,使用相对寻址来生成绝对地址才有效,因为代码地址必须是已知常量(在链接时已知)。而且必须与所需绝对地址相差不超过2GiB。 - Peter Cordes