引用内存位置的内容。 (x86寻址模式)
我有一个内存位置包含一个字符,我想与另一个字符比较(它不在堆栈的顶部,所以我不能只是pop
它)。 我如何引用一个内存位置的内容,以便我可以比较它?
基本上,我怎样才能在语法上做到这一点。
有关寻址模式(16/32 / 64bit)的更多扩展讨论,请参阅Agner Fog的“优化assembly”指南 ,第3.3节。 该指南比这个答案更详细的重新定位符号和/或32位位置无关的代码,等等。
另请参阅: AT&T(GNU)语法与不同寻址模式 (包括间接跳转/调用)的NASM语法表 。
另请参阅此答案底部的链接集合。
build议欢迎,尤其是 哪些部分是有用/有趣的,哪些部分不是。
x86(32和64位)有几种寻址模式可供select。 他们都是这样的forms:
[base_reg + index_reg*scale + displacement] ; or a subset of this [RIP + displacement] ; or RIP-relative: 64bit only. No index reg is allowed
(其中比例是1,2,4或8,位移是有符号的32位常量)。 所有其他forms(RIP相对除外)都是这样的子集,省略了一个或多个组件 。 这意味着你不需要清零index_reg
来访问[rsi]
。 在asm源代码中,你写什么命令并不重要: [5 + rax + rsp + 15*4 + MY_ASSEMBLER_MACRO*2]
工作正常。 (所有关于常量的math都是在汇编时发生的,导致一个单一的常量位移。)
寄存器必须与您所在的模式大小相同,除非您使用备用地址大小 ,需要额外的前缀字节。 窄指针在x32 ABI(长模式下的ILP32)之外很less有用。
例如 ,如果要将al
用作数组索引,则需要将其扩展为指针宽度。 (在将字节寄存器搞乱之前,将rax
的高位已经清零是有可能的,并且是实现这一点的好方法。)
一般情况下,每个可能的子集都是可编码的,除了那些使用e/rsp*scale
(在“普通”代码中显然没有用的,在esp
中始终保持指向堆栈内存的指针)。
通常,编码的代码大小是:
- 1B用于单寄存器模式(mod / rm(模式/寄存器或存储器))
- 2B用于双寄存器模式(mod / rm + SIB(Scale Index Base)字节)
- 位移可以是0,1或4字节(符号扩展到32或64,具体取决于地址大小)。 所以从
[-128 to +127]
位移可以使用更紧凑的disp8
编码,比disp32
节省3个字节。
代码大小的例外:
-
[reg*scale]
本身只能用32位的位移编码。 智能汇编程序通过将lea eax, [rdx*2]
编码为lea eax, [rdx + rdx]
,但是这个技巧只能用于缩放2。 -
编码
e/rbp
或r13
作为没有位移字节的基址寄存器是不可能的,所以[ebp]
被编码为[ebp + byte 0]
。 以ebp
作为基址寄存器的无位移编码意味着没有基址寄存器(例如[disp + reg*scale]
)。 -
即使没有索引寄存器,
[e/rsp]
需要一个SIB字节。 (不pipe是否有位移)。 将指定[rsp]
的mod / rm编码意味着有一个SIB字节。
有关特殊情况的详细信息,请参阅英特尔的ref手册中的表2-5和周围部分。 (它们在32位和64位模式下是相同的,即使没有REX前缀,添加RIP相关编码也不会与其他编码冲突。
对于性能而言,仅仅为了获得更小的x86机器代码而花费额外的指令通常是不值得的。 在具有uopcaching的Intel CPU上,它比L1 I $小,并且是更宝贵的资源。 最小化融合域uops通常更重要。
16位地址大小不能使用SIB字节,所以所有的一个和两个寄存器寻址模式都被编码成单个mod / rm字节。 reg1
可以是BX或BP, reg2
可以是SI或DI(或者您可以自己使用这四个寄存器中的任意一个)。 缩放不可用。 16位代码由于许多原因已经过时,包括这一个,如果你不需要,不值得学习。
请注意,当使用地址大小前缀时,16位限制适用于32位代码,所以16位LEAalgorithm具有高度的限制性。 但是,您可以解决这个问题: lea eax, [edx + ecx*2]
设置ax = dx + cx*2
, 因为源寄存器高位中的垃圾没有效果 。
他们如何使用
这个表格并不完全符合可能寻址模式的硬件编码,因为我区分使用标签(例如全局或静态数据)和使用小的恒定位移。 所以我覆盖硬件寻址模式+符号链接器支持。
如果在esi
有一个指针char array[]
,
-
mov al, esi
:无效,不会组装。 没有方括号,这根本不是一个负担。 这是一个错误,因为寄存器大小不一样。 -
mov al, [esi]
加载指向的字节。 -
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]
加载global_array[10]
。 在64位模式下,这可以是RIP相对地址。 build议使用DEFAULT REL
,默认情况下生成RIP相对地址,而不必始终使用[rel global_array + 10]
。 没有办法直接使用带有RIP相对地址的索引寄存器。 正常的方法是使用lea rax, [global_array]
mov al, [rax + rcx*8 + 10]
或类似的方法。 -
mov al, [global_array + ecx + edx*2 + 10]
从global_array[ecx + edx*2 + 10]
加载global_array[ecx + edx*2 + 10]
显然,您可以用一个寄存器来索引静态/全局数组。 即使是使用两个独立寄存器的二维数组也是可能的 (用一个额外的指令对比例进行预缩放,对于除2,4,8以外的比例因子)。 请注意,global_array + 10
math是在链接时完成的。 目标文件(汇编器输出,链接器input)通知链接器+10添加到最终的绝对地址,将正确的4字节位移放入可执行文件(链接器输出)。 这就是为什么你不能在链接时间常量上使用不是汇编时间常量 (例如符号地址)的任意expression式 。 -
mov al, 0ABh
不是负载,而是存储在指令内的立即数。 (注意,你需要在前缀0
加上一个常数,而不是符号,一些汇编程序也会接受0xAB
)。 你可以使用一个符号作为立即数,以获得一个地址到一个寄存器。- NASM:
mov esi, global_array
组装成mov esi, imm32
,将地址放入esi。 - MASM:
mov esi, OFFSET global_array
需要做同样的事情。 - MASM:
mov esi, global_array
组装成一个负载:mov esi, dword [global_array]
。
在64位模式下,寻址全局符号通常是使用RIP相对寻址完成的,汇编程序默认使用
DEFAULT REL
指令,或者使用mov al, [rel global_array + 10]
。 没有索引寄存器可以与RIP相对地址一起使用,只有固定的位移。 你仍然可以做绝对寻址(OS X除外),甚至还有一种特殊forms的mov
,可以从64位绝对地址加载( 而不是通常的32位符号扩展 )。AT&T语法调用操作码movabs
(也用于mov r64, imm64
),而Intel / NASM语法仍将其称为mov
一种forms。使用
lea esi, [rel global_array]
来获取相对于rip的地址到寄存器中,因为mov reg, imm
会将非相对地址硬编码到指令字节中。请注意,OS X要求所有64位代码独立于位置,而不仅仅是共享库。 macho64目标文件格式不支持 Linux ELF的绝对地址重定位 。 除了
[global_array + constant]
类的有效地址外,请确保不要在任何地方使用标签名称作为编译[global_array + constant]
,因为可以将其组装为RIP相对寻址模式。 例如[global_array + ecx]
是不允许的,因为RIP不能与任何其他寄存器一起使用,所以它必须用硬编码的global_array
的绝对地址作为32位移位( 这将被符号扩展为64b )。 - NASM:
任何和所有这些寻址模式都可以与LEA
一起使用,以不影响标志的奖励来进行整数运算 ,而不pipe它是否是有效地址。 [esi*4 + 10]
通常只对LEA有用(除非位移是一个符号,而不是一个小常量)。 在机器代码中,没有单独的缩放寄存器编码,没有32b位移,但是汇编器只是使用零位移来编码[esi*4]
。
您可以指定片段覆盖像mov al, fs:[esi]
。 段覆盖只是在通常的编码之前添加前缀字节。 其他一切都保持不变,使用相同的语法。
如果操作数大小不明确(例如在一个带有立即数和内存操作数的指令中),使用byte
/ word
/ dword
/ qword
/ xmmword
/ ymmword
指定:
mov dword [rsi + 10], 0xAB ; NASM mov dword ptr [rsi + 10], 0xAB ; MASM and GNU .intex_syntax noprefix movl $0xAB, 10(%rsi) # GNU(AT&T): operand size from insn suffix
请参阅有关NASM语法有效地址的yasm文档和/或wikipedia x86条目的寻址模式部分 。 维基页面说明在16位模式下允许的内容。 这是32位寻址模式的另一个“备忘单” 。
还有一个更详细的16位寻址模式指南 。 16位仍然具有与32位相同的寻址模式,所以如果您发现寻址模式混乱,请阅读它
另请参阅x86 wiki页面的链接。