常见的汇编指令

常见指令

NOP

  • NOP:No Operation,不执行任何动作的指令,只消耗 CPU 时钟;
  • NOP 的机器码是90
  • NOP 是空指令,就是没有操作,无操作;
  • 使用 NOP 来填充指令执行后多出来的字节,而不是用00来填充;

PUSH 压入堆栈

PUSH 的用法:

  • PUSH 0x1 / 0xF:把十六进制数字压入堆栈顶部;
  • PUSH EAX / EBX:把指定寄存器中的数据压入堆栈顶部;
  • PUSH [00401000]:把指定内存地址中的数据压入堆栈顶部;

POP 弹出堆栈

POP 的用法:

  • POP EAX / EBX:将堆栈顶部的内容弹出并放入指定的寄存器;

PUSHAD

  • 通用寄存器指:EAX / ECX / EDX / EBX / ESP / EBP / ESI / EDI;
  • PUSHAD 保护现场 / 备份现场,将通用寄存器的数据自上而下依次压入堆栈,最后一个寄存器的数据处于堆栈顶部;
  • PUSHAD 是 32 位操作;
  • PUSHAD 相当于是PUSH EAX, PUSH ECX, PUSH EDX, PUSH EBX, PUSH ESP, PUSH EBP, PUSH ESI, PUSH EDI八条命令的合集;

POPAD

  • 通用寄存器指:EAX / ECX / EDX / EBX / ESP / EBP / ESI / EDI;
  • POPAD 还原现场,将堆栈顶部的数据依次弹出并自下而上依次填充到通用寄存器;
  • POPAD 是 32 位操作;
  • POPAD 相当于是POP EDI, POP ESI, POP EBP, POP ESP, POP EBX, POP EDX, POP ECX, POP EAX八条命令的合集;

PUSHA

  • PUSHA 和 PUSHAD 功能相同,用于备份现场;
  • PUSHA 是 16 位操作;
  • PUSHA 相当于是PUSH AX, PUSH CX, PUSH DX, PUSH BX, PUSH SP, PUSH BP, PUSH SI, PUSH DI八条命令的合集;

POPA

  • POPA 和 POPAD 功能相同,用于还原现场;
  • POPA 是 16 位操作;
  • POPA 相当于是POP DI, POP SI, POP BP, POP SP, POP BX, POP DX, POP CX, POP AX八条命令的合集;

PUSHF

  • 将标志寄存器的值压栈;

POPF

  • 从栈中弹出数据,送入标志寄存器;

MOV

  • MOV 本意为move,移动,但功能相当于复制/赋值;
  • MOV EAX, ECX:两个操作数的长度必须相同;
  • MOV EAX, ECX:复制ECX(第二个参数)的数据到EAX(第一个参数),4 字节操作;
  • MOV AX, CX:复制CX(第二个参数)的数据到AX(第一个参数),2 字节操作;
  • MOV AL, CL:复制CL(第二个参数)的数据到AL(第一个参数),1 字节操作;
  • MOV [00402000], EAX:复制EAX(第二个参数)的数据到[指定内存地址](第一个参数),4 字节操作;
  • MOV [00402000], AX:复制AX(第二个参数)的数据到[指定内存地址](第一个参数),2 字节操作;
  • MOV [00402000], AH:复制AH(第二个参数)的数据到[指定内存地址](第一个参数),1 字节操作;

MOVSX

  • MOVSX EAX, CX:复制CX(第二个参数)的数据到EAX(第一个参数),如果EAX(第一个参数)的长度比CX(第二个参数)长,剩余的长度用CX(第二个参数)的符号位填充;
  • 正数用 0 填充,负数用 F 填充;

MOVZX

  • MOVZX EAX, CX:复制CX(第二个参数)的数据到EAX(第一个参数),如果EAX(第一个参数)的长度比CX(第二个参数)长,剩余的长度用 0 填充;
  • 带 0 扩展传送命令;

LEA

  • LEA:复制第二个参数的内存地址第二个参数运算后的内存地址第一个参数
  • LEA EAX, [00401000]:第一个参数必须是通用寄存器,第二个参数必须是内存地址;
  • LEA EAX, [ECX + 16]:第一个参数必须是通用寄存器,第二个参数必须是内存地址;
  • LEA 是取地址指令,仅操作地址,如:LEA EAX, DWORD PTR DS:[ECX+38]这里虽然有括号,但不会获取 ECX+38 指向内存的值,只计算 ECX+38 的值;

XCHG 互换 / 交换

  • XCHG EAX, ECX:第一个参数可以是通用寄存器或内存地址,第二个参数必须是通用寄存器;
  • XCHG [00401000], ECX:第一个参数可以是通用寄存器或内存地址,第二个参数必须是通用寄存器;

SHR 二进制右移

  1. 将一个寄存器或内存单元中的数据向右位移;
  2. 将最后移出的一位写入 CF 中;
  3. 最高位用 0 补充;
  4. 如果移动位数大于 1 时,必须将要移动的位数放在 CL 中;

SHL 二进制左移

  1. 将一个寄存器或内存单元中的数据向左移位;
  2. 将最后移出的一位写入 CF 中;
  3. 最低位用 0 补充;
  4. 如果移动位数大于 1 时,必须将要移动的位数放在 CL 中;

CLD

  • 用来操作方向标志位 DF,CLD 使 DF 复位,即让 DF = 00;

STD

  • 用来操作方向标志位 DF,STD 使 DF 置位,即让 DF = 01;

数学指令

ADD / SUB | 加法 / 减法

  • ADD / SUB的返回结果放在第一个参数中;

ADC 带进位的加法

  • ADC EAX, ECX = EAX + ECX + 0 | 1:两个参数累加并且加上进位标识符CF的值为最终结果;

SBB 带进位的减法

  • SBB EAX, ECX = EAX - ECX - 0 | 1:两个参数相减并且减去进位标识符CF的值为最终结果;

INC / DEC | 自增 / 自减

  • INC EAX:每执行一次该指令,参数的值自增 1;
  • DEC EAX:每执行一次该指令,参数的值自减 1;
  • 可以操作寄存器,也可以操作内存单元;

MUL 无符号乘法

  • 无符号乘法只有一个操作数,另一个操作数默认为 EAX(AX | AL);
  • MUL ECXMUL CL | CX | ECX默认和 AL | AX | EAX做乘法运算,运算结果分别存放到
    AH:AL | DX:AX | EDX:EAX中,其中AH | DX | EDX存放高位,AL | AX | EAX存放低位;

DIV 无符号除法

  • DIV CL:默认的被除数为EAX,如果 CL(除数)是 8 位,那么放在AL中,余数放在AH中;
  • DIV CX:默认的被除数为EAX,如果 CX(除数)是 16 位,那么放在AX中,余数放在DX中;
  • DIV ECX:默认的被除数为EAX,如果 ECX(除数)是 32 位,那么放在EAX中,余数放在EDX中;

IMUL 有符号乘法

  • 立即数:自然数;
  • 单操作数:IMUL CL | CX | ECX默认和 AL | AX | EAX做乘法运算,运算结果分别存放到
    AH:AL | DX:AX | EDX:EAX中,其中AH | DX | EDX存放高位,AL | AX | EAX存放低位;
  • 双操作数:IMUL AX, CX:第一个参数必须是通用寄存器,第二个参数可以是通用寄存器、内存地址或立即数,运算结果存放到第一个参数中;
  • 三操作数:IMUL AX, CX, 0x2:第一个参数必须是通用寄存器,第二个参数可以是通用寄存器或内存地址,第三个参数必须是立即数,将第二个参数和第三个参数的运算结果存放到第一个参数中;

IDIV 有符号除法

  • IDIV CL:默认的被除数为EAX,如果 CL(除数)是 8 位,那么放在AL中,余数放在AH中;
  • IDIV CX:默认的被除数为EAX,如果 CX(除数)是 16 位,那么放在AX中,余数放在DX中;
  • IDIV ECX:默认的被除数为EAX,如果 ECX(除数)是 32 位,那么放在EAX中,余数放在EDX中;
  • 双操作数和三操作数的做法与 IMUL 类似;

XADD 先交换后相加

  • XADD EAX, ECX:先交换两个参数的值,然后进行加法运算,运算结果保存在第一个参数中;
  • 这个指令其实是 XCHG 和 ADD 两个简单指令的组合;

NEG 取反

  • NEG EAX:操作数符号取反;

逻辑指令

AND / OR / XOR / NOT

  • 全部以二进制形式进行比较(操作);
  • AND EAX, ECX:双 1 为 1,否则为 0;
  • OR EAX, ECX:逢 1 为 1,否则为 0;
  • XOR EAX, ECX:不同为 1,相同为 0;
  • NOT EAX:二进制取反(按位取反);

比较指令

CMP

  • CMP EAX, ECX:两个参数相减进行比较,如果运算结果为 0,则 ZF(0 标志位)为 1;
  • 通用寄存器、内存地址和立即数之间可以相互比较;
  • 相当于 SUB 指令,但相减的结果不保存,只影响 0 标志位,当两个参数相等时,0 标志位置 1;

TEST

  • TEST EAX, EAX:判断 EAX 自身是否为 0;
  • 如果运算结果为 0,且 ZF(0 标志位)为 1,则说明 EAX 自身为 0;
  • AND相同,仅改变标志位而不改变操作数的值,若 2 个操作数中的一个为 0,则运算结果为 0,ZF 置 1;

跳转指令

JMP 无条件跳转

  • JMP 00401018:无条件跳转;

JE / JZ

  • ZF(0 标志位)为 1 则跳转;
  • 结果为 0 则跳转;

JNE / JNZ

  • ZF(0 标志位)为 0 则跳转;
  • 结果不为 0 则跳转;
  • JE / JZ相反;

JS

  • SF(符号位标志位)为 1 则跳转;
  • 结果为负则跳转;

JNS

  • SF(符号位标志位)为 0 则跳转;
  • 结果不为负则跳转;
  • JS相反;

JP / JPE

  • PF(奇偶标志位)为 1 则跳转;
  • 结果中 1 的个数为偶数则跳转;

JNP / JNPE / JPO

  • PF(奇偶标志位)为 0 则跳转;
  • 结果中 1 的个数为奇数则跳转;
  • JP / JPE相反;

JO

  • OF(溢出标志位)为 1 则跳转;
  • 结果溢出则跳转;

JNO

  • OF(溢出标志位)为 0 则跳转;
  • 结果没有溢出则跳转;
  • JO相反;

JB /JNAE

  • CF(进位 / 借位 标志位)为 1 则跳转;
  • 小于则跳转(无符号数);
  • JB 不关注符号位,只关注无符号位的运算;

JNB / JAE

  • CF(进位 / 借位 标志位)为 0 则跳转;
  • 大于等于则跳转(无符号数);
  • JB相反;

JBE / JNA

  • OF(溢出标志位)为 1ZF(0 标志位)为 1则跳转;
  • 小于等于则跳转(无符号数);

JNBE / JA

  • OF(溢出标志位)为 0并且ZF(0 标志位)为 0则跳转(都为 0);
  • 大于则跳转(无符号数);
  • JBE相反;

JL / JNGE

  • 小于则跳转(有符号数);
  • 与 JB 不同的是,JL 关注有符号位的运算;
  • JL 会忽略CF(进位 / 借位 标志位)的变化;

JNL / JGE

  • 大于等于则跳转(有符号数);
  • JL相反;

JLE / JNG

  • 小于等于则跳转(有符号数);

JNLE / JG

  • 大于则跳转(有符号数);

转移指令

转移指令就是可以控制 CPU 执行内存中某处代码的指令,也称为跳转指令;

LOOP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
assume cs:code
code segment
mov ax, 0
mov cx, 6

s: add ax, 2
loop s

mov ax, 4c00h
int 21h
code ends
end


  • LOOP 相当于

    1
    2
    3
    4
    5
    ; XOR CX, CX
    ; MOV CX, 6
    DEC CX
    TEST CX, CX ; CMP CX, 0
    JNZ [DEC CX 所在的地址]
  • 与以上指令不同的是:LOOP 执行时第一次循环计数器不会自减;

  • 约定俗成:使用 CX 作为计数器;

CALL / RETN

  • CALL 可以理解为 CALLBACK;

  • CALL 会执行一个子程序,可以理解为执行了一个函数;

  • CALL 指令会进行两步操作,首先将当前 IP 或 CS 和 IP 压栈,然后转移;

  • RET 在 CALL 所执行的子程序中时,会返回该 CALL 所在的主程序,并继续向下执行;

  • RET 与其他指令单独使用时,和 JMP 的功能相同;

  • RET 指令用栈中的数据,修改 IP 的内容,从而实现近转移;

    1
    POP IP
  • RETF 指令用栈中的数据,修改 CS 和 IP 的内容,从而实现远转移;

    1
    2
    POP IP
    POP CS

寻址方式

  • 直接寻址:指令后面直接写出此次运算使用的地址,称为直接寻址;
  • 间接寻址:只有执行到某一行指令才能知道此次运算使用的地址,称为间接寻址;