8086 汇编笔记 - 常用指令 - 03
数据传送
- MOV dest, src
将 src 中的数据传送至 dest.
MOV AX, BX ; CPU 的通用寄存器之间的数据传送
MOV AX, 1234H ; 立即数 -> 寄存器
MOV DS:[1234H] 5678H ; 立即数 -> 存储单元
MOV AX, DS:[1234H] ; 存储单元 -> 寄存器
MOV DS:[1234H], BX ; 寄存器 -> 存储单元
MOV AX, ES ; 段寄存器 -> 通用寄存器
MOV DS, AX ; 通用寄存器 -> 段寄存器
MOV ES, DS:[BX] ; 存储单元 -> 段寄存器
MOV DS:[1234H], CS ; 段寄存器 -> 存储单元
MOV WORD PTR DS:[0], 12H ; 将 12H 当作 字型数据 放入 DS:[0]
使用 MOV 指令时需注意以下几点:
MOV 可传送 8 位或 16 位数据, 取决于寄存器.
如果 MOV 指令中无法判断出操作数的位数, 则需要显式声明数据位数. 比如上面代码的最后一行, 表明操作数是一个 16 位的字型数据, 相应的还有
BYTE PTR
字节型数据.一条 MOV 指令无法在存储单元与存储单元之间传送数据.
CS 和 IP 不能作为 dest.
不能在段寄存器之间传送数据. 比如
MOV DS, ES
会报错.立即数不能作为 dest.
不能向段寄存器传送立即数. 比如
MOV DS, 1234H
会报错. 可以通过通用寄存器作为中转.MOV AX, 1234H MOV DS, AX
- XCHG dest, src
用于交换数据, 相当于三条 MOV 指令. 指令中的两个操作数可以是两个寄存器或寄存器与存储单元.
使用 XCHG 需要注意以下几点:
两个操作数不能同时为存储单元
任何一个操作数都不能为段寄存器和立即数
- LEA r16, mem
取有效地址送入 16 位寄存器 r16.
LEA SP, [BP][DI] ; 将 [BP+DI] 寻址方式的偏移地址送入 SP
下面的代码的作用相同, 但后一条指令使用了 OFFSET 伪指令, 由编译器在编译时赋值.
LEA BX, VAR
MOV BX, OFFSET VAR
- LDS 和 LES
LDS dest, src
LES dest, src
装入地址指令
LDS 和 LES 都将 src 指向的内存中连续的 4B 内容的低 16 位送入 dest 指定的通用寄存器. LDS 将 上述 4B 的高16为装入 DS, LES 则装入 ES.
dest 必须是通用寄存器, src 比如是内存操作数.
- LAHF 和 SAHF
标志传送指令
LAHF 将标志寄存器的低 8 位放入 AH
SAHF 将 AH 的内容送入标志寄存器的低 8 位
- PUSH 和 POP
PUSH src
POP dest
操作堆栈
src 和 dest 可以为 寄存器或存储单元
PUSH 将 src 中的 16 位数据放入 SS:[SP] 的位置, 并将 SP - 2. POP 将 SS:[SP] 的 16 位数据放入 dest, 并将 SP + 2.
- CBW 和 CWD
字扩展指令 CBW
将 AL 中的字节数据扩展到字型数据, 高 8 位 放入 AH.
字扩展双字指令 CWD
将 AX 中的字型数据扩展到双字型, 高 16 位放入 DX.
MOV AL, 12H
CBW ; AL 符号位为 0, 所以 0 -> AH
MOV AL, AAH
CBW ; AL 符号位为 1, 所以 FF -> AH
MOV AX, 1234H
CWD ; 0 -> DX
MOV AX, AAAAH
CWD; FFFF -> DX
算术运算指令
- 加法
不带进位加法
ADD dest, src ; (src) + (dest) -> dest
- ADD 指令的 src 和 dest 不能同时为两个存储单元
- 段寄存器之间也不能相加
- 主要影响 CF, ZF, OF, SF 标志寄存器
带进位的加法
ADC dest, src ; (src) + (dest) + (CF) -> dest
加 1 指令
INC reg/mem ; (reg/mem) + 1 -> reg/mem
- 操作数可以是寄存器或存储单元, 但不能为立即数
- 影响 AF, OF, PF, SF, ZF, 但不影响 CF
- INC 指令会将操作数视为无符号数
- 减法
不带进位减法
SUB dest, src ; (dest) - (src) -> dest
带进位减法
SBB dest, src ; (dest) - (src) - (CF) -> dest
减 1 指令
DEC reg/mem ; (reg/mem) - 1 -> reg/mem
DEC 指令不影响 CF 标志.
- 取补
NEG reg/mem ; 0 - (reg/mem) -> reg/mem
将操作数以及符号为逐位取反, 然后加 1.
- 乘法
无符号乘法
MUL reg/mem
如果操作数为 8 位, (AL) * (reg/mem) -> AX 如果操作数为 16 位, (AX) * (reg/mem) - > DX, AX
有符号乘法
IMUL reg/mem
- 除法
无符号除法
DIV src
当 src 为 8 位时
(AX) / (src) 的商 -> AL (AX) / (src) 的余 -> AH
当 src 为 16 位时
(DX, AX) / (src) 的商 -> AX (DX, AX) / (src) 的余 -> DX
注意: 当除法的商过大(超过 AL, AX 的范围时, 会产生异常中断)
有符号除法
IDIV src
与 DIV 功能相同, 但除数, 被除数都被当作有符号数.
- 比较指令
CMP dest, src ; (dest) - (src)
将目标操作数与源操作数相减, 不送回结果, 只更改标志位.
判断两数相等
根据 ZF 标志位, 若 ZF = 1 则 相等
判断无符号数大小
根据 CF 标志位, 若 CF = 1, 则 (dest) < (src)
判断有符号数大小
可根据 SF 和 OF 标志判断, 若 SF ⊕ OF = 1, 即 SF 与 OF 不同时, dest < src
逻辑指令
AND dest, src ; 按位且 (dest) & (src) -> dest
OR dest, src ; 按位或 (dest) | (src) -> dest
XOR dest, src ; 按位异或 (dest) ^ (src) -> dest
NOT reg/mem ; 按位取反 ~(reg/mem) -> reg/mem
TEST dest, src ; 按位与, 只设置标志位
移位指令
SAL reg/mem, 1/CL ; 算数左移
SAR reg/mem, 1/CL ; 算数右移
SHL reg/mem, 1/CL ; 逻辑左移
SHR reg/mem, 1/CL ; 逻辑右移
; 算数左/右移与逻辑左/右移的区别在于前者会保持符号位不变.
ROL reg/mem, 1/CL ; 循环左移
ROR reg/mem, 1/CL ; 循环右移
RCL reg/mem, 1/CL ; 带进位循环左移
RCR reg/mem, 1/CL ; 带进位循环左移
; 带进位与不带进位的区别是
; RCL/RCR 将 CF 标志位也加入了移位操作中
; 变成了 9 位 的循环移位操作
处理器指令
CLC ; 0 -> CF
STC ; 1 -> CF
CLD ; 0 -> DF
STD ; 1 -> DF
CLI ; 0 -> IF
STI ; 1 -> IF
CMC ; !CF -> CF, CF 取反
转移指令
无条件转移指令
JMP dest JMP reg/m16
JMP 分为三种转移方式
短转移
在段内短距离(-128 ~ 127)转移
段内转移
CS 不变, 给出转移的 16 位偏移地址
段间转移
CS 和 IP 都改变的转移 给出一个双字的数据, 低 16 位 -> IP, 高 16 位 -> CS
条件转移指令
无符号数
JZ JE ; ZF = 1 JNE JNZ ; ZF = 0 JA JNBE ; CF = 0 && ZF = 0 ; Jump if Not Below or Equal JAE JNB ; CF = 0 ; Jump if Not Below JB JNAE ; CF = 1 && ZF = 0 ; Jump if Not Above or Equal JBE JNA ; CF = 1 ; Jump if Not Above
有符号数
JG JNLE ; SF = OF && ZF = 0 ; Jump if Not Less or Equal JGE JNL ; SF = OF ; Jump if Not Less JL JNGE ; SF != OF ; Jump if Not Greater or Equal JLE JNG ; SF != OF || ZF = 1 ; Jump if Not Greater
特殊算数标志位
JC ; CF = 1 JNC ; CF = 0 JO ; OF = 1 JNO ; OF = 0 JP JPE ; PF = 1 JNP JPO ; PF = 0 JS ; SF = 1 JNS ; SF = 0 JCXZ ; CX = 0
循环指令
- LOOP label
执行 LOOP 指令时, 处理器会先将 CX - 1, 然后判断 若 CX != 0, 则继续循环, 否则退出循环.
- LOOPE/LOOPZ label
与 LOOP 相同, 首先 CX - 1 (ZF 不受影响), 然后判断:
若 CX = 0
跳出循环
否则
若 ZF = 0
跳出循环
否则
继续循环
- LOOPNE/LOOPNZ label
与 LOOPE/LOOPZ 不同的地方在 当 ZF = 1 时跳出循环
串处理操作指令
MOVSB
MOVSW
; 将 DS:SI 所指向的 字节/字 数据传送到 ES:DI 指向的位置
; 然后根据 DF 的值确定 递增(DF = 0)或递减(DF = 1) SI 与 DI 的值
CMPSB
CMPSW
; 将 SI 指向的 字节/字 数据与 DI 指向的数据相减比较
; 然后设置相关的标志位(AF, CF, OF, PF, SF, ZF)
; 然后根据 DF 改变 SI 与 DI
SCASB
SCASW
; 将 AL/AX 的内容与 DI 指向的数据相减比较, 设置标志位
; 然后根据 DF 改变 DI
LODSB
LODSW
; 将 SI 指向的 字节/字 数据放入 AL/AX
STOSB
STOSW
; 将 AL/AX 的内容放入 DI 指向的内存单元
; 然后根据 DF 改变 DI
重复前缀
REP
无条件执行跟在后面的指令, 由 CX 指定重复次数
REPE/REPZ
当后面的指令的结果使 ZF = 1 时, 重复执行, 并且由 CX 指定最大重复次数 该指令与 CMPS 和 SCAS 配合使用
REPNE/REPNZ
与 REPE/REPZ 条件相反, ZF = 0 时, 重复执行
子程序调用返回指令
定义子程序
<name> PROC [NEAR/FAR] ; code <name> ENDP
子程序的调用
CALL <name>
根据调用出近调用还是远调用, 将 IP / (CS, IP) 压入栈中, 然后转去执行子程序.
返回指令
RET [n]
将堆栈顶部的 IP / (IP, CS) 弹出, 然后返回到调用的地方. 如果指定了参数 n, 则在弹出堆栈之后, (SP) + n -> SP.
8086 软中断
INT n
执行这条指令时 CPU 会将标志寄存器压入堆栈, 禁止新的可屏蔽中断和单步中断(IF = 0, TF = 0), 并将当前的 CS 和 IP 压入堆栈. 然后会在 0000H 段中的偏移地址为 4n 的地方找到中断处理程序的 CS 和 IP. 4n 处为 IP, 4n + 2 处为 CS, 然后转去执行.
IRET
中断返回指令, 会自动将 IP, CS, FLAGS 出栈.