汇编语言
第二章
通用寄存器AX、BX、CX、DX,为了兼容可分为两个八位XH和XL
SI、DI、SP、BP、
PSW
| 汇编指令 | 描述 | |
|---|---|---|
mov ax/ah, 18 |
AX = 18 |
mov指令不能用于修改CS和IP的值 |
add ax, 8 |
AX = AX + 8 |
|
jmp 段地址:偏移地址 |
同时修改CS和IP的值 |
|
jmp 某一合法寄存器ax |
IP = ax |
仅修改IP的值 |
mov ax,[offset] |
AX = 地址为DS+offset内存单元中的数据 |
段寄存器
一个段的的最大长度为64KB:段地址1000H左移一位+偏移地址1111H 偏移最大为16位
四个段寄存器SS、 DS、ES、CS
指令指针寄存器IP
即CPU任意时刻将 CS:IP 指向的内容当做指令来执行
8086CPU给出物理地址的方法: 段地址 * 16 + 偏移地址
执行过程:
从CS:IP指向的内存读取指令,取到指令缓冲器
IP=IP+所读取指令的长度,从而指向下一条指令
执行指令跳转到(1)
8086CPU在加电启动或复位后
CS–>FFFFH
IP–>0000H
实验
1 | -r : 查询寄存器状态 |
各类存储芯片
随机存储器
装有BIOS是ROM(只读)
接口卡上的RAM
| 00000~9FFFF | 主储存器地址空间(RAM) |
|---|---|
| A000~BFFFF | 显存地址空间 |
| C0000~FFFFF | 各类ROM地址空间 |
| [^8086PC机内存地址空间分配1] |
- 从地址0~9FFFF读取数据实际就是在读取主随机存储器上的数据
- 向地址A0000~BFFFF的内存单元写入数据就是向显存写入数据
1 | -e b810:0000 01 01 02 02 03 03 04 04 |
- 向C0000~FFFFF的内存单元写入数据是无效的(ROM是只读的)
第三章 寄存器及内存访问
字单元: 存放一个字型数据的内存单元(16位)
N地址字单元: 起始地址为N的字单元
DS寄存器: 存放要访问数据的段地址
1 | mov bx,1000H |
栈、栈段
8086CPU提供出站入站的指令且出站入栈操作都是以字为单位,SS:SP选择一段以栈的方式访问的内存空间
段寄存器SS和寄存器SP,任意时刻SS:SP指向栈顶元素 SP递减栈由高地址向低地址
1 | 设置栈 |
Debug的T命令在修改寄存器SS的指令时,下一条指令会紧接着执行
1 | mov ax,1000H |
实验任务
单步调试(t指令)导致新建栈区内存发生改变
你在设置好 SS(栈段)和 SP(栈顶指针)后,就算还没有写任何 push 指令,这块内存也被修改了。
根本原因在于: Debug 的 t 指令(单步跟踪)是依赖 CPU 的单步中断(Interrupt 1) 来实现的。CPU 在处理任何中断时,都必须强制把当前的“运行现场”保存到当前有效的栈中,以便中断处理完后能找回原来的执行位置。这个“强制保存”的动作,悄无声息地向你的栈里压入了 6 个字节的数据。
执行目标指令: CPU 首先老老实实地执行完你代码里的一条指令(例如
mov bx, 1111)。触发单步中断: 指令执行完毕的瞬间,CPU 硬件检测到标志寄存器(FLAGS)中的 TF(陷阱标志)位为 1,立即触发单步中断。
强制压栈保存现场(内存被修改的发生地): 为了能去执行 Debug 程序的代码并在屏幕上显示寄存器状态,CPU 自动将 3 个关键寄存器压入你刚刚设置好的栈 (
SS:SP) 中。顺序严格如下:SP = SP - 2,压入 FLAGS (保存各种状态标志位)SP = SP - 2,压入 CS (保存中断发生时的代码段地址)SP = SP - 2,压入 IP (保存中断发生时,下一条将要执行的指令的偏移地址)
(此时,你的栈空间里就多出了这 6 个字节的数据,内存发生了你所观察到的改变。)
移交控制权: CPU 修改 CS 和 IP 的值,跳转到 Debug 的核心代码去执行,把刚才的寄存器状态打印到你的屏幕上。
恢复现场并返回: Debug 程序执行到最后一条指令
IRET(中断返回)。CPU 再次接管,按照刚才压栈的逆序,从你的栈中把数据弹出来:弹出数据到 IP,
SP = SP + 2弹出数据到 CS,
SP = SP + 2弹出数据到 FLAGS,
SP = SP + 2(此时,SP 指针完美复位,CPU 回到你的代码中继续待命,等待你输入下一个命令。)
第四章
汇编 -> 编译 -> 链接
源程序
1 | assume cs:codesg /* 表示codesg段 与cs寄存器(代码段) 有关系 即为一个代码段 */ |
| 伪指令 | 用法 |
|---|---|
| end | 汇编结束标记符 |
| codesg segment … codesg ends | 定义一个段 |
| assume | 某寄存器和某段有关 |
程序执行的过程跟踪
编程 -> 1.asm -> 编译 -> 1.obj -> 连接 -> 1.exe -> 加载(command)
加载过程:
- 先找到一段初始SA 地址作为起始地址有足够空间
- 在内存区的钱256个字节中创建一个程序前缀(PSP)的数据区
- PSP区:SA:0
程序区:SA:SA + 10H:0
一般将两个区分成不同段 - DA = SA 初始化后 CS:IP = SA + 10H:0
(程序执行完后可通过DS来查看代码所在地址)
第五章 [ BX ] 和 loop 指令
- BX -> EA 存放的数据作为一个偏移地址
- cx(count计数寄存器 用于loop循环的次数)先减一在判 0
- 在汇编源程序中,数据不能以字母开头 A000H -> 0A000H
Debug 和 汇编编译器 masm 对 [num] 的处理不同:
- debug会认为是ds:num处的数据,而masm会认为是常数num
解决方式:
- 在[ num ]前面显式给出段地址所在的段寄存器 例如 ds : [num]
**此处ds称作段前缀 **
DOS和其他合法程序一般不会使用0:2000:2ff(00200h002ffh)的256个字节空间所以这段空间是安全的(在0:0026处写入数据会造成死机)
实验:向内存0:200 ~ 0:23F 依次传入数据 0 ~ 63(3FH)
1 | assume cs:code |
第六章包含多个段的程序
- dw(define word)用于定义字形数据,若在段开头定义则数据地址在该段的起始地址(cs:[0])
1 | assume cs:code |
可执行文件由描述信息和程序组成,程序来源于源程序中的汇编指令和定义的数据,描述信息主要是编译和连接程序对源程序中相关伪指令进行的处理
1 | ;6.3 |
定义多个不同段可以直接用段的名称找到该段的地址
例如定义了 stack段,可以使用 mov ax, stack 可以将栈段的地址传入ax
实验五:
1 | assume cs:code, ds:data, ss:stack |
- CPU执行程序,返回前,data段中的数据没有变化
- cs = 076C 、ss = 076B、 ds = 076A
- X - 2, X - 1
- 实际占有空间 [ N / 16] * 16 个字节
- 写在后面需要提前计算代码段的长度计算偏移(32)
| 指令 | 作用 | 结果 |
|---|---|---|
end start |
告诉编译器:程序结束了,并且程序的启动入口在 start 标号处。 |
编译出的 EXE 文件头里会记录入口地址。 |
end |
仅仅告诉编译器:源文件到此结束,不再编译后面的文字。 | 不指定入口地址,系统默认从程序的第一行代码开始执行。 |
内存寻址方法和指令长度
我们将寻址方式按“从简到繁”排列,看看字节数是如何增加的:
- 寄存器寻址(最快、最短)
- 示例:
mov ax, bx - 长度: 2 字节
- 原理: 1 字节操作码 + 1 字节 ModR/M(告诉 CPU 是哪两个寄存器)。不需要任何额外的内存地址信息。
- 寄存器间接寻址(无位移)
- 示例:
mov ax, [bx] - 长度: 2 字节
- 原理: 地址已经存在寄存器里了,CPU 只需要 ModR/M 字节就能确定“去
bx指向的地方提货”。
- 直接寻址(带 16 位偏移量)
- 示例:
mov ax, [1234h]或push ds:[0] - 长度: 3 ~ 4 字节
- 原理: 除了操作码,指令里必须硬编码进
1234h这两个字节的地址。mov ax, [addr]是特化指令,3 字节。push ds:[0]是通用格式,4 字节(操作码 + ModR/M + 2字节偏移量0000h)。
- 基址变址寻址(最复杂)
- 示例:
mov ax, [bx + si + 1234h] - 长度: 4 字节
- 原理: 这是最复杂的组合。
- ModR/M 字节记录了
bx和si的组合关系。 - 指令结尾必须带上
1234h这 2 字节 的位移量。
- ModR/M 字节记录了
例5:编写程序将a段和b段中的数据相加存储到c段中
1 | assume cs:code |
例6:用push指令将a段中的前八个字形数据,逆序存储到b段中
1 | assmue cs:code |
内存地址方法
与或指令 and和or
大小写转换问题使用and方式就可以完美解决:
大写字母的ASCII码的二进制第五位(从零位开始)置零就一定是大写
基址变址寻址 [bx + idata]、[bx + si + idata]
SI和DI类似bx但不能拆成高低八位
双重循环用栈保存cx
实验六
1 | assume cs:code, ss:stack, ds:data |
第八章
[bp] 默认的段前缀是ss
用 X ptr 指明内存单元的长度,(dw)word(字)16、byte(字节)8、(dd)double word(两个字)32
1 | mov word ptr ds:[0], 1 |
div指令/mul指令
div word ptr 除数
- (DX + AX)/ (reg(N)) = AX … DX
- (AX) / reg(N) = AL… AH
mul reg
- 8*8: AL * reg = AX
- 16*16:AX * reg = DX(高位) AX(低位)
dup 、dw 、dd
1 | db 3 dup (0) -> 0, 0, 0 |
实验七
1 | assume cs:codesg, ds:data, es:table |
第九章 转移指令
操作符 offset 取得相应标号的偏移地址(在段中的偏移地址)
1 | start : mov ax, offset start ;相当于mov ax, 0 |
问题9.1
将s处的一条指令复制到s处
1 | assume cs:code |
jmp指令
jmp short 标号 ;段内短转移
jmp word ptr 内存单元地址 ;段内转移
jmp far ptr 标号 ;段间转移,远转移
jmp dword ptr 内存单元地址 ; 段间转移
指令数据中存储的不是标号的具体地址而是当前指令到标号地址的偏移量
jcxz指令
jcxz是有条件转移指令(所有有条件转移都是短转移)
jcxz 标号(如果 cx = 0, 转移)相当于 if ((cx) == 0) jmp short 标号
实验八
1 | assume cs:code |
由于jmp short s1指令保存的是s2到s1的位移 所以复制指令之后位移不变,会从s跳转到mov ax, 4c00h程序正常结束
实验九
1 | assume cs:code ds:data |

第十章CALL 和 RET
ret 和 retf 和 call
两者均使用栈中的数据ret : pop IPretf : pop CS & pop IP call 标号: push IP & jmp near ptr 标号call far ptr : push CS & push IP & jmp far ptr 标号
实验十
编写三个子程序
- 子程序1:显示字符串
1 | ;显示字符串 |
- 子程序2:解决除法溢出问题
1 | ;解决除法溢出问题 |
- 子程序3:word型数据转十进制显示
1 | ;word型数据转十进制显示 |
课程设计1
首先☝️根据实验7造表

子函数有,show_str数据显示、divdw除法防止溢出、dtoc读取数据转化成十进制保存
思路:
- 基本思路:先读取数据制表table -> 再从规范的表中读取数据 -> 读取到的数据丢给显示函数
- 显示函数:
dtoc调用divdw循环取余数存入data2 ->show_str读取 data 数据显示 - 数据显示:
dl定位列 、dh定位行、cl保存数据样式 - 数据读取:
bx定位数据段table行,bx +idata定位四组数据的列
1 | assume cs:code, ds:data, es:table |
第十一章 标志寄存器
标志寄存器 flag
第6位ZF: add、sub、mul、div、and等大部分运算指令影响ZF,pop、push、mov传送指令不影响
第2位PF:记录相关指令执行之后结果所有bit中1的个数是否为偶数
第7位SF:符号位检查结果是否为负
第0位CF : 进位借位标识符
第11位OF:CF是无符号数运算有意义的标志位,OF是有符号数运算的有意义标志位
第10位DF:方向标志位控制
adc和sbb指令
adc带进位加法指令
1 | ;ax高16位,bx低16位,执行这个数加 0020 F000H |
sbb带借位减法同理
1 | ;ax高16位,bx低16位,执行这个数减 0020 F000H |
cmp指令
cmp:cmp ax, bx (ax - bx)相当于减法但是不保存结果只改变寄存器
结合标志寄存器的内容对结果进行分析:
sf = 1 , of = 0没溢出且结果为负 ax < bxsf = 1, of = 1溢出且结果为负(正向溢出正数减负数)ax < bxsf = 0, of = 0没溢出且结果为正ax > bxsf = 0, of = 1溢出且结果为正(负溢出负数减正数) ax > bx
检测比较结果的条件转移指令
| 指令 | 含义 | 检测标志位 |
|---|---|---|
je |
等于转移 | zf = 1 |
jne |
不等于转移 | zf = 0 |
jb |
低于转移 | cf = 1 |
jnb |
不低于转移 | cf = 0 |
ja |
高于转移 | cf = 0 且 zf = 0 |
jna |
不高于转移 | cf = 1 且 zf = 1 |
jbe |
低于等于转移 | |
jae |
高于等于转移 |
检测点11.3
1 | ;统计F000:0处32个字节大小在(32,128)的数据个数 |
DF标志和串传送指令
串传送指令movsb相当于:mov es:[di], byte ptr ds:[si] ,df = 0 si、di递增,否则递减,同理字传送指令movsw传送一个字节si、di递增或者递减 2
配合rep使用:
1 | rep movsb |
cld将df设为0, std将df设为1
pushf和popf
将标志寄存器压入栈
debug中标志寄存器的表示

实验11
编写一个子程序将包含任意字符且以零结尾的字符串中的小写字母转换变成大写字母,ds:si指向字符串首地址
1 | assume cs:code |
第十二章 内中断
中断处理过程
- 取得中断类型码 N
- pushf
- TF = 0, IF = 0
- push CS
- push IP
- (IP) = (N * 4), (CS ) = ( N * 4 + 2)
在8086PC机中中断向量表制定存放在内存地址为 0 处,从0000:0000到0000:03FF,一个表项占两个字高地址存放段地址,低地址存放偏移地址,一般情况下,从 0200 : 02FF的256个字节的空间对应的中断向量表项是空的,操作系统和其他程序都不占用
iret
等价于
1 | pop IP |
安装
修改除法溢出0号中断,使用movsb指令写入int_0程序
- 安装int_0
- int_0
1 | assume cs:code |
TF标志
执行完一条指令之后如果TF = 1,则cpu会转去执行其他中断处理程序,故在进入中断处理程序之前会将TF设为0
响应中断的特殊情况
例在设置ss栈顶指令之后,cpu不会响应中断
实验12 编写0号中断处理程序
编写0号处理程序,除法溢出的时候,在屏幕正中间显示字符串”divide error!”,然后返回 dos
1 | assume cs:code |
第十三章 int 指令
int指令
int n :n表示中断类型码,表示直接调用n型中断
BIOS和DOS提供的中断例程
BIOS中主要包含:
- 硬件系统的检测和初始化程序
- 外部中断和内部中断的中断例程
- 用于对硬件设备进行 I/O 操作的中断例程
- 其他和硬件系统相关的中断例程
操作系统DOS也提供了中断例程,从操作系统的角度看,DOS的中断例程就是操作系统向程序员提供的编程资源。
BIOS和DOS中断例程的安装过程
- 开机后,CPU一加电,初始化(CS) = 0FFFH,(IP)= 0,自动从该单元执行程序。FFFF:0处有一条跳转指令,CPU执行该指令之后,转去执行BIOS中的硬件系统检测和初始化程序。
- 初始化程序建立BIOS所支持的中断向量表,将BIOS提供的中断例程的入口地址登记在中断向量表中。由于BIOS所提供的中断例程固化到ROM程序,所以只需要将入口地址登记在中断向量表中即可。
- 硬件系统监测和初始化完成之后,调用 int 19h 进行操作系统的引导,将计算机交由操作系统控制
- DOS启动之后,除完成其他工作之外,还将它所提供的中断例程装入内存,建立相关中断向量表
BIOS中断例程的应用
int 10 中断例程是 BIOS 提供的中断例程,BIOS和DOS提供的中断例程,都用ah来传递内部子程序的编号
1 | mov ah, 2 ;置光标 |
ah = 2 表示调用第10h号中断例程的2号子程序,功能为设置光标位置
1 | mov ah, 9 ;在光标位置显示字符 |
ah = 9 表示在光标位置显示字符
实验 13 编写、应用中断例程
1. 编写安装 int 7ch 中断例程,功能显示一个用0结束的字符串,中断例程安装在0:200处
1 | assume cs:code |
2. 编写并安装 int 7ch中断例程,功能为完成loop指令的功能
1 | assume cs:code |
3. 分别在程序第2,4,6,8行显示4句英文诗,补全程序
1 | assume cs:code |
第十四章 端口
端口的读写
- 访问内存
1 | mov ax, ds:[8] |
- CPU通过地址线将地址信息8发出
- CPU通过控制线发出内存读命令,选中存储器芯片
- 存储器将8号单元中的数据通过数据线送入CPU
- 访问端口
1 | in al, 60h |
- CPU通过地址线将地址信息60h发出
- CPU通过控制线发出端口读命令,选中端口所在芯片
- 端口芯片将60h端口中的数据通过数据线送入CPU
注意:in 和 out指令中,只能使用ax或al来存放从端口中读入的数据或要发送到端口中的数据
1 | ;0-255内的端口进行读写 |
CMOS RAM 芯片
- 128字节的RAM中,内部时钟占用 0 ~ 0dh 单元来保存时间信息,其余大部分用于保存系统配置信息,供BIOS程序读取
- 该芯片内部有两个端口 70h 和 71h
- 70h为地址端口,存放要访问的 RAM中单元的地址;71h为数据端口,存放从选定的单元中读取的数据,或者写入
shl 和 shr 指令
逻辑位移指令
将最后移除的一位写在CF中
1 | mov al, 01001000b |
CMOS RAM 中存储的时间信息
年、月、日、时、分、秒,长度均为一个字节,存放单元为:
9 、8、 7、 4、 2、 0均以BCD码形式存放
编程实现在屏幕中间显示当前月份
1 | assume cs:code |
实验14 访问CMOS RAM
编程,以“年/月/日 时:分:秒”的格式,显示当前的日期、时间
1 | assume cs:code,ds:data |
第十五章 外中断
外中断信息
外中断源一共有以下两类:
可屏蔽中断
IF=1时响应中断,这也解释了触发中断过程中将IF和TF设置成0的原因,同时可以通过1
2sti ;设置IF = 1
cti ;设置IF = 0不可屏蔽中断
CPU在执行完当前指令之后,立即响应,发生中断,此中断的的类型码固定为2,故不需要取终端类型码
PC机键盘的处理过程
键盘输入
按下一个按键产生一个扫描码,送入端口号为60h的寄存器,松开按键时产生一个断码也放入60h寄存器,断码=通码 + 80h
输入到达端口之后,相关芯片向CPU发出中断类型码为 9 的可屏蔽中断信息,如果IF=1则响应中断
int9中断例程,首先读取60h端口的扫描码,如果是字符键的扫描码,就把他对应的字符码送入内存BIOS键盘缓冲区;如果是控制键和切换键的扫描码,则将其转化为状态字节写入内存中存储状态字节的单元
编写 int9 中断例程
编程在屏幕中间依次显示字符a~z
1 | assume cs:code |
按下ESC之后改变颜色
1 | ;模拟int指令的调用功能,调用入口地址在ds:0、ds:2中的中断例程的程序为 |
完整程序
1 | assume cs:code, ds:data |
实验15 安装int9中断例程
安装一个新的int9中断例程,功能:在DOS下,按下“A”键之后,除非不在松开手,否则显示满屏A,其他按键照常处理(断码 = 通码 + 80h)
1 | assume cs:code, ds:data, ss:stack |
第十六章 直接定址表
描述了单元长度的标号
程序中使用的 code, s, start等标号,仅仅代表了内存单元的地址,但是还可以使用一种标号,不仅代表内存单元地址还表示了内存单元的长度(数据标号)
1 | code segment |
注意:在其他段中使用某个段中定义的数据标号,需要在assume中将这段定义
直接定址表
编写子程序,以十六进制的形式在屏幕中间显示给定的字节型数据(建立映射关系)
1 | showbyte: |
实验16 编写包含多个子程序的中断例程
安装一个新的int 7ch 中断例程,未显示输出提供如下功能的子程序
- 清屏
- 设置前景色
- 设置背景色
- 向上滚动一行
入口参数说明如下:
- ah寄存器传递功能码,0表示清屏,1表示设置前景色,2表示设置背景色,3表示向上滚动一行
- 对于1、2功能,用al传递颜色属性
1 | assume cs:code |
第十七章 使用BIOS进行键盘输入和磁盘读写
int9 中断例程对键盘输入的处理
按下按键,引发键盘中断,CPU执行int9中断,读取60h端口A键的通码,然后检测状态字节,检查是否有Shift、ctrl等切换键按下,如果没有,就把a键的扫描码和对应的ASCII码写入键盘缓冲区,高字节存储扫描码,低字节存储ASCII码
int 16h中断历程读取键盘缓冲区内容
1 | mov ah, 0 |
- 检测键盘缓冲区是否有数据
- 如果没有重复1
- 读取缓冲区第一个字单元中的键盘输入
- 将扫描码放入ah,ASCII放入al
- 将已读取的数据从缓冲区删除
应用int 13h 中断例程对磁盘进行读写
入口参数
- ah = int 13h的功能号 (2表示读扇区 3表示写扇区)
- al = 读取的扇区数
- ch = 磁道号
- cl = 扇区号
- dh = 磁头号
- dl = 驱动器号 软驱从0开始,0:软驱A,1:软驱:B;硬盘从80h开始,80h:硬盘C,81h:硬盘D
- es:[bx] 指向接受从扇区读入数据的内存区
例如:将 0:200中的内容写入0面0道1扇区
1 | mov ax,0 |
实验17 编写包含多个功能子程序的中断例程
1面 = 80道 , 1道 = 18扇区,用统一编号的方式对磁盘进行管理从0~2879
逻辑扇区号 = (面号 * 80 + 磁道号)* 18 + 扇区号 -1
安装一个新的int7ch中断例程,实现通过逻辑扇区号对软盘进行读写
1 | assume cs:code |
课程设计2
1 | 第 1 步:写一个最简单的 boot 程序,只显示菜单 |
1
1 | assume cs:code |
2编写安装程序
1 | assume cs:code |