目录
1. 指令编码概观
1.1 指令编码语法
1.1.1 遗留前缀(Legacy Prefixes)
1.1.2 REX前缀
1.1.3 操作码(Opcode)
1.1.4 转义序列
1.1.5 ModRM和SIB字节
1.1.6 位移和立即数域
1.2 指令编码在内存中的表示
2. 指令前缀
2.1 遗留前缀概述
2.2 操作数大小重写前缀
2.3 寻址大小重写前缀
2.4 段重写前缀
2.5 LOCK前缀
2.6 重复操作前缀
2.6.1 REP重复操作前缀
2.6.2 REPE和REPZ重复操作前缀
2.6.3 REPNE和REPNZ重复操作前缀
2.6.4 不能使用重复操作前缀的指令
2.6.5 重复前缀的优化
2.7 REX前缀
2.8 VEX前缀和XOP前缀
3. 操作码(OPCODE)
4. ModRM和SIB字节
4.1 ModRM字节格式
4.2 SIB(Scale-Index-Byte)字节格式
4.3 遗留32位模式和兼容模式下操作数的寻址方法
4.4 64位模式下的操作数寻址方法
4.5 位移字节(Displacement Bytes)
4.6 立即数字节(Immediate Bytes)
4.7 RIP相对寻址
4.7.1 编码
4.7.2 REX前缀和RIP相对寻址
4.7.3 地址大小前缀和RIP相对寻址
4.8 使用REX时的编码考量
4.8.1 字节寄存器寻址
4.8.2 寄存器特别编码
4.9 使用VEX和XOP前缀编码
4.9.1 三字节转义序列
4.9.2 两字节转义序列
这里的编码指令参考自AMD编码文档,但是对Intel处理器同样适用,几乎没有本质区别。X64指令编码成可变长度的字节字符串。一条指令编码的每一个字节的次序和意义由架构指定。编码中的域指定了指令的基本操作,一个或多个源操作数的位置,以及操作结果的目标存储。编码中的域也可以包括用于执行指令的数据,或计算基于内存的操作数地址的数据。
一条指令编码后的长度介于1字节到15字节之间。整个字节序列表示一条指令,包括其本操作,源操作数和目的操作数的位置,任何操作修饰符,以及任何立即数和/或位移值,整个合起来称为指令编码。下面介绍指令编码语法和其在内存的表示。
下图提供了一条指令编码的直观框图:
注意事项:
1. 在利用VEX或XOP编码的扩展指令中,不允许使用REX前缀。
2. map=VEX/XOP.map_select 域。
3. 一条指令编码后的总字节数必须小于或等于15。
4. 编码一条8字节立即数域的指令不使用位移域,反之亦然。
上图中每个方格表示一种特别类型或功能的指令编码字节(可能是1个或多个字节)。为了理解这个编码图形,需按照箭头所指的方向路径沿“Start”到“End”走完,才算一条完整的指令。遍历图形时先后通过的方块表示用于编码指令的字节顺序和数量。注意,在上述图形的路径中,遗留前缀legacy prefix)字节环回表示在一条单一指令的编码中,最多可以使用4个编码前缀。分支表示语法中的点,在这个点上会基于被编码的指令而采用相应的替代语义。向下导向VEX前缀和XOP前缀方格的路径上的“VEX 或 XOP”门意味着只有使用VEX或XOP前缀的扩展指令才能使用语法图的这个特定分支。该图将在以下各节中进一步解释。
正如图表所示,一条指令可选地最多可以以5个遗留前缀开头。这些遗留前缀在“遗留前缀概览”部分已有介绍。遗留前缀修改指令的默认地址大小、操作数大小、或者段大小;或者它们会执行一种特定的操作,例如,修改操作码,触发原子总线锁,或者重复执行某条指令。
在大部分SSE指令编码中,遗留指令操作数大小或重复前缀被改换用作修改操作码。对于利用XOP或VEX前缀的扩展编码,不允许使用遗留前缀。
在一个或多个可选的遗留前缀或后,可以在64位模式下使用REX前缀来访问AMD64 寄存器编号和大小扩展。参考应用程序编程寄存器组部分对这些设施的介绍。假如使用了REX前缀,则其必须紧置于操作码字节的前面,或者紧置于一个遗留转义序列(escape sequence)字节的前面。在使用VEX 或 XOP编码转义前缀编码的扩展指令中,不允许使用REX前缀。违反这种限制会触发一个#UD处理器异常。
操作码是1个字节,它指定了一条指令的基本操作。每一条指令都要求有一个操作码。一条指令的二进制制和它所表示的操作之间的对应关系用一个表来表示,称为操作码映射表。因为它是8位值的索引,因此,一个操作码映射有256个条目(操作码是1个字节)。因为按照架构的定义,实际指令数超过256个,因此,必须定义多个不同的操作码,必须在指令中对这些选用替代操作码映射进行编码,转义序列提供了访问替代操作码映射的能力。
假如没有操作码转义,使用主操作码映射(1个字节)。在图表中,表现为从REX前缀方格指向主操作码映射方块这条路径。
转义序列允许访问不同于主操作码映射表的可替代操作码映射表,转义序列可以是一个字节、两个字节、或者三个字节,始于一个为此目的而分配的位于主操作码映射表中的唯一字节值。转义序列有两种不同的类型:遗留转义序列和扩展转义序列。以下介绍遗留转义序列,关于扩展转义序列参见“VEX和XOP前缀”部分。
遗留前缀允许使用1个1字节转义序列(0FH),和3个2字节转义序列(0FH, 0FH; 0FH,38H和0FH,3AH)。1字节转义序列0FH二级(2字节)操作码映射表。按照遗留的术语,序列[0FH,opcode]称为2字节操作码。
2字节转义序列0FH, 0FH选择3DNow!操作码映射表,它使用1个立即数字节而不是一个操作码字节为索引。在这种情况下,紧随转义序列后的字节是ModRM字节而不是操作码字节。在上图中,离开第二个转义方格之后,通过路径标识为“3DNow!”标识表示。
2字节转义序列[0FH,38H]和[0FH,3AH]分别选择0F_38H和0F_3AH操作码映射表。这些操作码主要用于编码SSE指令。
操作码可以后接ModRM字节(mode-register-memory),它进一步进描述了操作和/或操作数。ModRM字节之后也可以接SIB字节(scale-index-base),用于指定变址寄存器,间接构成内存寻址。ModRM和SIB字节参见“ModRM字节格式”部分。它们的遗留功能可以通过 REX 前缀或VEX和XOP转义序列来增强。
指令编码可以1字节、2字节、或者4字节的位移域或1字节、2字节、或者4字节的立即数据结尾,具体取决于指令和/或寻址模式。特定指令还允许使用 8 字节立即数域或 8 字节位移域。
指令以小端字节序存储于内存中。指令的第一个字节存储在内存的最低内存地址处,如下图1-2所示。因为指令是字节字符串,因此,它们可以始于任何内存地址。指令的总长度必须小于或等于15字节。如果指令超过这个 限制,会触发通用保护异常。
注意:
上标1标注表示选项仅在64位模式下可获得。
上标2标注表示仅在长模式或保护模式下可获得。
上标3标注表示不允许使用F0,F2,F3和66前缀。
上标4标注表示指定一个8字节立即域的指令不含有移位域,反之不含有立即域(而只有位移域)。
(还应注意,以上每一项并非就是只占一字节。)
指令前缀有两种类型:指令修饰符前缀和指令编码转义前缀。指令修饰符前缀可能改变指令的操作(包换执行重复指令)、改变其操作数类型、增加寄存器规范、或者甚至是改变操作码字节的解释。
指令修饰符前缀包括遗留前缀和REX前缀。遗留前缀在下一节讨论。REX前缀参见“REX前缀”专题的文档部分。
另一方面,编码转义前缀表明其后的两个或三个字节遵循不同的编码语法。作为一个组,编码转义前缀及其后续字节构成一个多字节转义序列。这些多字节转义序列执行类似于指令修饰符前缀的功能,但它们也提供了一种直接指定替代操作码映射的方法。
当前定义的编码转义前缀是VEX和XOP前缀。详细参见“VEX 和 XOP 前缀”部分的文档。
遗留前缀组织成五组,每条指令编码最多只能包括这五组前缀中每一组的一个前缀(即不能同时包含每个组中的二个及以上)。遗留前缀在前缀位置内可能是任意顺序。来自多同一个组中的多个前缀的情况未定义(如果这样使用,可能是触发异常或者忽略)。遗留指令前缀的一些限制:
(1) 操作数大小重写——此前缀仅影响通用寄存器或者其它源寄存器或目的寄存器是通用寄存器的指令的操作数大小。当用于SIMD或一些其它指令的时候,此前缀重新用于修改操作码。
(2) 地址大小重写——此前缀仅影响内存操作数的大小。
(3) 段重写——在64位模式下,系统忽略掉CS,DS,ES和SS段重写前缀。
(4) LOCK前缀——此前缀仅允许使用在某些修改内存的指令中。
(5) 重复操作前缀——这些前缀仅影响某些字符串操作指令,用于SIMD或一些其它指令的时候,此前缀重新用于修改操作码。
请注意,LOCK和重复操作前缀在用作指令修饰符时实际上是互斥的,因为没有任何指令对它们同时出现均有意义。
---------------------------------------表2-1 遗留指令前缀------------------------------------------------------
前缀分组[1] |
助记符 |
前缀字节(16字节) |
描述 |
操作数大小重写 |
无 |
66[2] |
改变内存或寄存器操作数大小 |
地址大小重写 |
无 |
67[3] |
改变默认内存操作数的大小 |
段重写 |
CS |
2E[4] |
强制用当前CS段作内存操作数 |
DS |
3E[4] |
强制用当DS段作内存操作数 |
|
ES |
26[4] |
强制用当前ES段作内存操作数 |
|
FS |
64 |
强制用当前SS段作内存操作数 |
|
GS |
65 |
强制用当前GS段作内存操作数 |
|
SS |
36[4] |
强制用当前FS段作内存操作数 |
|
LOCK前缀 |
LOCK |
F0[5] |
引发某种读-修改-写指令以原子形式发生 |
重复操作前缀 |
REP |
F3[6] |
重复执行字符串操作(INS, MOVS, OUTS, LODS和STOS)直到rCX=0 |
REPE或REPZ |
重复执行字符串比较或扫描操作(CMPSx 和SCASx),直到rCX=0或者零标识(ZF)置0 |
||
F2[6] |
重复执行字符串比较或扫描操作(CMPSx 和SCASx),直到rCX=0或者零标识(ZF)置1 |
||
脚注说明: 1. 当用作指令修饰符时,一条指令应包含不超过每个重写前缀组中的一个前缀以及一个锁定或重复前缀。 2. 当用于SIMD和其他一些指令的编码时,这个前缀被重新用于扩展操作码。64 位媒体浮点 (3DNow!™) 指令会忽略该前缀。 3. 此前缀在用作隐含计数寄存器时也会改变 RCX 寄存器的大小。 4. 在64位模式下,CS、DS、ES 和 SS 段覆盖被忽略。 5. LOCK 前缀不应用于指定指令以外的指令。 6. 此前缀只能用于比较字符串和扫描字符串指令。 当用于编码时SIMD 和其他一些指令,前缀被重新用于扩展操作码。 |
一条指令的默认操作数大小由其操作码的组合(默认位D(default)在当前代码段描述符中)和当前操作模式决定,如图表1-2所示。 操作数大小覆盖前缀(66h)选择非默认操作数大小。 前缀可用于访问内存或通用寄存器(GPR)中非固定大小操作数的任何通用指令,也可用于 x87 FLDENV、FNSTENV、FNSAVE 和 FRSTOR 指令。
在64位模式下,前缀允许在逐指令的基础上混合16位、32位和64位数据。在兼容模式和遗留模式下,前缀允许在逐指令的基础上混合16位、32位操作数。
---------------------------------------表2-2 操作数大小重写--------------------------------------------
操作模式 |
缺省操作数大小(按位) |
相对操作数大小(按位) |
指令前缀[1] |
||
66h |
REX.W[3] |
||||
长模式 |
64位模式 |
32[2] |
64 |
不关心 |
是 |
32 |
否 |
否 |
|||
16 |
是 |
否 |
|||
兼容模式 |
32 |
32 |
否 |
无应用 |
|
16 |
是 |
||||
16 |
32 |
是 |
|||
16 |
否 |
||||
遗留模式(保护模式,虚拟-8086模式或者实模式) |
32 |
32 |
否 |
||
16 |
是 |
||||
16 |
32 |
是 |
|||
16 |
否 |
||||
脚注说明: 1. “否”表明使用默认操作数大小。 2. 这是典型默认大小,尽管一些指令使用其它默认操作数大小。 3. 参见REX前缀文档。 |
在64位模式下,大部分指令默认是32位操作数大小。对于这些指令而言,REX前缀可以指定64位操作数大小,66h前缀指定16位操作数大小。REX前缀位于66h前缀之前。然而,假如一条指令默认是64位操作数大小,则它不需要REX前缀,并且仅可以被重写为16位操作数大小。因为在64位模式下,不存在32位操作数大小的重写前缀。在64位模式下,这两组指令具有默认64位操作数大小:
(1) 近分支。更多细节,参见文档卷1的“64位模式下的近分支”。
(2) 除远分支下的所有式引用RSP指针的指令。更多细见,参见文档卷1的“栈操作部分”。
不能使用操作数大小前缀的指令。操作数大小前缀仅应不用于通用寄存器指令,以及x87 FLDENV, FNSTENV, FNSAVE, 和FRSTOR指令,其中,前缀选择16位和32位操作数大小。所有其它x87指令和64位媒体浮点(3DNow!™)指令忽略前缀。
对于所有其它指令(大部分是SIMD指令),66h, F2h, 和F3h前缀用作操作码扩展,以0Fh,0F_38h, 和0F_3Ah操作码映射去扩展指令编码空间。
操作数和REX前缀。REX前缀的W位域位于66h前缀之前。
访问非栈内存指令的默认地址大小由取决于当前的操作模式,如下表2-3所示。地址大小重写前缀(67h)选择非默认地址大小。依据操作模式,前缀允许16位和32位地址混合,或者32位和64位地址混合,依据指令基础。前缀改变内存操作数的地址大小。对于显式地使用RCX寄存器的指令而言,前缀也改变RCX寄存器的大小。
对于显式地访问栈段寄存器(SS)的指令而言,所访问的栈的地址大小取决于栈段描述符中的D位(缺省)。在64位模式中,系统忽略D位,所有引用的栈的地址大小都是64位地址大小。然而,假如一条指令既然访问了栈内存,又访问了非栈内存,非栈访问的地址大小决定因素由下表2-3显示:
------------------------------------------------表2-3 地址大小重写------------------------------------------
操作模式 |
缺省地址大小 (位) |
有效地址大小(位) |
是否要求地址大小前缀(67h)? |
|
长 模 式 |
64位模式 |
64 |
64 |
否 |
32 |
是 |
|||
兼容模式 |
32 |
32 |
否 |
|
16 |
是 |
|||
16 |
32 |
是 |
||
16 |
否 |
|||
遗留模式(保护模式,虚拟-8086和实模式) |
32 |
32 |
否 |
|
16 |
是 |
|||
16 |
32 |
是 |
||
16 |
否 |
|||
注:“否”表示使用缺省的地址大小。 |
正如以上表格所示,在64位模式下,缺省地址大小是64位。这个默认大小可以重写为32位大小。但是在64位模式下,系统不支持16位地址大小。在兼容模式和遗留模式下,缺省地址大小是16位或32位,取决于操作模式(见卷2部分处理器初始化和长模式激活文档部分)。
某些指令显式地而不是隐式地引用指针寄存器或者计数寄存器。在这些指令中,地址大小前缀影响这些寻址和计数寄存器的大小,就像显式地引用此类寄存器一样。以下表3-4列出了所有这类使用三种可能的地址大小引用的指令和寄存器。
------------------------------------表2-4 指针和计数器寄存器和地址大小前缀----------------------------------
指令 |
指令和计数寄存器 |
||
16位地址大小 |
32位地址大小 |
64位地址大小 |
|
CMPS,CMPSB, CMPSW,CMPSD, CMPSQ——比较字符串 |
SI, DI, CX |
ESI, EDI, ECX |
RSI, RDI, RCX |
INS, INSB, INSW, INSD——输入字符串 |
DI, CX |
EDI, ECX |
RDI, RCX |
JCXZ,JECXZ, JRCXZ——基于 CX/ECX/RCX为零的跳转 |
CX |
ECX |
RCX |
LODS,LODSB, LODSW,LODSD, LODSQ——载入字符串 |
SI, CX |
ESI, ECX |
RSI, RCX |
LOOP,LOOPE, LOOPNZ,LOOPNE, LOOPZ——循环 |
CX |
ECX |
RCX |
MOVS,MOVSB, MOVSW,MOVSD, MOVSQ——移动字符串 |
SI, DI, CX |
ESI, EDI, ECX |
RSI, RDI, RCX |
OUTS,OUTSB, OUTSW,OUTSD ——输出字符串 |
SI, CX |
ESI, ECX |
RSI, RCX |
REP,REPE, REPNE, REPNZ,REPZ——重复执行指令前缀 |
CX |
ECX |
RCX |
SCAS,SCASB,SCASW,SCASD, SCASQ——扫描字符串 |
DI, CX |
EDI, ECX |
RDI, RCX |
STOS,STOSB,STOSW,STOSD, STOSQ——存储字符串 |
DI, CX |
EDI, ECX |
RDI, RCX |
XLAT, XLATB——查表转换指令 |
BX |
EBX |
RBX |
段重写仅可用于引用非栈内存的指令。大部分引用内存的指令使用ModRM字节编码。这样的引用内存指令的缺省段由表示ModRM字节的基址寄存器隐式指定,说明如下:
(1) 引用非栈段(指堆内存或其它)的指令——假如一条编码指令引用了任何基址寄存器,而不是rBP或rSP,或者假如一条包括了一个立即数偏移,则缺省段是数据段(DS)。这些指令可以使用段重写前缀去选择非默认段中的某一个,如表2-5所示。
(2) 字符串指令——字符串指令引用了两个内存操作数。按默认,它们引用了数据段(DS)和扩展段(DS:rSI和ES:rDI),这些指令可以重写它们的数据段引用,如表2-5所示,但是它们不能重写ES扩展段引用。
(3) 引用栈段的指令——假如一条编码指令引用了rBP或rSP基寄存器,缺省段是栈段(SS),引用栈的所有指令(push, pop, call, 中断, 中断返回)默认都使用SS。这些指令不能使用段重写前缀。
------------------------------------------------------表2-5 段重写前缀------------------------------------------------
助记符 |
前缀字节(16进制) |
描述 |
CS1 |
2E |
强制将当前 CS 段用于内存操作数。 |
DS1 |
3E |
强制将当前 DS 段用于内存操作数。 |
ES1 |
26 |
强制将当前 ES段用于内存操作数。 |
FS |
64 |
强制将当前 FS段用于内存操作数。 |
GS |
65 |
强制将当前 GS用于内存操作数。 |
SS1 |
36 |
强制将当前 SS 段用于内存操作数。 |
脚注1说明:在64位模式下,系统忽略CS,DS,ES,和SS段重写前缀。 |
64位模式下的段重写前缀。在64位模式下,CS,DS,ES,和SS段重写前缀无效。这四个前缀不被视为作为多前缀规则目的的段重写前缀。而是被视为null前缀。
在64位模式下,FS和GS段重写前缀被视为真段重写前缀。使用FS和GS前缀引起它们的相应的段基址加上有效地址参与地址计算。详情请参阅卷2的“64位模式下的FS和GS寄存”文档。
LOCK前缀会引起某些读取-修改-写入的指令以原子的方式执行。这样做的机制是依赖于具体实现的(例如,这种机制可以包括处理器和总线控制器之间的总线信号或者包消息)。这个前缀的目的在于,在多处理器环境下,确保处理器可以以独占的方式使用共享内存。LOCK前缀只能用于下列指令去写内存操作数:ADC, ADD, AND, BTC, BTR, BTS, CMPXCHG, CMPXCHG8B, CMPXCHG16B, DEC,INC, NEG, NOT, OR, SBB, SUB, XADD, XCHG, 和XOR。如果LOCK指令用于除此之外的其它任何指令,系统将触发一个非法操作码(invalid-opcode)的异常。
重复操作前缀引起某些指令,例如,载入字符串、移动字符串、输入字符串和输出字符、等等串重复执行。这个指令仅应当用于这种字符串操作指令。两对重复操作前缀,REPE/REPZ 和REPNE/REPNZ,对于某些比较字符串和扫描字符串指令而言,构成了某些重复操作功能。这些重复功能使用rCX寄存器作为计数寄存器。rCX寄存器的大小取决于地址的大小,见上表2-4。
REP重复执行与其关联的字符串指令的次数由rCX计数寄存器指定。当rCX计数寄存器的值达到0时终止。此前缀可以与INS, LODS, MOVS, OUTS, 和STOS指定一起使用。表2-6显示了这种有效的REP前缀操作码。
------------------------------------------------表2-6 REP前缀操作码-------------------------------------
助记符 |
操作码(16进制) |
REP INS reg/mem8, DX REP INSB |
F3 6C |
REP INS reg/mem16/32, DX REP INSW REP INSD |
F3 6D |
REP LODS mem8 REP LODSB |
F3 AC |
REP LODS mem16/32/64 REP LODSW REP LODSD REP LODSQ |
F3 AD |
REP MOVS mem8, mem8 REP MOVSB |
F3 A4 |
REP MOVS mem16/32/64, mem16/32/64 REP MOVSW REP MOVSD REP MOVSQ |
F3 A5 |
REP OUTS DX, reg/mem8 REP OUTSB |
F3 6E |
REP OUTS DX, reg/mem16/32 REP OUTSW REP OUTSD |
F3 6F |
REP STOS mem8 REP STOSB |
F3 AA |
REP STOS mem16/32/64 REP STOSW REP STOSD REP STOSQ |
F3 AB |
REPE和REPZ是同义词并且具有相同的操作码。这些前缀执行与其关联的字符串指令的次数由rCX计数寄存器指定。当rCX计数寄存器的值达到0或者ZF标识被置0时,重复操作终止。REPE和REPZ前缀可以与CMPS, CMPSB, CMPSD, CMPSW, SCAS, SCASB,SCASD, 和SCASW一起使用。表2-7显示了有效的REPE和REPZ前缀操作码。
---------------------------------------------表2-7 REPE和REPZ前缀操作码-------------------------------
助记符 |
操作码(16进制) |
REPx CMPS mem8, mem8 REPx CMPSB |
F3 A6 |
REPx CMPS mem16/32/64, mem16/32/64 REPx CMPSW REPx CMPSD REPx CMPSQ |
F3 A7 |
REPx SCAS mem8 REPx SCASB |
F3 AE |
REPx SCAS mem16/32/64 REPx SCASW REPx SCASD REPx SCASQ |
F3 AF |
REPNE和REPNZ是同义词并且具有相同的操作码。这些前缀执行与其关联的字符串指令的次数由rCX计数寄存器指定。当rCX计数寄存器的值达到0或者ZF标识被置1时,重复操作终止。REPNE和REPNZ前缀可以与CMPS, CMPSB, CMPSD, CMPSW, SCAS,SCASB, SCASD, 和SCASW指令一起使用。表2-8显示了有效的REPNE和REPNZ前缀操作码。
----------------------------------------- 表2-8 REPE和REPZ前缀操作码------------------------------------
助记符 |
操作码(16进制) |
REPNx CMPS mem8, mem8 REPNx CMPSB |
F2 A6 |
REPNx CMPS mem16/32/64, mem16/32/64 REPNx CMPSW REPNx CMPSD REPNx CMPSQ |
F2 A7 |
REPNx SCAS mem8 REPNx SCASB |
F2 AE |
REPNx SCAS mem16/32/64 REPNx SCASW REPNx SCASD REPNx SCASQ |
F2 AF |
一般而言,重复操作指令前缀仅应用于以上图2-6,2-7,2-8中列出的指令。对于其它的指令(大部分是SIMD指令),使用66h,F2和F3前缀修饰指令,用以以0Fh,0F_38h和0F_3Ah操作码映射扩展编码空间。
重复前缀的优化取决于硬件实现,可能有设置开销。假如重复数量可变,这种开销可以通过替换一个简单的循环去移动或存储数据的方式避免。重复字符串操作指令可以扩展为内联载入或者存储序列,或者一个可仿真REP STOS的存储序列。
对重复字符串移动指令,通过移动最可能大的操作数大小的字符串,可以使其性能最大化。例如,使用REP MOVSD指令而不是REP MOVSW指令,和使用REP MOVSW指令而不是REP MOVSB指令。使用REP STOSD指令而不是REP STOSW指令,和使用REP STOSW指令而不是REP MOVSB指令。
根据硬件实现,向上的方向移字符串(DF置0)可能比向下的方向移(DF置1)更快。仅某种重叠的REP MOVS指令才城要DF=1,例如,当源操作数和目的操作数重叠的时候。
REX前缀,在64位模式上可获得。使能AMD64寄存器和操作数大小扩展。不同于遗留指令的修饰前缀,REX不是一个单一的唯一值,而是占据了一个范围(40h到4Fh)。图2-1展示了REX前缀是如何适配指令的编码语法。
在64位模式下,REX前缀使用下面的特征:
(1) 启用扩展的通用寄存器(GPR)和YMM/XMM寄存器。
(2) 当访问通用寄存器时,启用64位操作数大小。
(3) 扩展了调试和控制寄存器。
(4) 使用统一的字节寄存器(AL-A15)。
REX包含5个域。REX前缀占用1个字节,上半字节对REX前缀而言是唯的,用于标识它。下半字节分成4个1 bit(W,R,X和B)。见下图中的标识。图2-9显示了REX前缀的格式。因为低半字节的每一个位可以是1或者0(因此有16种组合),REX前缀跨越了主编码字节映射的一个完整行,占据了整个条目的40h到4Fh。
---------------------------------------------图2-1 REX前缀格式---------------------------------------------------
REX前缀的指令通常要求访问64位通用寄存器或者扩展的通用寄存器或者YMM/XMM寄存器。有几条指令在64位模式下缺省是(或者固定是)64位的操作数大小,因此,不需要REX前缀。不需要REX前缀的指令如下表2-09所示:
-----------------------------------------表2-9 在64位模式下不需要REX前缀的指令---------------------------
CALL (Near) |
POP reg/mem |
ENTER |
POP reg |
Jcc |
POP FS |
JrCXZ |
POP GS |
JMP (Near) |
POPF, POPFD, POPFQ |
LEAVE |
PUSH imm8 |
LGDT |
PUSH imm32 |
LIDT |
PUSH reg/mem |
LLDT |
PUSH reg |
LOOP |
PUSH FS |
LOOPcc |
PUSH GS |
LTR |
PUSHF, PUSHFD, PUSHFQ |
MOV CRn |
RET (Near) |
MOV DRn |
一条指令仅可以有一个REX前缀,且在指令编码中必须紧置于操作码前面或者第一个转义字节前面。在不能访问扩展寄存器的环境中使用REX前缀会被系统忽略。指令大小限定于15字节也包括含有REX前缀在内的情况。
对INC和DEC指令的影响:REX前缀值取自16个单字节INC和DEC指令,8个遗留通用寄存器各有一个。因此,这些用于INC和DEC的单字节操作码在64位模式下不可用,尽管它们在遗留和兼容模式下可用。这些INC和DEC指令的功能在64位模式下仍然可用,但是,使用的是这些指令的ModRM形式(操作码FF/0和FF/1)。
扩展指令的编码语法,在保护模式和长模式下也可用,通过VEX或XOP前缀引入的方式,提供了1个两字节和3个三字节转义序列。这些多字节序列不仅选择操作码映射,并且提供了与REX前缀类似,但又可替之的指令修饰符。
由VEX前缀C5h前缀开头的2字节转义序列意味着映射选择编码为1。3字节转义序列以VEX C4h前缀或者NOP(8Fh)前缀开头,经由VEX/XOP.map_select域显式地选择目标操作码映射。5-bit VEX.map_select域允许31个不同操作码映射(操作码映射00h保留,因此余下31个)中的一个选择。XOP.map_select域限定在08h和1Fh之间,因此,仅可以选择24个操作码映射。
VEX和XOP转义序列包括扩展寄存器寻址到总数16,增加操作数的规范容量到四个操作数,并且修饰指令的操作。
扩展SSE指令子集AVX, AES, CLMU, FMA, FMA4, 和 XOP以及几个非SSE指令利用扩展编码语法。更多细节请参见文档“利用VEX和XOP编码”文档部分中的两字节和三字节的扩展转义序列编码部分文档。
操作码(opcode)是一个单字节,它指定了一条指令的基本操作。在某些情况下,它也指定指令的操作数。每条指令都要求一个操作码。操作码的二进制值和其所表示的操作之间的对应关系由一个称为操作码映射(opcode map)的表定义。正如前一章节所讨论的,遗留前缀66h,F2h和F3h以及指令编码内的其它域可以用于修改由操作码编码的操作。
66h,F2h和F3h前缀的存在对操作码执行的操作的影响,体现在操作码映射表中,是用另外的行按应用前缀建索引。在某些编码中,也使用ModRM字节的三个位reg域和r/m域进行编码。体现在操作码映射表中的指令分组表,它细化了经额外编码位表示的修饰。
尽管每一条指令都有一个唯一的操作码映射和操作码,但是汇编器通常支持同一条指令的多个可替换的助记符,以改善汇编语言代码的可读性。
64位浮点3DNow!指令利用两字节转义序列0Fh, 0Fh去选择3DNow!操作码映射。对于这些令而言,在指令编码结尾,操作码以立即数域的形式编码。
关于操作码字节是如何将具体的指令编码为基础的操作的细节,请参见文档附录的操作码映射表。
ModeRM是可选项,取决于指令。当它出现的时候,它跟在操作码后面,用于指定:
(1) 两个基于寄存器的操作数,或
(2) 一个基于寄存器的操作数和第二个基于内存的操作数,以及一个寻址模式。
在某些指令的编码中,ModeRM字节内的域重定义用于提供定义指令功能的额外的操作码。
ModeRM字节被划分成3个域——mod,reg和r/m。通常,reg域用于指定基于寄存器的操作数,而mod域和r/m域合在一起指定第二个操作数,这个操作数基于寄存器或内存。当操作数是基于内存时,也需要指定寻址模式。
在64位模式下, REX.R和REX.B位分别增加了reg和r/m域,从而允许指定两倍数量的寄存器。
下列图4-1显示了ModRM字节格式:
--------------------------------------图4-1 ModRM字节格式----------------------------------------------------
根据寻址模式,SIB字节可以出现在ModRM字节后。SIB用于各种形式的变址化的寄存器间接寻址的规范。细节请参见下一段。
ModRM.mod(Bits(7:6))。Mod域与r/m域一起用于指定操作数的寻址模式。ModRM.mod=11b指定寄存器直接寻址模式(寄存器中存放的是已经计算好的变量的内存地址,无需再计算地址,直接使用)。在寄存器直接寻址模式下,操作数存储在指定的寄存器中。ModRM.mod<11b指定了寄存器间接寻址模式,在寄存器间接寻址模式下,存储在寄存器中的值,需要与指定编码中指定的可选位移结合在一起,计算出基于内存的操作数据址。其它的5个位的编码(mod,r/m)在下面讨论。
ModRM.reg(Bits(5:3))。Reg域用于指定基于寄存器的操作数,尽管对于某些指令,这个域用于扩展操作编码。这个域的编码如下表4-2表所示。
ModRM.r/m(Bits(2:0))。正如以上指出的,r/m域用于与mod域一起,编码32个不同的操作数规范。这个域的编码如下表4-2表所示。
----------------------------------- 表4-1 ModRM.reg域和ModRM.r/m域编码-----------------------------------
编码值(二进制) |
ModRM.reg[1] |
ModRM.r/m(mod=11b) [1] |
ModRM.r/m(mod≠11b) [2] |
000 |
rAX, MMX0, XMM0, YMM0 |
rAX, MMX0, XMM0, YMM0 |
[rAX] |
001 |
rCX, MMX1, XMM1, YMM1 |
rCX, MMX1, XMM1, YMM1 |
[rCX] |
010 |
rDX, MMX2, XMM2, YMM2 |
rDX, MMX2, XMM2, YMM2 |
[rDX] |
011 |
rBX, MMX3, XMM3, YMM3 |
rBX, MMX3, XMM3, YMM3 |
[rBX] |
100 |
AH, rSP, MMX4, XMM4, YMM4 |
AH, rSP, MMX4, XMM4, YMM4 |
SIB[3] |
101 |
CH, rBP, MMX5, XMM5, YMM5 |
CH, rBP, MMX5, XMM5, YMM5 |
[RBP][4] |
110 |
DH, rSI, MMX6, XMM6, YMM6 |
DH, rSI, MMX6, XMM6, YMM6 |
[rSI] |
111 |
BH, rDI, MMX7, XMM7, YMM7 |
BH, rDI, MMX7, XMM7, YMM7 |
[rDI] |
脚注说明: 1. 具体使用的寄存器是指令依赖的。 2. mod=01和mod=10包括一个由指定的位移域指定的偏移。括号[*]表示存储操作数地址的具体寄存器。 3. 变址寄存器间接寻址。SIB字节跟在ModRM字节之后,参见后续SIB章节的介绍。 4. mod=00b,r/m=101b表示32位模式下或者绝对寻址(仅位移参与构成地址),或者64位模式下的RIP相对寻址(不使用rBP寄存器)。mod=[01b, 10b], r/m=101b表示以rBP作为基址的基址+偏移模式。 |
与reg域相似,r/m域用于某些指令扩展操作编码。
SIB字节有3个域——scale,index和base——为32位和64位变址寄存器间接寻址模式定义了比例因子(scale factor),变址寄存器的数量,以及基址寄存器的数量。一个使用变址寄存器间接寻址模式的基于内存的操作数计算有效地址的基本公式为:
effective_address = scale*index + base+ offset (基中,有效地址可以认为是程序计算出来的虚拟地址,最终由系统映射成物理地址。) 这种寻址模式的特定变体将和的一个或多个元素设置为零。
下图4-3显示了SIB字节的格式。
-----------------------------------------------图4-2 SIB字节格式---------------------------------------------------
SIB.scale (Bits[7:6])。 scale域用以在计算有效地址的scale*index部分时指定比例因子。在通常用法中,scale表示数组中以字节计数的数组单个元素的大小。SIB.scale的编码格式如下表4-2所示。
-----------------------------------------表4-2 SIB.scale域编码-----------------------------------------
编码值(二进制) |
比例因子 |
00 |
1 |
01 |
2 |
10 |
4 |
11 |
8 |
SIB.index (Bits[5:3])。index域用于指定变址寄存器间接有效地址中变址部分的包含index的寄存器。SIB.index编码见下面的表4-3。
SIB.base (Bits[2:0])。base域用于指定变址寄存器间接有效地址中基址部分的包含base的寄存器。SIB.base编码见下面的表4-3。
-------------------------------------------表4-3 SIB.index域和SIB.index域编码---------------------------------
编码字节 (二进制) |
SIB.index |
SIB.index |
000 |
[rAX] |
[rAX] |
001 |
[rCX] |
[rCX] |
010 |
[rDX] |
[rDX] |
011 |
[rBX] |
[rBX] |
100 |
(none)[1] |
[rSP] |
101 |
[rBP] |
[rBP], (none)[2] |
110 |
[rSI] |
DH,[rSI] |
111 |
[rDI] |
BH, [rDI] |
脚注说明: 1. 寄存器规范是null。变址寄存器间接有效地址scale*index部分置为0。 2. 假如ModRM.mod=00b,则寄存器规范为null。变址寄存器间接有效地址base部分置为0。否则,base将rBP寄存器编码为在有效地址计算中使用的base地址源。 |
表4-4 ModRM.r/m = 100b的SIB.base编码
SIB.base域 |
||||||||
mod |
000 |
001 |
010 |
011 |
100 |
101 |
110 |
111 |
00 |
[rAX] |
[rCX] |
[rDX] |
[rBX] |
[rSP] |
32位位移 |
[rSI] |
[rDI] |
01 |
[rBP]+ 8位位移 |
|||||||
10 |
[rBP]+ 32位位移 |
|||||||
11 |
无应用 |
关于操作数的更多讨论参见后续两章节。
ModRM字节的mod和r/m域提供了共5个比特用于编码32位规范的操作数和内存寻址模式。下表表4-5显示了这些编码。
-------------------------------表4-5 使用ModRM和SIB字节的操作数寻址编码----------------------------------
ModRM.mod |
ModRM.r/m |
寄存器/有效地址 |
00 |
000 |
[rAX] |
001 |
[rCX] |
|
010 |
[rDX] |
|
100 |
[rBX] |
|
011 |
SIB[1] |
|
101 |
32位位移 |
|
110 |
[rSI] |
|
111 |
[rDI] |
|
01 |
000 |
[rAX]+ 8位位移 |
001 |
[rCX] + 8位位移 |
|
010 |
[rDX] + 8位位移 |
|
100 |
[rBX] + 8位位移 |
|
011 |
SIB[1] + 8位位移[2] |
|
101 |
[rBP]+ 8位位移 |
|
110 |
[rSI] + 8位位移 |
|
111 |
[rDI] + 8位位移 |
|
01 |
000 |
[rAX]+ 32位位移 |
001 |
[rCX] + 32位位移 |
|
010 |
[rDX] + 32位位移 |
|
100 |
[rBX] + 32位位移 |
|
011 |
SIB[1] + 32位位移[2] |
|
101 |
[rBP]+ 32位位移 |
|
110 |
[rSI] + 32位位移 |
|
111 |
[rDI] + 32位位移 |
|
11 |
000 |
AL/rAX/MMX0/XMM0/YMM0 |
001 |
CL/rCX/MMX1/XMM1/YMM1 |
|
010 |
DL/rDX/MMX2/XMM2/YMM2 |
|
100 |
BL/rBX/MMX3/XMM3/YMM3 |
|
011 |
AH/SPL/rSP/MMX4/XMM4/YMM4 |
|
101 |
CH/BPL/rBP/MMX5/XMM5/YMM5 |
|
110 |
DH/SIL/rSI/MMX6/XMM6/YMM6 |
|
111 |
BH/DIL/rDI/MMX6/XMM6/YMM7 |
|
脚注说明: 0. 在下面的说明中,scaled_index=SIB.index*(1< 1. SIB字节跟在ModRM字节之后,有效地址按照scaled_index+base计算。当SIB.base=101b时,寻址模式取决于ModRM.mod。参见上表4-4。 2. SIB字节跟在ModRM字节之后,有效地址按照scaled_index+base+8位位移计算。1字节位移域作为偏移。 3. SIB字节跟在ModRM字节之后,有效地址按照scaled_index+base+32位位移计算。4字节位移域作为偏移。 |
需要注意,寻址模式mod=11b是寄存器直接寻址模式,即,操作数包含在具体的寄存器中,而模式mod=[00b,10b]为基于内存的操作数指定了不同的寻址模式。
对于mod=11b这种情况,包含操作数的寄存器由r/m域指定。对其它模式(mod=[00b,10b]),mode和r/m域一起为基于内存的操作数指定寻址模式。大部分寄存器间接寻址模式意味着基于内存的操作数的地址包含在由r/m指定的寄存器中。对于这些
寄存器间接模式,mod=01b和mod=10b都包括一个编码在指令的位移域中的偏移。
编码{mode≠11b,r/m=110b}指定的变址寄存器间接寻址模式,其目标地址由存储于寄存器中的值和一个直接编码在SIB字节中的比例因子这两个量的组合值组合计算而成。这于这些寻址模式,有效地址的按下列公式计算得到:
effective_address = scale * index + base + offset
scale编码在SIB.scale域,index包含于由SIB.index域和base指定的寄存器中,offset按1字节或4字节大小编码于指令的位移域(displacement)中。
假如编码为{mode,r/m}=00100b,则公式的offset(偏移)部分被置为0。假如编码是{mode,r/m}=01100b和{mode,r/m}=10100b,offset按1字节或4字节大小编码于指令的位移域(displacement)中。最后,{mode,r/m}=00101b指定了一个绝对寻址模式,在这种模式中,指令的地址由编码指令的4字节位移域直接提供。在64位模式下,这种寻址模式转变为RIP相对寻址(参见“RIP相对寻址”的文档部分)。
AMD64架构扩充了一倍的通用寄存器数量并扩展了位宽到64位。也扩充了一倍的XMM/YMM寄存器数量。为了支持包含于8个额外通用寄存器或额外的XMM/YMM寄存器中的操作数寻址规范,并使得扩充的通用寄存器可以存储在寻址模式中的使用的地址,REX前缀提供了R,X和B位域,用于将各种操作模式中使用到的ModRM和SIB字节中的reg,r/m,index,和base域扩展到4个bit位。第四个REX位域(W)允许指令编码指定64位操作数大小。
表表4-6以及本节后面部分描述REX的每一个位域。
-------------------------------------------表4-6 REX前缀字节的域---------------------------------------------
助记符 |
位的位置 |
描述 |
-- |
7:4 |
01004h |
REX.W |
3 |
0=缺省操作数大小,1=64位操作数大小 |
REX.R |
2 |
ModRM的reg域的1bit(MSB)扩展,允许访问16个寄存器。 |
REX.X |
1 |
ModRM的index域的1bit(MSB)扩展,允许访问16个寄存器。 |
REX.B |
0 |
ModRM的r/m域、SIB的base域、或者操作码reg域的1bit(MSB)扩展,允许访问16个寄存器。 |
REX.W: 操作数宽度(位3)。将REX.W位置为1则指定操作数大小为64位。类似于现有的66H操作数大小重写前缀。REX 64位操作数大小重写不会影响字节操作。因为这是无字节操作(non-byte),REX操作数大小重写占位于66H之前,假如66H前缀与REX前缀一起使用,并且REX前缀的W域已设置为1,则系统忽略66H前缀。然而,假如66H前缀与REX前缀一起使用,并且REX前缀的W域已设置为0,则系统不会忽略66H前缀,并且将操作数大小置为16位。
REX.R: 寄存器域扩展(位2)。当ModRM.reg域用于编码通用寄存器、YMM/XMM寄存器、控制寄存器、调试寄存器时,REX.R位使系统增加一个1位的扩展(在最高有效位的位置)到ModRM.reg域。当ModRM.reg域指定其它寄存器或用于扩展编码时,REX.R不会修改ModRM.reg域。对于这种情况,系统会忽略REX.R位。
REX.X: 变址域扩展(位1)。REX.X位使系统增加一位的扩展(在最高有效位的位置)到SIB.index域。
REX.B: 基址域扩展(位0)。REX.B位使系统增加一位的扩展(在最高有效位的位置)到ModRM.r/m域,从而指定一个通用寄存器或XMM寄存器;或者增加一位的扩展(在最高有效位的位置)到SIB.base域,从而指定一个通用寄存器。
位移(displacement)(又称“offset”,偏移)(注意:这里存在概念混用。如果在32位模式下,有段寄存器参与构成寻址的情况,“offset”称为偏移,指的是从段寄存器到当前位置的地址长度,而“displacement”称为位移,指两个地址之间的地址长度,勿混为一淡。这里将位移和偏移不加区别地使用,则是在64位寻址模式下,不考虑段寄存器),是一个被添加到代码段(绝对寻址)或指令指针(相对寻址)基址上的有符号值,加到那一种寻址的地址上构成有效地址,取决于寻址模式。位移的大小是1字节、2字节或者4字节。假如寻址模式要求位移参与构成有效地址,则位移字节(1、2或4字节)则紧编码在指令编码的操作码,ModRM,或SIB字节的后面。
在64位模式下,和遗留模式和兼容模式下一样,ModRM和SIB编码也是用于指定位移的大小。然而,在有效地址计算中,位移是通过有符号数扩展的方式扩展到64位。同样,在64位模式下,系统也提供了某些64位偏移和立即数用以构成MOV指令。具体细节请参见“立即数大小”章节的文档。
立即数是一个值——典型的是一个操作数值——直接编码在指令中(即指令中的常数值)。根据操作码和操作模式的不同,立即数的大小可以是1、2、4或8字节。在64位模式下,系统允许在载入通用寄存器的MOV指令上使用64位立即数,在其它模式下,就会限定只能使用4字节以下的立即数。具体细节请参见“立即数大小”章节的文档。
假如一条指定带有立即数,则立即数字节(1、2、4、或8字节)在指令编码中紧编码于操作码,ModRM,SIB,或偏移字节后面(位于它们之中任何一个的最后面)。一些128位的媒体指令使用立即数字节作为条件编码。
在64位模式下,涉及到64位指令指针内容的寻址(程序计数器)——称为基于程序计数器的相对寻址(RIP相对寻址,或称PC相对寻址)——为某些指令而实现。在这种情况下,有效地址由下一条指令的64位RIP加上一段位移构成。
在遗留X86架构模式下,指令指针的相对寻址仅可在控制传输指令中可获得。而在64位模式下,任何使用ModRM寻址的指令都可以使用RIP相对寻址。这个特征对于寻址位置独立的代码中的数据以及对于寻址全局变量的代码特别有用。
如果没有RIP相对寻址,ModRM指令相对于零寻址内存。如果有RIP相对寻址,ModRM指令相对于使用一个有符号的32位位移的64位RIP寻址。这提供了一个距离RIP有±2G的偏移范围。程序中通常有多处代码引用数据,特别是引用全局数据,这并不是基于寄存器。要加载这样一段程序,加载器典型的做法是为程序在内存中选定一个位置,然后基于这个选定的位置调整程序对全局数据的引用。RIP相对数据寻址使得不必进行这种调整便可以寻址数据。
表4-7显示了RIP相对寻址的ModRM和SIB编码,32位仅位移寻址的冗余形式存在于当前的ModRM和SIB编码中。一个ModRM编码带有几个SIB编码。RIP相对寻址形式使用这几种形式之一进行编码。在64位模式下,ModRM 32位移编码({mod,r/m}=00101b)重定义为RIP+disp32(32位位移),而不是仅位移。
-----------------------------------------------------表4-7 RIP相对寻址编码---------------------------------------
ModRM |
SIB |
遗留或兼容模式 |
64位模式 |
其它64的影响 |
mod=00 r/m=101 |
未出现 |
disp32 |
RIP+disp32 |
基于0(通常)的位移寻址必须使用SIB形式(见下行) |
mod=00 r/m=100[1] |
base=101[2] index=100[3] scale=xx |
disp32 |
disp32 |
无 |
脚注说明: 1. 用32位位移编码变址寄存器间接寻址模式时的指令。 2. base寄存器的规范是null(计算有效地址时,有效地址的base部分置为0)。 3. index寄存器的规范是null(计算有效地址时,有效地址的scale*index部分置为0)。 |
ModRM的RIP相对寻址不依赖于REX前缀。特别是r/m的101编码,习惯于选择RIP相对寻址,因此不受REX前缀的影响。例如,选择R13(REX.B = 1, r/m = 101)并且mod=00
仍然会导致RIP相对寻址。
ModRM的四位r/m字段并未完全解码。因此,为了在无偏移的情况下编址R13,软件必须将它以1个位的0偏移编码为R13+0。
RIP相对寻址由64位模式使能,而不是由64地址大小使能。反过来说,使用地址大小前缀(参见“地址大小重写前缀”部分的文档)不会禁用RIP相对寻址。地址大小前缀的影响在于截断并零扩展所计算的有效地址到32位,和任何其它的寻址模式相同。
在下图4-3中,列出了四个例子,展示了REX前缀的R,X和B位是如何与ModRM字节,SIB字节,和操作码字节串联起来指定寄存器和内存寻址的。
在遗留架构下,字节寄存器(AH, AL, BH, BL, CH, CL, DH, 和DL)在ModRM的reg域或r/m域中,或在操作码的reg域 中,按照寄存器从0到7进行编码。REX前缀提供了另外一种字节寄存器寻址功能,使得可获得的任何按最低有效位表示的通用寄存器实现对字节的操作。这就提供了一套统一的字节,字,双字,和四字寄存器,更加适用于编码器分配寄存器。
需了解指令编码细节的读者应当明白,某些ModRM和SIB域的组合,对寄存器编码而言具有特殊的意义。对于其中的一些组合而言,由REX前缀扩展的指令域不会被编码(系统会忽略它),在扩展寄存器中,只是创建这些编码的别名。表4-8分别描述了这些情况的行为。
-----------------------------------表4-8 寄存器的REX专用编码------------------------------------------------
ModRM和SIB编码[2] |
在遗留和兼容模式下的含义 |
在遗留和兼容模式下的影响 |
其它REX的影响 |
ModRM字节: mod≠11 r/m[1]≠100(ESP) |
SIB字节出现 |
基于ESP的寻址要 求SIB字节 |
REX前缀加了第四个位,其被解码并修改SIB字节的基址寄存器。因此,基于R12的寻址也要求SIB字节。 |
ModRM字节: mod≠00 r/m[1]=x101(EBP) |
不使用基寄存器 |
使用EBP而不带位移必须设置mod=01并带一个位移0(可带也可不带变址寄存器) |
REX前缀加了第四个位,其不被解码(不关心)。因此,使用EBP或R13而不带位移,必须通过设置mod=01并带一个位移0的方式实现。 |
SIB字节: index[1]=x100(ESP) |
不使用变址寄存器 |
ESP不能用作变址寄存器 |
REX前缀加了第四个位,其被解码。因此,没有附加影响,扩展的变址域用将区分使用ESP或R12,允许将R12当成变址寄存器使用。 |
SIB字节: base=b101(EBP) ModRM.mod=00 |
如果ModRM.mod=00,不使用基址寄存器。 |
基址寄存器取决于mod编码,使用比例index而不使用位移必须通过设置mod=01实现,无需加上0位移。 |
REX前缀加了第四个位,其不被解码(不关心)。因此,使用RBP或R13而不带位移,必须通过设置mod=01并带一个位移0的方式实现 (可带也可不带变址寄存器) 。 |
脚注说明: 1. REX前缀位展示在ModRM的r/m域,SIB的index域,以及SIB的base域编码的第四个位(最高有效位)。ModRM的r/m是小写的“x”表示REX前缀位没有解码(不关心)。 2. 有关 ModRM和SIB字节的说明,请参阅“ModRM和 SIB字节”的文档部分。 |
下面是使用REX的操作数寻址扩展示例。
------------------------图4-3 使用REX的R,X和B位进行编码的示例---------------------------
案例一:寄存器到寄存器的寻址(无内存操作数):
案例二:无SIB字节的内存寻址:
案例三:带SIB字节的内存寻址:
案例四:寄存器操作数编码于操作码字节中
通过编码转义前缀引入了转义序列,从而建立了字节遵循的上下文环境和格式。当前定义的前缀分为两类:XOP和VEX前缀。XOP前缀和VEX C4h前缀使用相同的语法引入了一个3字节序列,而VEX C5h前缀使用不同的语法引入了一个2字节转义序列。
这些转义序列提供了用于扩展操作数规范的域以及提供了可替换操作数映射的选择。编码支持最多两种额外的操作数以及扩展的(超过7个)寄存器寻址。双操作数的规范伴随着使用遗留模式的ModRM和可选的SIB字节,SIB字节的reg,r/m,index和base域分别扩展了1个位,在行为是类似于REX前缀。
扩展的SSE指令编码利用扩展的转义序列。XOP指令使用3字节转义序列,其通过XOP前缀引入。AVX, FMA, FMA4,和CLMUL指令子集使用3字节或2字节转义序列,其通过VEX前缀引入。
所有的扩展指令都可以使用三字节转义序列,但是某些VEX扩展指令遵从下一节4.9.2节所描述的约束条件。“隔开的两字节转义序列”也可以利用“合在一起的一对双字节转义序列”。下图4-4展示了常见的基于XOP和VEX编码的三字节转义序列的格式。
-------------------------------图4-4 XOP/VEX三字节转义序列格式 -----------------------------------------------
----------------------------------------表4-9 三字节转义序列域定义 -------------------------------------------------
字节 |
位 |
助记符 |
描述 |
0 |
[7:0] |
VEX, XOP |
特定于扩展指令集的值 |
1 |
[7] |
R |
ModRM reg域的反转一位扩展 |
[6] |
X |
SIB index域的反转一位扩展 |
|
[5] |
B |
r/m域或SIB base域的反转一位扩展 |
|
[4:0] |
映射选择 |
操作码映射选择 |
|
2 |
[7] |
W |
在64位模式下,对于通用寄存器,缺省的操作数大小重写为64位;作为某些基于 YMM/XMM 的操作的操作数配置说明符。 |
[6:3] |
vvvv |
源或目的寄存器选择符,采用补码格式 |
|
[2] |
L |
向量长度说明符 |
|
[1:0] |
pp |
意味着66,F2或F3操作码扩展。 |
(1) 字节0(VEX/XOP前缀)
字节0是编码转义字节,它引入了编码转义序列,并确立了它所随后要遵循的上下文环境。VEX和XOP前缀遵循下面的规范:
(a) VEX前缀编码为C4h
(b) VEX前缀编码为8Fh
(2) 字节1
(a) VEX/XOP.R(位7)
这个反转位相当于REX.R位,在64位模式下,ModRM.reg域的1个位扩展,允许访问16个YMM/XMM寄存器和通用寄存器。在32位保护模式和兼容模式下,这个位值必须设置为1。
(b) VEX/XOP.X(位6)
这个反转位相当于REX.X位,在64位模式下,SIB.index域的1个位扩展,允许访问16个YMM/XMM寄存器和通用寄存器。在32位保护模式和兼容模式下,这个位值必须设置为1。
(c) VEX/XOP.B(位5)
这个反转位相当于REX.B位,仅在三字节前缀模式下可获得,ModRM.r/m域的1位扩展用于指定通用寄存器或XMM寄存器,或者SIB.base域的1位扩展用于指定通用寄存器。这允许访问16个YMM/XMM寄存器和通用寄存器。在32位保护模式和兼容模式下,这个位将被忽略。
(d) VEX/XOP.map_select(位[4:0])
5位map_select域用于选择一个可替换的操作码映射。VEX和XOP的map_select域编码空间是不连惯的,下表4-10列出了VEX.map_select域对应的编码,表4-11列出了XOP.map_select域对应的编码。
-------------------------------------表4-10 VEX.map_select域编码---------------------------------------------
二进制值 |
操作码映射 |
类比于遗留操作码映射 |
00000 |
保留 |
无 |
00001 |
VEX操作码映射1 |
第二个(“2字节”)操作码映射 |
00010 |
VEX操作码映射2 |
0F_38h(“3字节”)操作码映射 |
00011 |
VEX操作码映射3 |
0F_3Ah(“3字节”)操作码映射 |
00100-11111 |
保留 |
---------------------------------------------表4-10 XOP.map_select域编码------------------------------------------
二进制值 |
操作码映射 |
00000-00111 |
保留 |
01000 |
XOP操作码映射8 |
01001 |
XOP操作码映射9 |
01010 |
XOP操作码映射10(Ah) |
01011-11111 |
保留 |
AVX指令使用VEX操作码映射1-3进行编码。AVX指令集提供的指令所包含的操作类似于大部分遗留SSE指令所提供的操作。对于那些功能类似于遗留SSE指令功能的AVX指令,VEX操作码映射使用与遗留版本相同的二进制操作码值和修饰符。VEX操作码映射与遗留操作码映射之间的对应关系见上表4-10。
VEX操作码映射1-3也用于编码FMA4和FMA指令。此外,并非所有遗留SSE指令都具有对应相当的AVX指令。因此,VEX操作码映射与AVX操作码映射并不是相同的。
XOP操作码映射对于XOP指令而言是唯一的。XOP.map_select的值严格限定于范围[08h:1Fh]。假如XOP.map_select域的值小于8,三字节XOP转义序列的前面两字节被解释成POP指令的形式。
遗留和扩展操作码映射覆盖的细节请参见文档的附录A。
(3) 字节2
(a) VEX/XOP.W(位7)
此功能与具体指令相关。此位通常用于配置源操作数的次序。
(b) VEX/XOP.vvvv (位[6:3])
为三个或四个操作数的指令指定另一个操作数。以反转1的补码的形式编码XMM或YMM寄存器,见下表4-11。
-----------------------------------------------表4-11 VEX/XOP.vvvv编码--------------------------------------------
二进制值 |
寄存器 |
二进制值 |
寄存器 |
0000 |
XMM15/YMM15 |
1000 |
XMM07/YMM07 |
0001 |
XMM14/YMM14 |
1001 |
XMM06/YMM06 |
0010 |
XMM13/YMM13 |
1010 |
XMM05/YMM05 |
0011 |
XMM12/YMM12 |
1011 |
XMM04/YMM04 |
0100 |
XMM11/YMM11 |
1100 |
XMM03/YMM03 |
0101 |
XMM10/YMM10 |
1101 |
XMM02/YMM02 |
0110 |
XMM09/YMM09 |
1110 |
XMM01/YMM01 |
0111 |
XMM08/YMM08 |
1111 |
XMM00/YMM00 |
在32位模式下,值0000b到0111b是无效值。vvvv典型的用法是用于编码第一个源操作数,但是对于VPSLLDQ, VPSRLDQ, VPSRLW, VPSRLD, VPSRLQ, VPSRAW, VPSRAD,
VPSLLW, VPSLLD, 和VPSLLQ转换指令,这个域用于指定目标寄存器。
(c) VEX/XOP.L(位2)
L=0指定了128位向量长度(XMM寄存器/128位内存位置)。L=1指定256位向量长量(YMM寄存器/256位内存位置)。对于SSE或者XOP标题操作数,则系统忽略L位。某些向量SSE指令仅支持128位向量大小。对于这些指令,L位置为0。
(d) VEX/XOP.pp(位[1:0])
这两个位用于指定一个隐含的66h,F2h或66h,F3h操作码扩展,其功能类似于遗留指令编码用于扩展操作作码编码空间的功能。VEX/XOP.pp域的编码与其作为操作码修饰符的功能之间的对应关系见下表4-12,遗留前缀66h,F2h或66h,F3h不允许用于扩展指令编码。
---------------------------------------------表4-12 VEX/XOP.pp的编码-----------------------------------------------
二进制值 |
隐含前缀 |
00 |
无 |
01 |
66h |
10 |
F3h |
11 |
F2h |
所有VEX编码指令都可以使用三字节转义序列进行编码,但是某些指令也可以更多地采用压缩编码,使用两字节转义序列编码。两字节转义序列的格式如下图4-5所示。
------------------------------------------------图4-5 VEX两字节转义序列格式-----------------------------
------------------------------------表4-13 VEX双字节转义序列域定义 ---------------------------------------------
前缀字节 |
位 |
助记符 |
描述 |
0 |
[7:0] |
VEX |
VEX两字节编码转义前缀 |
1 |
7 |
R |
反转的ModRM.reg域1位扩展 |
[6:3] |
vvvv |
源或目标寄存器选择符,1的补码格式 |
|
2 |
L |
向量长度修饰符 |
|
[1:0] |
pp |
隐含的66h,F2h或66h,F3h操作码扩展 |
(1) 字节0
VEX前缀的两字节转义序列的编码为C5h。
(2) 字节1
需要指出的是,当以三字节转义序列形式编码的时候,这个字节的第7位用于VEX.R而不是VEX.W。R,L,vvvv和pp定义在三字节转义序列中。
当应用两字节转义序列的时候,对应三字节格式的具体域采用固定值的形式,如下表4-14所示。
----------------------------------------------表4-14 两字节VEX格式的固定域值------------------------------------
VEX域 |
值(二进制) |
X |
1 |
B |
1 |
W |
0 |
map_select |
00001 |
尽管可以使用VEX三字节转义序列进行编码。符合列表4-14中的约束的所有指令都可以使用两字节转义序列编码。需要指出的是,map_select的隐含值是00001b,这意味着,只要包含在VEX操作码映射表1中的指令,才可以使用这种格式编码。
使用另一种map_select定义值(00010b和00011b)的VEX编码指令不能采用两字节转义序列格式。注意,VEX.pp域的值是显式地编码于这种格式中,不能指定任何表4-14中定义的任何隐式值。
注: 本文内容主要翻译整理自AMD处理器文档的编码部分,其中涉及到的参见部分和编码映射表,请查阅AMD文档中的附录部分。