前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >X86指令格式(操作码列和指令列解释)

X86指令格式(操作码列和指令列解释)

原创
作者头像
franket
发布2020-06-18 22:34:39
33K0
发布2020-06-18 22:34:39
举报
文章被收录于专栏:技术杂记技术杂记

操作码列

1.主操作码是 1、2 或 3 字节.其中2字节操作码和三字节操作码都在0F开头,但是二字节的SIMD opcode是一个强制前缀+0fh+一字节的操作码:

一字节操作码示例:

操作码

指令

说明

98

CBW

AX  AL 的符号扩展

FF /4

JMP r/m32

绝对间接近跳转,地址由 r/m32 给出

2.只要可能,便会按照内存中的出现顺序以十六进制字节的形式给出这些代码,非十六进制字节的其它定义如下:

操作码

指令

说明

E8 cw

CALL rel16

相对近调用,位移量相对于下一条指令

E8 cd

CALL rel32

相对近调用,位移量相对于下一条指令

FF /2

CALL r/m16

绝对间接近调用,地址由 r/m16 给出

FF /2

CALL r/m32

绝对间接近调用,地址由 r/m32 给出

9A cd

CALL ptr16:16

绝对远调用,地址由操作数给出

9A cp

CALL ptr16:32

绝对远调用,地址由操作数给出

FF /3

CALL m16:16

绝对间接远调用,地址由 m16:16 给出

FF /3

CALL m16:32

绝对间接远调用,地址由 m16:32 给出

/digit

 为0到7之间的数字,表示指令的 ModR/M byte 只使用 r/m字段作为操作数,而其reg字段作为opcode的一部分,使用digit(下表的/digit(Opcode列))指定的数字

这里再解释下ModR/M byte,图请看http://hgy413.com/3288.html

代码语言:javascript
复制
mod(模式)域:连同r/m(寄存器/内存)域共同构成了32个可能的值:8个寄存器和24个寻址模式。
reg/opcode(寄存器/操作数)域:指定了8个寄存器或者额外的3个字节的opcode。究竟这三个字节用来做什么由主opcode指定。
r/m(寄存器/内存)域:可以指定一个寄存器作为操作数,或者可以和mod域联合用来指定寻址模式。有时候,它和mod域一起用来为某些指令指定额外的信息。

一个指令往往需要引用一个在内存当中的值,典型的如mov:

代码语言:javascript
复制
MOV eax, dword ptr [123456]///一个立即数表示的地址
MOV eax, dword ptr [esi]///一个存放在寄存器当中的地址
MOV eax, ebx///寄存器本身

这其中的 123456 或者 esi 就是 MOV 指令引用的内存地址,而MOV关心的是这个地址当中的内容。这个时候,需要某种方式来为指令指定这个操作数的类型:是一个立即数表示的地址,还是一个存放在寄存器当中的地址,或者,就是寄存器本身。

这个用来区分操作数类型的指令字节就是 ModR/M,确切的说是其中的5个位,即mod和r/m域。剩下的三个位,可能用来做额外的指令字节。因为,IA32的指令个数已经远超过一个字节所能表示的256个了。因此,有的指令就要复用第一个字节,然后依据ModR/M当中的reg/opcode域进行区分。

CALL指令的表示法:FF /2,是 0xFF 后面跟着一个 /digit 表示的东西。就是说,0xFF后面需要跟一个ModR/M字节,ModR/M字节使用reg/opcode域 = 2 。那么,reg/opcode = 2 的字节有32个,正如ModR/M的解释,这32个值代表了32种不同的寻址方式。是哪32种呢?手册上面有张表:

ModRM-2.png
ModRM-2.png

非常复杂的一张表。现在就看看这张表怎么读。对于SIB的介绍,我们先忽略

   首先是列的定义。由于reg/opcode域可以用来表示opcode,也可以用来表示reg,因此同一个值在不同的指令当中可能代表不同的含义。在表当中,就表现为每一列的表头都有很多个不同的表示。我们需要关心的就是 opcode这一个。注意看我用红圈圈出来的部分,这一列就是opcode=2的一列。而我们需要的CALL指令,也就是在这一列当中,0xFF后面需要跟着的内容。

   行的定义就是不同的寻址模式。正如手册所说,mod + R/M域,共5个字节,定义了32种寻址模式。

   0x10--0x17 对应于寄存器寻址。例如指令 CALL dword ptr [eax] :[eax]寻址对应的是0x10,因此,该指令对应的二进制就是 FF 10。同理, CALL dword ptr [ebx] 是 FF 13,CALL dword ptr [esi] 是 FF 16,这些指令都是2个字节。有人也许问 CALL word ptr [eax] 是什么?抱歉,这不是一个合法的32位指令。

blob.png
blob.png

注意到这一列中有个disp32,说明是ff 15 + 32位数据:

代码语言:javascript
复制
00020000 ff1510203040    call    dword ptr ds:[40302010h]

   0x50-0x57部分需要带一个disp8,即 8bit立即数,也就是一个字节。这个是基地址+8位偏移量的寻址模式。例如 CALL dword ptr [eax+10] 就是 FF 50 10 。注意虽然表当中写的是 [eax] + disp8 这种形式,但是并不表示是取得 eax 指向的地址当中的值再加上 disp8,而是在eax上加上disp8再进行寻址。因此写成 [eax+disp8] 更不容易引起误解。后面的disp32也是一样的。这个类型指令是3个字节。

代码语言:javascript
复制
00020000 ff5130          call    dword ptr [ecx+30h]
blob.png
blob.png

  0x90-0x97部分需要带 disp32,即4字节立即数。这个是基地址+32位偏移量。例如 CALL dword ptr [eax+12345] 就是 FF 90 00 01 23 45。有趣的是, CALL dword ptr [eax+10] 也可以写成 FF 90 00 00 00 10。至于汇编成哪个二进制形式,这是汇编器的选择。这个类型的指令是6个字节。

代码语言:javascript
复制
00020000 ff9210203040    call    dword ptr [edx+40302010h]
blob.png
blob.png

  0xD0-0xD7部分则直接是寄存器。这边引用的寄存器的类型有很多,但是在CALL指令当中只能引用通用寄存器,因此 CALL eax 就是 FF D0,臭名昭著的 CALL esp 就是 FF D4。注意 CALL eax 和 CALL [eax] 是不一样的。

代码语言:javascript
复制
00020000 ffd0            call    eax
00020002 ff10            call    dword ptr [eax]
blob.png
blob.png

 这时应该大家注意到了0x14,0x54,0x94,0x14,0x54,0x94部分是最复杂的,因为这个时候,ModR/M不足以指定寻址方式,而是需要一个额外的字节,这个字节就是指令当中的第4个字节SIB,SIB字节包括下列信息:

某些特定的ModR/M字节需要一个后续字节,称为SIB字节。32位指令的基地址+偏移量,以及 比例*偏移量 的形式的寻址方式需要SIB字节。\ scale(比例)域指定了放大的比例。 index(偏移)域指定了用来存放偏移量 的寄存器。 base (基地址)域用来标识存放基地址的寄存器。

0x14, 0x54, 0x94就是这里所说的“特定的ModR/M字节。这个字节后面跟着的SIB表示了一个复杂的寻址方式,典型的见于虚函数调用:

代码语言:javascript
复制
CALL dword ptr [ecx+4*eax]

就是调用ecx指向的虚表当中的第eax个虚函数。这个指令当中,因为没有立即数,因此FF后面的字节就是0x14,而 [ecx+4*eax] 就需要用SIB字节来表示。SIB确定的寻址方式是[base+Index* Scale +disp]

在这个指令当中,ecx就是 Base,4是Scale,eax是Index。

那么,Base, Scale和Index是如何确定的呢?手册上同样有一张表(又是巨大的表):

1.png
1.png

列是Base,行是Index*Scale,例如[ecx+4*eax] 就是0x81。

blob.png
blob.png

根据这张表,CALL dword ptr [ecx+4*eax] 就是 FF 14 81 。由此可见,对于 0x14系列的来说,CALL指令就是 3个字节。

代码语言:javascript
复制
00020000 ff1481          call    dword ptr [ecx+eax*4]

而 0x54 带 8bit 立即数,就是对应于 CALL指令:CALL dword ptr [ecx+4*eax+xx],这个指令就是 FF 54 81 xx,是4个字节。

代码语言:javascript
复制
00020000 ff548120        call    dword ptr [ecx+eax*4+20h]

同理,0x94带32位立即数,对应于CALL指令:CALL dword ptr [ecx+4*eax+xxxxxxxx],这个指令就是 FF 94 81 xx xx xx xx,是7个字节。

代码语言:javascript
复制
00020000 ff948120304000  call    dword ptr [ecx+eax*4+403020h]

/r

表示指令的 ModR/M 字节同时包含寄存器操作数与 r/m 操作数

89 /r

MOV r/m32,r32

将 r32 移到 r/m32

比如 

代码语言:javascript
复制
00020000 8933            mov     dword ptr [ebx],esi
blob.png
blob.png

cb、cw、cd、cp

1 字节 (cb)、2 字节 (cw)、4 字节 (cd) 或 6 字节 (cp) 值,跟在操作码的后面,用于指定代码偏移量,并可能用于给代码段寄存器指定新的值,一般用于我们在汇编中写call lable

代码语言:javascript
复制
E8 cw 的含义是:字节 0xE8 后面跟着一个2字节操作数表示要跳转到的地址与当前地址的偏移量。
E8 cd 的含义是:字节 0xE8 后面跟着一个4字节的操作数表示要跳转的地址与当前地址的偏移量。
9A cp 的含义是:字节 0x9A 后面跟着一个6字节的操作数表示要跳转的地址和代码段寄存器的值。

ib、iw、id 

指令的 1 字节 (ib)、2 字节 (iw) 或 4 字节 (id) 立即数操作数,跟在操作码、ModR/M字节或基数索引字节的后面。操作码确定操作数是否为有符号值。所有的字与双字都是按照低位字节在先的形式给出。

操作码

指令

说明

14 ib

ADC AL,imm8

带进位将 imm8 加到 AL 上

15 iw

ADC AX,imm16

带进位将 imm16 加到 AX 上

15 id

ADC EAX,imm32

带进位将 imm32 加到 EAX 上

代码语言:javascript
复制
00020000 1510203040      adc     eax,40302010h

+rb、+rw、+rd 

从0到7的寄存器代码,它添加到加号左侧给出的十六进制字节,以形成单个操作码字节。寄存器如下:

操作码

指令

FF /6

PUSH r/m32

50+rw

PUSH r16

50+rd

PUSH r32

rb

rw

rd

AL

=

0

AX

=

0

EAX

=

0

CL

=

1

CX

=

1

ECX

=

1

DL

=

2

DX

=

2

EDX

=

2

BL

=

3

BX

=

3

EBX

=

3

rb

rw

rd

AH

=

4

SP

=

4

ESP

=

4

CH

=

5

BP

=

5

EBP

=

5

DH

=

6

SI

=

6

ESI

=

6

BH

=

7

DI

=

7

EDI

=

7

代码语言:javascript
复制
00020000 50              push    eax
00020001 51              push    ecx
00020002 52              push    edx
00020003 53              push    ebx
00020004 54              push    esp
00020005 55              push    ebp
00020006 56              push    esi
00020007 57              push    edi

+i

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

指令列

rel:relative(rel8,rel16,rel32)

rel8:指令前128个字节到指令后127个字节范围内的相对地址。

rel16与rel32汇编后的指令所在的代码段内的相对地址。rel16 符号适用于操作数大小属性等于 16 位的指令;rel32 符号适用于操作数大小属性等于 32 位的指令。

77 cb

JA rel8

高于(CF=0 且 ZF=0)时短跳转

0F 8C cw/cd

JL rel16/32

小于 (SF<>OF) 时近跳转

代码语言:javascript
复制
00020000 7710            ja      00020012//20002+10 = 20012
00020002 0f8c10200057    jl      57022018//20008+57002010=57022018

ptr16:16 与 ptr16:32 

远指针,通常与指令不在同一个代码段中。16:16 记法表示指针值包含两个部分。冒号左侧的值是一个16位选择器,或是代码段寄存器的目标值。冒号右侧的值对应目标段中的偏移量。指令的操作数大小属性是16位时,使用 ptr16:16 符号;操作数大小属性是32位时,使用 ptr16:32 符号  

EA cd

JMP ptr16:16

绝对远跳转,地址由操作数给出

EA cp

JMP ptr16:32

绝对远跳转,地址由操作数给出

FF /5

JMP m16:16

绝对间接远跳转,地址由 m16:16 给出

FF /5

JMP m16:32

绝对间接远跳转,地址由 m16:32 给出

代码语言:javascript
复制
00020000 ff25d4924100    jmp     dword ptr ds:[4192D4h]

r(register)

r8 - 字节通用寄存器 AL、CL、DL、BL、AH、CH、DH 或 BH 之一。

r16 - 字通用寄存器 AX、CX、DX、BX、SP、BP、SI 或 DI 之一。

r32 - 双字通用寄存器 EAX、ECX、EDX、EBX、ESP、EBP、ESI 或 EDI 之一。

imm(立即数)

imm8 - 立即数字节。imm8 符号是 -128 到 +127(含)之间的一个有符号数字。对于结合使用 imm8 与字或双字操作数的指令,立即数会进行符号扩展,以形成一个字或双字。字的高位字节使用立即数的最高位填充。

imm16 - 操作数大小属性等于 16 位的指令使用的立即数字。这是 -32,768 到 +32,767(含)之间的一个数值。

imm32 - 操作数大小属性等于 32 位的指令使用的立即数双字。它允许使用 -2,147,483,648 到 +2,147,483,647(含)之间的数值。

r/m

r/m8 - 字节操作数,可以是字节通用寄存器(AL、BL、CL、DL、AH、BH、CH 及 DH)的内容,或是内存中的一个字节。

r/m16 - 操作数大小属性等于 16 位的指令使用的字通用寄存器或内存操作数。字通用寄存器有:AX、BX、CX、DX、SP、BP、SI 及 DI。内存的内容位于有效地址计算提供的地址。

r/m32 - 操作数大小属性等于 32 位的指令使用的双字通用寄存器或内存操作数。双字通用寄存器有:EAX、EBX、ECX、EDX、ESP、EBP、ESI 及 EDI。内存的内容位于有效地址计算提供的地址。

这里要特别注意:

89 /r

MOV r/m32,r32

将 r32 移到 r/m32

8B /r

MOV r32,r/m32

将 r/m32 移到 r32

 如:

代码语言:javascript
复制
MOV ecx,edx

这里就有两种解释:

如果是89,则r32为源操作数,r32 = edx, r/m32 = ecx

blob.png
blob.png

如果是8b,则r32为目标操作数,r32 = ecx, r/m32 = edx

blob.png
blob.png

所以可以构建出如下汇编:

代码语言:javascript
复制
00020000 89d1            mov     ecx,edx
00020002 8bca            mov     ecx,edx

m(内存操作数)

m - 内存中的 16 或 32 位操作数。

m8 - 内存中的字节操作数,通常表示为变量或数组名称,但由 DS:(E)SI 或 ES:(E)DI 寄存器指向它。此术语仅用于字符串指令与 XLAT 指令。

m16 - 内存中的字操作数,通常表示为变量或数组名称,但由 DS:(E)SI 或 ES:(E)DI 寄存器指向它。此术语仅用于字符串指令。

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

m64 - 内存中的内存四字操作数。此术语仅用于 CMPXCHG8B 指令。

m128 - 内存中的内存双四字操作数。此术语仅用于“数据流单指令多数据扩展指令集”。

m16:16、m16:32 - 包含两个数字组成的远指针的内存操作数。冒号左侧的数字对应指针的段选择器。右侧的数字对应它的偏移量。

m16&32、m16&16、m32&32 - 由成对的数据项组成的内存操作数,其大小分别在和号 (&) 的左右两侧指出。允许使用所有的内存寻址模式。m16&16 与 m32&32 操作数由 BOUND 指令使用,以便提供包含数组下标的上、下边界的操作数。m16&32 操作数由 LIDT 与 LGDT 指令使用,以便提供用于加载限制字段的字,以及用于加载对应的 GDTR 与 IDTR 寄存器基址字段的双字。

moffs8、moffs16、moffs32 - 字节、字或双字类型的简单内存变量(内存偏移量),供 MOV 指令的一些变体使用。实际地址按照相对于段基址的简单偏移量的形式给出。指令中不使用 ModR/M 字节。随 moffs 显示的数字表示其大小,这由指令的地址大小属性确定。

sreg(段寄存器)

段寄存器的位分配情况是:ES=0、CS=1、SS=2、DS=3、FS=4 及 GS=5

其余

m32real、m64real、m80real - 分别是内存中的单精度、双精度及扩展型实数浮点操作数。

m16int、m32int、m64int - 分别是内存中的字、短整型及长整型浮点操作数。

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

ST(i) - 从 FPU 寄存器堆栈的栈顶元素数算起的第 i 个元素。(i0 到 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。

xmm/m32 - XMM 寄存器,或是 32 位内存操作数。128 位 XMM 寄存器有:XMM0 到 XMM7。内存的内容位于有效地址计算提供的地址。

xmm/m64 - XMM 寄存器,或是 64 位内存操作数。128 位 SIMD 浮点寄存器有:XMM0 到 XMM7。内存的内容位于有效地址计算提供的地址。

xmm/m128 - XMM 寄存器,或是 128 位内存操作数。128 位 XMM 寄存器有:XMM0 到 XMM7。内存的内容位于有效地址计算提供的地址。

参考

CALL指令有多少种写法

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 操作码列
    • /digit
      • /r
        • cb、cw、cd、cp
          • ib、iw、id 
            • +rb、+rw、+rd 
              • +i
              • 指令列
                • rel:relative(rel8,rel16,rel32)
                  • ptr16:16 与 ptr16:32 
                    • r(register)
                      • imm(立即数)
                        • r/m
                          • m(内存操作数)
                            • sreg(段寄存器)
                              • 其余
                              • 参考
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档