x86 指令
在本部分的列表中,标有星号 (*) 的说明尤其重要。 未如此标记的说明并不重要。
在 x86 处理器上,指令大小可变,因此向后反汇编是模式匹配的练习。 若要从地址向后拆解,应在比实际想要更远的点开始反汇编,然后向前看,直到指令开始生效。 前几个指令可能没有任何意义,因为你可能已在指令中间开始反汇编。 遗憾的是,反汇编可能永远不会与指令流同步,你必须尝试在不同的起点进行反汇编,直到找到有效的起点。
对于打包良好的 switch 语句,编译器将数据直接发出到代码流中,因此通过 switch 语句进行反汇编通常会偶然遇到 (没有意义的指令,因为它们实际上是数据) 。 找到数据的末尾,并继续在那里进行反汇编。
指令表示法
指令的一般表示法是将目标寄存器放在左侧,将源放在右侧。 但是,此规则可能有一些例外情况。
算术指令通常是由源寄存器和目标寄存器组合在一起的双寄存器。 结果将存储到目标中。
某些指令同时具有 16 位和 32 位版本,但此处仅列出了 32 位版本。 此处未列出仅在分段模型中使用的浮点指令、特权指令和指令 (Microsoft Win32 不使用) 。
为了节省空间,许多指令都以组合形式表示,如以下示例所示。
* |
MOV |
r1、 r/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 |
r1、 r/m |
使用符号扩展名移动。 |
|
* |
MOVZX |
r1、 r/m |
以零扩展名移动。 |
MOVSX 和 MOVZX 是 mov 指令的特殊版本,用于执行从源到目标的符号扩展或零扩展。 这是唯一允许源和目标为不同大小的指令。 (事实上,它们的大小必须不同。
堆栈操作
堆栈由 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 (dx:ax) 。 |
CWDE |
将 word (ax) 转换为 dword (eax) 。 |
CDQ |
将 dword (eax) 转换为 qword (edx:eax) 。 |
所有转换都执行符号扩展。
算术和位操作
所有算术和位操作指令修改标志。
算法
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 |
dx:ax = ax * r/m16 |
|
MUL |
r/m32 |
edx:eax = eax * r/m32 |
|
IMUL |
r/m8 |
斧头 = 铝 * r/m8 |
|
IMUL |
r/m16 |
dx:ax = ax * r/m16 |
|
IMUL |
r/m32 |
edx:eax = eax * r/m32 |
|
IMUL |
r1、 r2/m |
r1 *= r2/m |
|
IMUL |
r1、 r2/m、#n |
r1 = r2/m * #n |
无符号乘法和有符号乘法。 乘法后的标志状态未定义。
DIV |
r/m8 |
(ah, al) = (ax % r/m8, ax / r/m8) |
|
DIV |
r/m16 |
(dx, ax) = dx:ax / r/m16 |
|
DIV |
r/m32 |
(edx, eax) = edx:eax / r/m32 |
|
IDIV |
r/m8 |
(ah, al) = ax / r/m8 |
|
IDIV |
r/m16 |
(dx, ax) = dx:ax / r/m16 |
|
IDIV |
r/m32 |
(edx, eax) = edx:eax / 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 在除法后调整。 |
这些指令在执行未打包的二进制编码十进制运算后调整 al 和 ah 寄存器。
位
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 |
r1、 r2/m、 cl/#n |
左双移。 |
按 cl/#n 左移 r1,用 r2/m 的顶部位填充。 最后移出的位放置在携带中。
SHRD |
r1、 r2/m、 cl/#n |
右双移位。 |
按 cl/#n 向右移动 r1,用 r2/m 的底部位填充。 最后移出的位放置在携带中。
ROL |
r1、 cl/#n |
按 cl/#n 向左旋转 r1。 |
Ror |
r1、 cl/#n |
按 cl/#n 向右旋转 r1。 |
RCL |
r1、 cl/#n |
按 cl/#n 向左旋转 r1/C。 |
RCR |
r1、 cl/#n |
按 cl/#n 向右旋转 r1/C。 |
旋转类似于移位,只不过移出的位作为传入的填充位重新出现。 旋转指令的 C 语言版本将携带位合并到旋转中。
BT |
r1、 r2/#n |
将 r1 的位 r2/#n复制到携带中。 |
Bts |
r1、 r2/#n |
设置 r1 的位 r2/#n,将上一个值复制到携带中。 |
Btc |
r1、 r2/#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 |
将 T 从 esi 移动到 edi。 |
|
CMPST |
将 esi 中的 T 与 edi 进行比较。 |
|
SCAST |
从 edi 扫描 T 以获取 accT。 |
|
LODST |
将 T 从 esi 加载到 accT。 |
|
STOST |
将 T 存储到 acc T 中的 edi。 |
执行该操作后,根据方向标志 (向上或向下) 的方向标志的设置,按大小 (T) 递增或递减源寄存器和目标寄存器。
指令可以以 REP 作为前缀,以重复 ecx 寄存器指定的操作次数。
rep mov 指令用于复制内存块。
代表 stos 指令用于使用 accT 填充内存块。
标志
LAHF |
从标志加载 ah 。 |
SAHF |
存储 ah 到标志。 |
STC |
设置携带。 |
Clc |
清除携带。 |
CMC |
补码携带。 |
性病 |
将方向设置为 向下。 |
CLD |
将方向设置为 向上。 |
性病 |
启用中断。 |
CLI |
禁用中断。 |
互锁指令
XCHG |
r1、 r/m |
交换 r1 和 r/m。 |
XADD |
r1、 r/m |
将 r1 添加到 r/m,将原始值放在 r1 中。 |
CMPXCHG |
r1、 r/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 |
r、 r |
r = 0 |
|
TEST |
r、 r |
检查 r 是否 = 0。 |
|
* |
ADD |
r、 r |
左移 r 1。 |