x86 指令

在本部分的列表中,标有星号 (*) 的说明尤其重要。 未如此标记的说明并不重要。

在 x86 处理器上,指令大小可变,因此向后反汇编是模式匹配的练习。 若要从地址向后拆解,应在比实际想要更远的点开始反汇编,然后向前看,直到指令开始生效。 前几个指令可能没有任何意义,因为你可能已在指令中间开始反汇编。 遗憾的是,反汇编可能永远不会与指令流同步,你必须尝试在不同的起点进行反汇编,直到找到有效的起点。

对于打包良好的 switch 语句,编译器将数据直接发出到代码流中,因此通过 switch 语句进行反汇编通常会偶然遇到 (没有意义的指令,因为它们实际上是数据) 。 找到数据的末尾,并继续在那里进行反汇编。

指令表示法

指令的一般表示法是将目标寄存器放在左侧,将源放在右侧。 但是,此规则可能有一些例外情况。

算术指令通常是由源寄存器和目标寄存器组合在一起的双寄存器。 结果将存储到目标中。

某些指令同时具有 16 位和 32 位版本,但此处仅列出了 32 位版本。 此处未列出仅在分段模型中使用的浮点指令、特权指令和指令 (Microsoft Win32 不使用) 。

为了节省空间,许多指令都以组合形式表示,如以下示例所示。

*

MOV

r1r/m/#n

r1 = r/m/#n

表示第一个参数必须是寄存器,但第二个参数可以是寄存器、内存引用或即时值。

为了节省更多空间,还可以按如下所示表示指令。

*

MOV

r1/m、 r/m/#n

r1/m = r/m/#n

这意味着第一个参数可以是寄存器或内存引用,第二个参数可以是寄存器、内存引用或即时值。

除非另有说明,否则使用此缩写时,不能同时为源和目标选择内存。

此外,可以将位大小后缀 (8、16、32) 追加到源或目标,以指示参数必须具有该大小。 例如,r8 表示 8 位寄存器。

内存、数据传输和数据转换

内存和数据传输指令不会影响标志。

有效地址

*

Lea

r、m

加载有效地址。

(r = m) 的地址

例如,LEA eax,[esi+4] 表示 eax esi = + 4。 此指令通常用于执行算术。

数据传输

MOV

r1/m、 r2/m/#n

r1/m = r/m/#n

MOVSX

r1r/m

使用符号扩展名移动。

*

MOVZX

r1r/m

以零扩展名移动。

MOVSXMOVZXmov 指令的特殊版本,用于执行从源到目标的符号扩展或零扩展。 这是唯一允许源和目标为不同大小的指令。 (事实上,它们的大小必须不同。

堆栈操作

堆栈由 esp 寄存器指向。 esp 处的值是最近推送 (堆栈的顶部,首先弹出) ;较旧的堆栈元素驻留在更高的地址。

推送

r/m/#n

将值推送到堆栈上。

POP

r/m

堆栈中的 Pop 值。

PUSHFD

将标志推送到堆栈上。

POPFD

堆栈中的弹出标志。

PUSHAD

推送所有整数寄存器。

POPAD

弹出所有整数寄存器。

Enter

#n、#n

生成堆栈帧。

*

离开

拆下堆栈帧

C/C++ 编译器不使用 Enter 指令。 (Enter 指令用于使用 Algol 或 Pascal.) 等语言实现嵌套过程

休假指令等效于:

mov esp, ebp
pop ebp

数据转换

CBW

将字节 (al) 转换为 word (ax) 。

CWD

将 word (ax) 转换为 dword (dxax) 。

CWDE

将 word (ax) 转换为 dword (eax) 。

CDQ

将 dword (eax) 转换为 qword (edxeax) 。

所有转换都执行符号扩展。

算术和位操作

所有算术和位操作指令修改标志。

算法

ADD

r1/m、 r2/m/#n

r1/m += r2/m/#n

ADC

r1/m、 r2/m/#n

r1/m += r2/m/#n + 携带

子项

r1/m、 r2/m/#n

r1/m -= r2/m/#n

SBB

r1/m、 r2/m/#n

r1/m -= r2/m/#n + 携带

负面

r1/m

r1/m = -r1/m

公司

r/m

r/m += 1

DEC

r/m

r/m -= 1

Cmp

r1/m、 r2/m/#n

计算 r1/m - r2/m/#n

cmp 指令计算减法并根据结果设置标志,但会丢弃结果。 它通常后跟一个测试减法结果的条件 跳转 指令。

MUL

r/m8

斧头 = * r/m8

MUL

r/m16

dxax = ax * r/m16

MUL

r/m32

edxeax = eax * r/m32

IMUL

r/m8

斧头 = * r/m8

IMUL

r/m16

dxax = ax * r/m16

IMUL

r/m32

edxeax = eax * r/m32

IMUL

r1r2/m

r1 *= r2/m

IMUL

r1r2/m、#n

r1 = r2/m * #n

无符号乘法和有符号乘法。 乘法后的标志状态未定义。

DIV

r/m8

(ahal) = (ax % r/m8, ax / r/m8)

DIV

r/m16

(dxax) = dxax / r/m16

DIV

r/m32

(edxeax) = edxeax / r/m32

IDIV

r/m8

(ahal) = ax / r/m8

IDIV

r/m16

(dxax) = dxax / r/m16

IDIV

r/m32

(edxeax) = edxeax / r/m32

无符号和有符号除法。 伪代码解释中的第一个寄存器接收余数,第二个寄存器接收商。 如果结果溢出目标,则会生成除法溢出异常。

未定义除法后的标志状态。

*

SETcc

r/m8

r/m8 设置为 0 或 1

如果条件 cc 为 true,则 8 位值设置为 1。 否则,8 位值设置为零。

二进制编码的十进制

除非正在调试以 COBOL 编写的代码,否则不会看到这些说明。

DAA

加法后的小数调整。

DAS

减法后的小数调整。

这些指令在执行打包的二进制编码十进制运算后调整 al 寄存器。

Aaa

ASCII 在添加后进行调整。

AAS

ASCII 在减法后调整。

这些指令在执行解包的二进制编码十进制运算后调整 al 寄存器。

Aam

ASCII 在乘法后调整。

AAD

ASCII 在除法后调整。

这些指令在执行未打包的二进制编码十进制运算后调整 alah 寄存器。

AND

r1/m、 r2/m/#n

r1/m = r1/m 和 r2/m/#n

OR

r1/m、 r2/m/#n

r1/m = r1/m 或 r2/m/#n

XOR

r1/m、 r2/m/#n

r1/m = r1/m xor r2/m/#n

NOT

r1/m

r1/m = 按位非 r1/m

*

TEST

r1/m、 r2/m/#n

计算 r1/m 和 r2/m/#n

测试指令计算逻辑 AND 运算符并根据结果设置标志,但会丢弃结果。 它通常后跟一个条件跳转指令,用于测试逻辑 AND 的结果。

SHL

r1/m、 cl/#n

r1/m <<= cl/#n

Shr

r1/m、 cl/#n

r1/m >>= cl/#n 零填充

*

SAR

r1/m、 cl/#n

r1/m >>= cl/#n sign-fill

最后移出的位放置在携带中。

SHLD

r1r2/m、 cl/#n

左双移。

cl/#n 左移 r1,用 r2/m 的顶部位填充。 最后移出的位放置在携带中。

SHRD

r1r2/m、 cl/#n

右双移位。

cl/#n 向右移动 r1,用 r2/m 的底部位填充。 最后移出的位放置在携带中。

ROL

r1cl/#n

cl/#n 向左旋转 r1

Ror

r1cl/#n

cl/#n 向右旋转 r1

RCL

r1cl/#n

cl/#n 向左旋转 r1/C。

RCR

r1cl/#n

cl/#n 向右旋转 r1/C。

旋转类似于移位,只不过移出的位作为传入的填充位重新出现。 旋转指令的 C 语言版本将携带位合并到旋转中。

BT

r1r2/#n

r1 的位 r2/#n复制到携带中。

Bts

r1r2/#n

设置 r1 的位 r2/#n,将上一个值复制到携带中。

Btc

r1r2/#n

清除 r1位 r2/#n,将以前的值复制到携带中。

控制流

Jcc

dest

分支条件。

JMP

dest

直接跳跃。

JMP

r/m

跳转间接。

CALL

dest

直接呼叫。

*

CALL

r/m

间接调用。

调用指令将返回地址推送到堆栈上,然后跳转到目标。

*

Ret

#n

返回

ret 指令弹出并跳转到堆栈上的返回地址。 RET 指令中的非零#n指示在弹出返回地址后,应将#n值添加到堆栈指针。

如果结果为非零,则递减 ecx 和跳转。

LOOPZ

如果结果为非零且设置了 zr,则递减 ecx 和 jump。

LOOPNZ

如果结果不为零且 zr 清晰,则递减 ecx 和跳转。

JECXZ

如果 ecx 为零,则跳转。

这些指令是 x86 的 CISC 遗产的残余,在最近的处理器中,实际上比写出的等效指令慢。

字符串操作

MOVST

Tesi 移动到 edi。

CMPST

esi 中的 Tedi 进行比较。

SCAST

edi 扫描 T 以获取 accT。

LODST

Tesi 加载到 accT。

STOST

T 存储到 acc T 中的 edi

执行该操作后,根据方向标志 (向上或向下) 的方向标志的设置,按大小 (T) 递增或递减源寄存器和目标寄存器。

指令可以以 REP 作为前缀,以重复 ecx 寄存器指定的操作次数。

rep mov 指令用于复制内存块。

代表 stos 指令用于使用 accT 填充内存块。

标志

LAHF

从标志加载 ah

SAHF

存储 ah 到标志。

STC

设置携带。

Clc

清除携带。

CMC

补码携带。

性病

将方向设置为 向下。

CLD

将方向设置为 向上。

性病

启用中断。

CLI

禁用中断。

互锁指令

XCHG

r1r/m

交换 r1r/m。

XADD

r1r/m

r1 添加到 r/m,将原始值放在 r1 中。

CMPXCHG

r1r/m

比较和交换条件。

cmpxchg 指令是以下内容的原子版本:

   cmp     accT, r/m
   jz      match
   mov     accT, r/m
   jmp     done
match:
   mov     r/m, r1
done:

杂项

INT

#n

捕获到内核。

绑定

r、m

如果 r 不在范围内,则为陷阱。

*

NOP

无操作。

XLATB

al = [ebx + al]

BSWAP

r

寄存器中的交换字节顺序。

下面是 int 指令的特殊情况。

INT

3

调试器断点陷阱。

INT 3 的操作码0xCC。 NOP 的操作码0x90。

调试代码时,可能需要修补一些代码。 为此,可以将有问题的字节替换为0x90。

习语

XOR

rr

r = 0

TEST

rr

检查 r 是否 = 0。

*

ADD

rr

左移 r 1。