8086AssemblyNote
8086汇编复习手册
声明 : 本文全部内容的背景均为80x86平台;文中标点中英混用,懒得改;文中代码基本都经实际验证过。文章内容均个人(PGZXB pgzxb@qq.com)整理,未经校正,欢迎勘误!
8086平台基础
大小端
通用寄存器
(1) 数据寄存器
AX: 累加器, 所有的I/O指令都使用累加器与外设接口传送信息;
BX: 基址寄存器, 可用来存放偏移地址;
CX: 计数寄存器, LOOP指令用作计数器;
DX: 数据寄存器;
注: DX和AX常用来作为整体存放32位数, 高位存在DX, 低位存在AX (MUL指令、CWD指令)。
(2) 地址指针寄存器
SP: 堆栈指针寄存器,指向堆栈的栈顶;
BP: 基址指针寄存器,通常是与SS寄存器配对使用。
(3)变址寄存器
SI: 源变址寄存器。
DI: 目的变址寄存器。
它们常常在处理数组(或串)中作为索引(Index)。
注意: 通用寄存器是通用的,都可用来存放普通数据(地址寄存器存个数字也是可以的),但以上三类每一类寄存器都专有用途。
段寄存器
CS: 代码段寄存器, 指令存放在代码段内
SS: 堆栈段寄存器, PUSH, POP
DS: 数据段寄存器, 变量(常量)一般都定义于数据段
ES: 附加段寄存器, 不常用,串指令用到过
存放段基地址, 即段起始地址的高16位。
- 注意事项
- CS不能被指令修改
- 只有BX, BP, SI, DI能当作地址寄存器被使用, SP能作地址寄存器但是不能被使用(修改)
- 不是地址寄存器的寄存器也可以用来存地址(所谓地址只是16位无符号数而已), 只不过不能用来寄存器寻址、寄存器相对寻址、基址变址寻址、相对基址变址寻址(总之就是用寄存器存地址的寻址方式都不能*)
- BP默认段寄存器是SS, 其他默认均为DS
标志位
- 注意: 标志位设置和指令有关, 当指令要求与一般规律冲突时, 以指令要求为准.
标志名 | 英文简称 | 标志表示 1/0 | 标志位设置的一般规律 |
---|---|---|---|
溢出 | OF | OV/NV | 正 + 正 得 负 时为1 负 + 负 得 正 时为1 正 - 负 得 负 时为1 负 - 正 得 正 时为1 |
方向 | DF | DN/UP | |
中断 | IF | EI/DI | |
符号 | SF | NG/PL | 符号位为1时为1 符号位为0时为0 |
零 | ZF | ZR/NZ | 运算结果为0时为1 |
辅助进位 | AF | AC/NA | |
奇偶 | PF | PE/PO | 结果最后一个字节 二进制形式1的个数 为偶数时为1, 反之为0 |
进位 | CF | CY/NC | 最高位有进位(借位)时为1, 反之为0 |
标志位的设置总结(每条指令的标志位的设置查看指令速查表):
数据传送类指令除了标志寄存器传送指令均不影响标志位
INC, DEC不影响CF, 其他标志位的设置按一般规律
加减法指令除了INC, DEC设置标志位均按一般规律
乘法指令如果乘积结果的高一半为0则设置CF=OF=0, 否则设置CF=OF=1. 乘法指令对于CF和OF以外的标志位设置无定义(无定义不意味不影响)
除法指令对所有标志位的设置均无定义
NOT指令不影响标志位
除NOT以外的逻辑指令(& | ^ TEST)设置CF=OF=0, AF无定义, 其他标志位的设置按一般规律
移位指令对标志位的设置比较复杂
移位指令都会影响CF, 影响方式如下图
移位指令只有当CNT为1时才会设置OF, 高位变换OF=1, 否则OF=0
循环移位指令不影响除CF,OF以外的标志位
循环移位以外的移位指令对AF无定义, 其他标志位的设置按一般规律
串传送 MOVSB / MOVSW不影响条件码。
存串 STOSB / STOSW 不影响条件码。
取串LODSB / LODSW不影响条件码。
串比较 CMPSB / CMPSW 对条件码的影响 : 看书
串扫描 SCASB / SCASW对条件码的影响 : 看书
寻址方式及其使用和注意事项
- 寻址方式一共七种
- 立即寻址方式
- 寄存器寻址方式
- 直接寻址方式
- 寄存器间接寻址方式
- 寄存器相对寻址方式(常用于处理数组)
- 基址变址寻址方式
- 相对基址变址寻址方式
寻址方式 | 示例 | 注意事项 |
---|---|---|
立即寻址方式 | MOV AX, 10H | |
寄存器寻址方式 | MOV AX, BX | |
直接寻址方式 | MOV AX, [2000H] | |
寄存器间接寻址方式 | MOV AX, DS:[BX] | only BX BP SI DI |
寄存器相对寻址方式 | MOV AX, ARR[SI] MOV AX, [ARR + SI] |
only BX BP SI DI |
基址变址寻址方式 | MOV AX, [BX][DI] | 基址寄存器 : BX BP 变址寄存器 : SI DI |
相对基址变址寻址方式 | MOV AX, ARR[BX][DI] MOV AX, ARR[BX + DI] MOV AX, [ARR + BX + DI] |
基址寄存器 : BX BP 变址寄存器 : SI DI |
8086对段寄存器使用的约定
序号 | 内存访问类型 | 默认段寄存器 | 可重设的段寄存器 | 段内偏移地址来源 |
---|---|---|---|---|
1 | 取指令 | CS | 无 | IP |
2 | 堆栈操作 | SS | 无 | SP |
3 | 串操作之源串 | DS | ES、SS | SI |
4 | 串操作之目标串 | ES | 无 | DI |
5 | BP用作基址寻址 | SS | ES、DS | 按寻址方式计算得有效地址 |
6 | 一般数据存取 | ES | ES、SS | 按寻址方式计算得有效地址 |
注: 除BP外其他可用来当地址寄存器的默认段均为DS
8086汇编模板(熟练记忆, 考试要写)
1 | DATA SEGMENT ; 数据段, 对应DS段, 习惯将变(常)量定义于此 |
8086汇编指令速查表
指令 | 作用 | 对标志位的影响 | 备注 |
---|---|---|---|
MOV | MOV 1, 2 将2的内容移到1 |
不影响 | 不能直接MOV立即数到段寄存器 不能修改CS寄存器 两个操作数不能都是内存(CMP, XCHG也是) |
PUSH | 入栈 | 不影响 | 只能操作字; 不能搞立即数 |
POP | 出栈 | 不影响 | 只能操作字; 不能搞立即数 |
XCHG | 交换两个的值 | 不影响 | 两个操作数不能都是内存 两个操作数必须有一个在寄存器中 操作数不能使用段寄存器 两个操作数类型要匹配 |
IN | 输入 | 不影响 | 仅限于AX,AL传送信息 用法: IN AL, PORT(字节) IN AX, PORT(字节) IN AL, DX(字节) IN AX, DX(字) |
OUT | 输出 | 不影响 | 仅限于AX,AL传送信息 用法: OUT PORT, AL(字节) OUT PORT, AX(字节) OUT DX, A(字节)L OUT DX, AX(字) |
XLAT | 换码指令 | 不影响 | |
LEA | 有效地址送寄存器 | 不影响 | 目的操作数可使用16/32位寄存器, 不能使用段寄存器 操作数长度, 地址长度, 操作 16, 16, 正常送 16, 32, 截取低位 32, 16, 零扩展到32位 32, 32, 正常送 |
CBW | 字节扩展到字 | 不影响 | AL -(有符号扩展)-> AX |
CWD | 字扩展到双字 | 不影响 | AX -(有符号扩展)-> DX, AX |
ADD | 加法 | 一般规律 | 两个操作数不能同时是内存 |
ADC | 带进位加法 | 一般规律 | 两个操作数不能同时是内存 |
INC | 加1 | 不影响CF, 其他一般 | |
SUB | 减法 | 一般规律 | 两个操作数不能同时是内存 |
SBB | 带借位减法 | 一般规律 | 两个操作数不能同时是内存 |
DEC | 减1 | 一般规律 | |
NEG | 求补 | 不影响CF, 其他一般 | |
CMP | 比较 | 一般规律 | 两个操作数不能同时是内存 没有结果, 仅设置标志位 |
MUL/IMUL | 乘法 | 乘积结果的高一半为0设置CF=OF=0, 否则设置CF=OF=1. 对于CF和OF以外的标志位无定义 | 操作数不能是立即数、段寄存器 内存操作数指定是WORD PTR 还是BYTE PTR |
DIV/IDIV | 除法 | 无定义 | 操作数不能是立即数、段寄存器 内存操作数指定是WORD PTR 还是BYTE PTR |
AND | 位与 | 设置CF=OF=0, AF无定义, 其他标志位的设置按一般规律 | |
OR | 位或 | 设置CF=OF=0, AF无定义, 其他标志位的设置按一般规律 | |
NOT | 按位求反 | 不影响 | |
XOR | 位异或 | 设置CF=OF=0, AF无定义, 其他标志位的设置按一般规律 | |
TEST | 测试 | 设置CF=OF=0, AF无定义, 其他标志位的设置按一般规律 | |
SHL/SHR | 逻辑左右移 | 见标志位设置总结 | 所移位数要么为1, 要么为CL |
SAL/SAR | 算术左右移 | 见标志位设置总结 | 所移位数要么为1, 要么为CL |
ROL/ROR | 循环左右移 | 见标志位设置总结 | 所移位数要么为1, 要么为CL |
RCL/RCR | 循环带借位左右移 | 见标志位设置总结 | 所移位数要么为1, 要么为CL |
MOVS | 串转移 | 不影响 | |
CMPS | 串比较 | 见标志位设置总结 | |
LODS | 从串取 | 不影响 | |
STOS | 存入串 | 不影响 | |
SCANS | 扫描串 | 见标志位设置总结 | |
INS | 串输入 | 不影响 | |
OUTS | 串输出 | 不影响 | |
JMP | 无条件跳转 | 不影响 | |
LOOP | 循环 | 不影响 | |
LOOPZ/LOOPE | 循环 | 不影响 | |
LOOPNZ/LOOPNE | 循环 | 不影响 | |
J__ | 有条件跳转 | 不影响 | 三类: 标志位类JZ, JS, JO, JC 无符号JA, JB 有符号JG, JL (‘不’ : J后加N, ‘等于’ : 后面跟E) |
CALL | 子程序调用 | 不影响 | |
RET | 子程序返回 | 不影响 |
常用DOS调用
1号调用
1
2
3MOV AH, 01H
INT 21H
; 控制台等待输入, 输入字符的ASCII存在AL中2号调用
1
2
3MOV AH, 02H
MOV DL, 'A'
INT 21H ; 控制台输出一个字符, 其ASCII码在DL中9号调用
1
2
3
4; 输出字符串, 以'$'为结尾标志, 字符串起始地址需要存在DX中
MOV AH, 09H
LEA DX, STRING ; STRING DB "Hello, World!$"
INT 21H10号调用
1
2
3
4
5
6
7
8; 输入字符串, 要求较复杂
; 定义缓冲区 : BUF DB 80, ?, 80 DUP('$')
; 规则 : 调用前将BUF的地址传入DX, 调用后实际字符串存到了BUF的第三个字节开始之后
; 最多输入字符的个数有BUF的第一个字节指定, 这也意味着10号调用一次最多可读入255个
; 输入字符串实际大小将被存放在BUF的第2个字节
MOV AH, 0AH
LEA DX, BUF
INT 21H4CH号调用
1
2
3; 返回DOS, 程序结束应调用它, 否则会出运行时错误
MOV AH, 4CH
INT 21H ;调用4CH号功能,结束程序
汇编子程序设计
函数定义
1
2
3
4函数名 PROC
; 函数体
RET ; 函数一定有RET语句作为返回调用处的出口
函数名 ENDP函数调用
1
CALL 函数名
函数参数和返回值
1
2
3
4
5
6
7
8
9; 汇编函数没有所谓参数和返回值
;; 作为参数功能的寄存器, 即利用全局变量传递数据
; 例子:
; MOD函数, 参数为AX, BL, 返回值为DL, 为AX % BL的值, 均为无符号数
MOD PROC
DIV BL
MOV DL, AH
MOD ENDP避免寄存器被破坏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25; 函数内可能会用到除了用作参数和返回值的寄存器,
; 为了避免调用方的数据被破坏, 应该在函数体内对适当的寄存器进行保护, 利用PUSH,POP
;;;;;;;;;;;;;;;;;;;;;;;;;
; function : print \r\n ; ; 打印回车换行
;;;;;;;;;;;;;;;;;;;;;;;;;
FUNC_PRINT_CRLF PROC
PUSH DX ; 保护DX, AX
PUSH AX
; print \r\n
MOV DL, 0DH ; '\r'
MOV AH, 02H
INT 21H
MOV DL, 0AH ; '\n'
MOV AH, 02H
INT 21H
POP AX ; 恢复DX, AX
POP DX
; return
RET
FUNC_PRINT_CRLF ENDP
汇编常用技法
灵活使用栈(PUSH & POP)
- 暂存(保护)寄存器的值
- 逆序数组
- 加个特殊标记当栈底(我常用-1(0FFFFH)做栈底)
字符串加个特殊标记当结尾(常用’$’)
汇编编程实战
输入输出
0.输入以回车结束的字符串
1 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
1.输出有结束标志的字符串
结尾为 \'$\'1 | ; 结尾 : '$' |
2.输出N进制数
1 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
3.输入N进制数
1 | ; 10进制为例 |
数组
0.找出数组中的最大值
新增代码示例1 | DATA SEGMENT ; 数据段, 对应DS段, 习惯将变(常)量定义于此 |
1.插入元素到升序序列
新增代码示例1 | DATA SEGMENT ; 数据段, 对应DS段, 习惯将变(常)量定义于此 |
2.排序数组(冒泡排序)
1 | DATA SEGMENT |
字符串
0.比较字符串是否相等
1 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
1.翻转字符串(同样适用于数组)
1 | DATA SEGMENT |
综合示例
0.输入字符串找出出现数量最多的字符输出该字符及其数量
新增代码示例(见期末模拟(一), 参考程序随答案公布)
- 输入格式 : 输入一行字符串, 以回车结尾, 输入字符个数不会多于200个
- 输出格式 : 输出两行, 每一行均以回车换行结尾, 第一行为出现最多的字符, 第二行为出现的次数
- (最多出现的字符由多个时, 随意输出一个即可)
- 思路 : 内外两层循环,当外循环找到一个ASCII不为0的字符时进入内循环否则一直向后找,内循环统计和外循环找到的不为0的字符相同的字符个数,最后加上外循环找到的那一个,和CNT变量比大小,若大于CNT则更新CNT为新的计数,更新CHAR为新的最多出字符,为了避免重复,统计完即刻将当前位置字符赋为ASCII 0,将外循环那个字符也赋成ASCII 0
1 | DATA SEGMENT ; 数据段, 对应DS段, 习惯将变(常)量定义于此 |
1.输入若干数字求平均值, 最大值, 最小值并输出
- 思路
- 边输入边求和并计数以及记录最大最小值
- 输出
2.输入字符串反向输出
- 格式 : 输入一行字符串, 要求倒序输出, 末尾要求打印回车换行
- 思路
- 边输入边压栈
- 边弹栈边输出
1 | DATA SEGMENT |
3.输入N求斐波那契数列第N项并输出
- 思路 : 三个变量(寄存器)来回倒腾