如何阅读英特尔操作码表示法

ash*_*her 15 x86 assembly intel machine-code opcode

我正在阅读一些关于英特尔操作码汇编指令的资料,但我无法理解它遵循操作码字节是什么意思.例如:"cw","cd","/ 2","cp","/ 3".请给我一个提示是什么意思或在哪里可以找到完整的参考?提前致谢!

E8 cw CALL rel16相对于下一条指令调用near,relative,displacement

E8 cd CALL rel32相对于下一条指令调用near,relative,displacement

FF / 2 CALL r/m16调用r/m16中给出的接近绝对间接地址

FF / 2 CALL r/m32调用r/m32中给出的接近绝对间接地址

9A cd CALL ptr16:16调用操作数中给出的far,absolute,address

9A cp CALL ptr16:32调用操作数中给出的far,absolute,address

FF / 3 CALL m16:16调用m16:16中给出的远,绝对间接地址

FF / 3 CALL m16:32调用m16:32中给出的远,绝对间接地址

Mar*_*kus 15

3.1.1.1指令汇总表中的操作码列(不带VEX前缀的指令)

上表中的"Opcode"列显示了为每种形式的指令生成的目标代码.在可能的情况下,代码以十六进制字节的形式给出,其顺序与它们在内存中的出现顺序相同.十六进制字节以外的条目定义如下:

•REX.W -表示使用影响操作数大小或指令语义的REX前缀.第2章讨论了REX前缀和其他可选/强制指令前缀的顺序.请注意,在操作码列中未明确列出将旧式指令提升为64位行为的REXprefix.

•/ digit - 0到7之间的数字表示指令的ModR/M字节仅使用r/m(寄存器存储器)操作数.reg字段包含提供指令操作码扩展的数字.

•/ r -表示指令的ModR/M字节包含寄存器操作数和r/m操作数.

•cb,cw,cd,cp,co,ct - 1字节(cb),2字节(cw),4字节(cd),6字节(cp),8字节(co)或10 -byte(ct)值遵循操作码.此值用于指定代码段寄存器的代码偏移量和可能的新值.

•ib,iw,id,io -操作码后面的指令的1字节(ib),2字节(iw),4字节(id)或8字节(io)立即操作数,ModR/M字节或缩放索引字节.操作码确定操作数是否为有符号值.所有单词,双字和四字首先以低位字节给出.

•+ rb,+ rw,+ rd,+ ro -表示操作码字节的低3位用于编码寄存器操作数而没有modR/M字节.该指令将低3位操作码字节的相应十六进制值列为000b.在非64位模式下,寄存器代码(从0到7)被添加到操作码字节的十六进制值.在64位模式下,表示REX.b的4位字段,操作码[2:0]字段对指令的寄存器操作数进行编码."+ ro"仅适用于64位模式.代码见表3-1.

•+ i -其中一个操作数是FPU寄存器堆栈中的ST(i)时浮点指令中使用的数字.数字i(范围从0到7)被添加到加号左侧给出的十六进制字节,以形成单个操作码字节.

3.1.1.3操作码汇总表中的指令列

"指令"列给出了ASM386程序中出现的指令语句的语法.

以下是用于在指令语句中表示操作数的符号列表:

•rel8 -相对地址,范围从指令结束前的128个字节到指令结束后的127个字节.

•rel16,rel32 -与组装指令在同一代码段内的相对地址.rel16符号适用于操作数大小为16位的指令; rel32符号适用于操作数大小为32位的指令.

•ptr16:16,ptr16:32 -一个far指针,通常指向与指令不同的代码段.符号16:16表示指针的值有两部分.冒号左边的值是一个16位选择器或者代码段寄存器的值.右侧的值对应于目标段内的偏移量.当指令的操作数大小属性为16位时,使用ptr16:16符号; 当操作数大小属性为32位时,使用ptr16:32符号.

•r8 -字节通用寄存器之一:AL,CL,DL,BL,AH,CH,DH,BH,BPL,SPL,DIL和SIL; 或者使用REX.R和64位模式时可以使用其中一个字节寄存器(R8L-R15L).

•r16 -通用寄存器之一:AX,CX,DX,BX,SP,BP,SI,DI; 或者使用REX.R和64位模式时可以使用其中一个字寄存器(R8-R15).

•r32 -双字通用寄存器之一:EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI; 或在64位模式下使用REX.R时可使用双字寄存器(R8D-R15D)之一.

•r64 -四字通用寄存器之一:RAX,RBX,RCX,RDX,RDI,RSI,RBP,RSP,R8-R15.这些在使用REX.R和64位模式时可用.

•imm8 -立即字节值.imm8符号是-128和+127之间的带符号数字.对于imm8与字或双字操作数组合的指令,立即值可以形成单词或双字.该字的高字节填充了立即值的最高位.

•imm16 -用于操作数大小属性为16位的指令的立即字值.这是介于-32,768和+32,767之间的数字.

•imm32 -用于操作数大小属性为32位的指令的立即双字值.它允许使用+2,147,483,647和-2,147,483,648之间的数字.

•imm64 -用于操作数大小属性为64位的指令的立即四字值.该值允许使用+9,223,372,036,854,775,807和-9,223,372,036,854,775,808之间的数字.

•r/m8 -字节操作数,它是字节通用寄存器(AL,CL,DL,BL,AH,CH,DH,BH,BPL,SPL,DIL和SIL)的内容或来自存储器的字节.字节寄存器R8L - R15L在64位模式下使用REX.R可用.

•r/m16 -用于操作数大小属性为16位的指令的字通用寄存器或存储器操作数.通用寄存器一词是:AX,CX,DX,BX,SP,BP,SI,DI.存储器的内容在有效地址计算提供的地址处找到.字寄存器R8W-R15W在64位模式下使用REX.R可用.

•r/m32 -双字通用寄存器或存储器操作数,用于其operandsize属性为32位的指令.双字通用寄存器是:EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI.存储器的内容在有效地址计算提供的地址处找到.在64位模式下使用REX.R时,双字寄存器R8D-R15D可用.

•r/m64 -四字通用寄存器或存储器操作数,用于使用REX.W时操作数大小属性为64位的指令.四字通用寄存器是:RAX,RBX,RCX,RDX,RDI,RSI,RBP,RSP,R8-R15; 这些仅在64位模式下可用.存储器的内容在有效地址计算提供的地址处找到.

•m -内存中的16位,32位或64位操作数.

•m8 -内存中的字节操作数,通常表示为变量或数组名称,但由DS指出:(E)SI或ES:(E)DI寄存器.在64位模式下,RSI或RDI寄存器指向它.

•m16 -存储器中的字操作数,通常表示为变量或数组名称,但由DS指出:(E)SI或ES:(E)DI寄存器.此命名法仅用于字符串指令.

•m32 -内存中的双字操作数,通常表示为变量或数组名称,但由DS指出:(E)SI或ES:(E)DI寄存器.此命名法仅用于字符串指令.

•m64 -内存中的内存四字操作数.

•m128 -内存中的内存双四字操作数.

•m16:16,m16:32和m16:64 -包含由两个数字组成的远指针的内存操作数.冒号左侧的数字对应于指针的段选择器.右边的数字对应于它的偏移量.

•m16&32,m16和16,m32和32,m16和64 -由数据项对组成的内存操作数,其大小显示在&符号的左侧和右侧.允许所有内存寻址模式.BOUND指令使用m16&16和m32&32操作数来提供包含数组索引的上限和下限的操作数.LIDT和LGDT使用m16和32操作数来提供用于加载限制字段的字,以及用于加载相应GDTR和IDTR寄存器的基本字段的双字.LIDT和LGDT在64位模式下使用m16和64操作数来提供用于加载限制字段的字,以及用于加载相应GDTR和IDTR寄存器的基本字段的四字.

•moffs8,moffs16,moffs32,moffs64 - MOV指令的某些变体使用的字节,字或双字类型的简单存储器变量(存储器偏移).实际地址由相对于段基的简单偏移给出.指令中没有使用ModR/M字节.moffs显示的数字表示其大小,该大小由指令的address-size属性决定.

•Sreg -段寄存器.段寄存器位分配是ES = 0,CS = 1,SS = 2,DS = 3,FS = 4,GS = 5.

•m32fp,m64fp,m80fp - 内存中的单精度,双精度和双扩展精度(分别)浮点操作数.这些符号指定浮点值,用作x87 FPU浮点指令的操作数.

•m16int,m32int,m64int - 内存中的字,双字和四字整数(分别)操作数.这些符号表示用作x87 FPU整数指令的操作数的整数.

•ST或ST(0) - FPU寄存器堆栈的顶部元素.

•ST(i) - FPU寄存器堆栈顶部的第i个元素(i←0到7).

•mm - MMX寄存器.64位MMX寄存器为:MM0到MM7.

•mm/m32 - MMX寄存器的低32位或32位存储器操作数.64位MMX寄存器为:MM0到MM7.存储器的内容在有效地址计算提供的地址处找到.

•mm/m64 - MMX寄存器或64位存储器操作数.64位MMX寄存器为:MM0到MM7.存储器的内容在有效地址计算提供的地址处找到.

•xmm - XMM寄存器.128位XMM寄存器是:XMM0到XMM7; 在64位模式下使用REX.R可以使用XMM8到XMM15.

•xmm/m32- XMM寄存器或32位内存操作数.128位XMM寄存器是XMM0到XMM7; 在64位模式下使用REX.R可以使用XMM8到XMM15.存储器的内容在有效地址计算提供的地址处找到.

•xmm/m64 - XMM寄存器或64位内存操作数.128位SIMD浮点寄存器是XMM0到XMM7; 在64位模式下使用REX.R可以使用XMM8到XMM15.存储器的内容在有效地址计算提供的地址处找到.

•xmm/m128 - XMM寄存器或128位内存操作数.128位XMM寄存器是XMM0到XMM7; 在64位模式下使用REX.R可以使用XMM8到XMM15.存储器的内容在有效地址计算提供的地址处找到.

• - 表示隐含使用XMM0寄存器.当存在歧义时,xmm1表示使用XMM寄存器的第一个源操作数,xmm2表示使用XMM寄存器的第二个源操作数.某些指令使用XMM0寄存器作为第三个源操作数,表示为.第三个XMM寄存器操作数的使用隐含在指令编码中,不会影响ModR/M编码.

•ymm - YMM寄存器.256位YMM寄存器是:YMM0到YMM7; YMM8到YMM15在64位模式下可用.

•m256 -内存中的32字节操作数.此命名法仅用于AVX指令.

•ymm/m256 - YMM寄存器或256位内存操作数.

• - 表示使用YMM0寄存器作为隐式参数.

•bnd - 128位边界寄存器.BND0到BND3.

•mib -使用SIB寻址形式的内存操作数,其中索引寄存器不用于地址计算,忽略Scale.在有效地址计算中仅使用基数和位移.

•m512 -内存中的64字节操作数.

•zmm/m512 - ZMM寄存器或512位内存操作数.

•{k1} {z} -用作指令写掩码的掩码寄存器.64位k寄存器是:k1到k7.Writemask规范仅通过EVEX前缀提供.屏蔽可以作为合并屏蔽完成,其中旧值保留用于屏蔽元素或作为归零屏蔽.使用EVEX.z位确定掩码类型.

•{k1} -没有{z}:掩码寄存器用作指令写掩码,用于不允许归零但支持合并掩码的指令.这对应于要求aaa字段的值不同于0(例如,聚集)的指令和仅允许合并掩蔽的存储类型指令.

•k1 -用作常规操作数(目标或源)的掩码寄存器.64位k寄存器是:k0到k7.

•mV -向量存储器操作数; 操作数大小取决于指令.

•vm32 {x,y,z} -使用VSIB内存寻址指定的内存操作数的向量数组.使用公共基址寄存器,常量比例因子和向量索引寄存器指定存储器地址数组,该寄存器具有XMM寄存器(vm32x),YMM寄存器(vm32y)或ZMM寄存器中32位索引值的各个元素. (vm32z).

•vm64 {x,y,z} -使用VSIB内存寻址指定的内存操作数的向量数组.使用公共基址寄存器,常量比例因子和向量索引寄存器指定存储器地址数组,该寄存器具有XMM寄存器(vm64x),YMM寄存器(vm64y)或ZMM寄存器中的64位索引值的各个元素(vm64z).

•zmm/m512/m32bcst -可以是ZMM寄存器,512位存储器位置或从32位存储器位置加载的512位向量的操作数.

•zmm/m512/m64bcst -可以是ZMM寄存器,512位存储器位置或从64位存储器位置加载的512位向量的操作数.

• -表示使用ZMM0寄存器作为隐式参数.

•{er} -表示支持嵌入式舍入控制,仅适用于指令的寄存器寄存器形式.这也意味着支持SAE(抑制所有异常).

•{sae} -表示支持SAE(抑制所有异常).这用于支持SAE的指令,但不支持嵌入式舍入控制.

•SRC1 -表示使用VEX/EVEX前缀编码且具有两个或更多源操作数的指令的指令语法中的第一个源操作数.

•SRC2 -表示使用VEX/EVEX前缀编码且具有两个或更多源操作数的指令的指令语法中的第二个源操作数.

•SRC3 -表示使用VEX/EVEX前缀编码且具有三个源操作数的指令的指令语法中的第三个源操作数.

•SRC -单源指令中的源.

•DST -指令中的目标.该字段由reg_field编码.


Doc*_*Max 12

我最喜欢的来源是英特尔本身:英特尔®64和IA-32架构软件开发人员手册.与过去的版本不同,所有卷现在都很好地包含在一个(3044页)PDF中.

看起来最有帮助的部分是第2卷第3章中的3.1.1.1(截至我写这篇文章的最新PDF版本的第432页).


Pet*_*des 10

指令的立即版本的许多操作码,包括83使用/rModR/M 字节中的 3位字段作为 3 个额外的操作码位。英特尔的 vol.2 手册记录了这一点,我认为附录中的操作码表包括它。

这就是为什么最原始的8086立即作出指示,如and r/m, imm仍只允许2个操作数,不像shrd eax, edx, 4imul edx, [rdi], 12345其中两个ModRM字段用于编码DST / SRC操作数,以及操作码暗示的立即操作数。

SHRD/SHLD 并添加了 386,并且 imul-immediate 添加了 186。 不幸的是,复制与与 ( and eax, edx, 0xf) 不可编码,但至少 x86 可以使用 LEA 进行非常常见的复制和添加或子操作。

但是,如果每个立即数和单操作数指令(如pushnot)都需要一个完整的操作码,那么 8086 就会用完 1 字节的操作码。(特别是因为设计者选择在没有 AL 和 AX 的 modrm 字节的短格式上花费大量编码空间,例如cmp ax, 12345在 16 位模式中只有 3 个字节而不是 4 个,或者在 32位模式cmp eax, imm32中只有 5 个字节而不是 6 个字节。cmp r/m32, imm32-bit 模式。对于单字节 xchg-with-ax 和一字节 inc/dec 寄存器。)


示例:解码48 83 C4 38 (从如何根据“寄存器/操作码”字段将一个操作码字节解码为不同的指令?那是什么?,此 Q 的副本)

48 是一个 REX.W 前缀(REX 只设置了 W 位,所以它表示 64 位操作数大小,但没有高位寄存器)。

操作码83说它可以是 7 种不同的指令,具体取决于称为“寄存器/操作码字段”的字段

每条指令自己的文档,例如add(vol2 手册的 html 摘录),都显示了类似
REX.W + 83 /0 ibfor 的编码ADD r/m64, imm8,这就是你所拥有的。

来自 wiki.osdev.org 的 ModRM 位域图

  7                           0
+---+---+---+---+---+---+---+---+
|  mod  |    reg    |     rm    |
+---+---+---+---+---+---+---+---+
Run Code Online (Sandbox Code Playgroud)

0xc4 = 0b11000100,因此 reg 字段 = 0。因此我们的操作码是83 /0,在英特尔的表示法中。

其余的 ModRM 字段是:

  • mode = 0b11,因此 rm 字段编码寄存器操作数,而不是寻址模式的基址寄存器。
  • rm = 0b100。reg #4 = SPL/SP/ESP/RSP。(在本例中为 RSP,因为它是 64 位操作数大小)。请参阅英特尔的手册,或https://wiki.osdev.org/X86-64_Instruction_Encoding#Registers以获取表格。

所以指令是 add rsp, 0x38

ndisasm -b64 同意:

$ cat > foo.asm
db 0x48, 0x83, 0xC4, 0x38
$ nasm foo.asm     # create a flat binary with those bytes, not an object file
$ ndisasm -b64 foo
00000000  4883C438          add rsp,byte +0x38
Run Code Online (Sandbox Code Playgroud)