汇编语言-王爽-Notes
各种常见寄存器的名称:
AX:累加器寄存器(Accumulator Register)的低16位,缩写自“Accumulator”。
BX:基址寄存器(Base Register)的低16位,缩写自“Base”。
CX:计数寄存器(Count Register),用于循环计数,缩写自“Count”。
DX:数据寄存器(Data Register),也可以用于一些算术运算,缩写自“Data”。
SI: Source Index,(源索引寄存器),用于存储源操作数地址。
DI: Destination Index,(目标索引寄存器),用于存储目标操作数地址。
SP: Stack Pointer,(栈指针),指向当前栈顶位置。
BP: Base Pointer,(基址指针),通常用于指向函数堆栈帧中的参数和局部变量。
IP: Instruction Pointer,(指令指针),存储下一条要执行的指令地址。
CS: Code Segment,(代码段寄存器),存储代码段的起始地址。
SS: Stack Segment,(栈段寄存器),存储堆栈段的起始地址。
DS: Data Segment,(数据段寄存器),存储数据段的起始地址。
ES: Extra Segment,(附加段寄存器),通常用于存储附加数据段的起始地址。
PSW: Program Status Word,(程序状态字),存储 CPU 执行指令时的状态信息,例如标志位、中断使能等。也被称为标志寄存器。
第1章 基础知识
1.1 机器语言
机器语言是机器指令的集合, 电子计算机的机器指令是一列二进制数字, 计算机将之转变为一列高低电平.
每一种微处理器, 由于硬件设计和内部结构不同, 就需要用不同的电平脉冲来控制, 使它工作, 因此每一种微处理器都有自己的机器指令集, 也就是机器语言.
1.2 汇编语言的产生
汇编指令是机器指令便于记忆的书写格式, 也就是助记符.
1.3 汇编语言的组成
3 类指令:
- 汇编指令: 机器码的助记符, 有对应的机器码
- 伪指令: 没有对应的机器码, 由编译器执行, 计算机并不执行
- 其他符号: 由编译器识别, 没有对应的机器码
1.5 指令和数据
指令和数据是应用上的概念.
在内存或磁盘上, 指令和数据没有任何区别, 都是二进制信息.
CPU 在工作时有时候把信息看作指令, 有时候看作数据, 为同样的信息赋予了不同的意义.
如:
1 |
|
1.7 CPU 对存储器的读写
CPU 要想进行数据的读写, 必须和外部器件进行下面 3 类信息的交互:
- 存储单元的地址 (地址信息)
- 器件的选择, 读或写的命令 (控制信息)
- 读或写的数据 (数据信息)
也就有 3 类总线:
- 地址总线
- 控制总线
- 数据总线
1.8 地址总线
一个 CPU 有 N 根地址线, 则可以说这个 CPU 的地址总线的宽度为 N, 这样的 CPU 最多可以寻找 $2^N$ 个内存单元.
1.9 数据总线
数据总线的宽度决定了 CPU 和外界的数据传送速度.
1.10 控制总线
这里的控制总线是个总成, 控制总线是一些不同控制线的集合, 有多少根控制总线, 就意味着 CPU 提供了对外部器件的多少种控制.
1.14 各类存储芯片
从读写属性上看分为两类:
- RAM (Random Access Memory, 随机存储器), 可读可写. 必须带电存储, 关机后存储的内容丢失
- ROM (Read Only Memory, 只读存储器), 只能读取不能写入, 关机后其中的内容不会丢失
1.15 内存地址空间
CPU 将系统中各类存储器看作一个逻辑存储器:
这个逻辑存储器就是 内存地址空间
每个物理存储器在这个逻辑存储器中占有一个地址段, 即一段地址空间, CPU 在这段地址空间中读写数据, 实际上就是在相应的物理存储器中读写数据.
第2章 寄存器
在 CPU 中:
- 运算器进行信息处理
- 寄存器进行信息存储
- 控制器控制各种器件进行工作
- 内部总线连接各种器件, 在他们之间进行数据的传输
程序员可以通过改变各种寄存器中的内容来实现对 CPU 的控制.
不同的 CPU, 寄存器的个数, 结构是不同的.
8086CPU 中的 14 个寄存器为:
AX:累加器寄存器(Accumulator Register)的低16位,缩写自“Accumulator”。
BX:基址寄存器(Base Register)的低16位,缩写自“Base”。
CX:计数寄存器(Count Register),用于循环计数,缩写自“Count”。
DX:数据寄存器(Data Register),也可以用于一些算术运算,缩写自“Data”。
SI: Source Index,(源索引寄存器),用于存储源操作数地址。
DI: Destination Index,(目标索引寄存器),用于存储目标操作数地址。
SP: Stack Pointer,(栈指针),指向当前栈顶位置。
BP: Base Pointer,(基址指针),通常用于指向函数堆栈帧中的参数和局部变量。
IP: Instruction Pointer,(指令指针),存储下一条要执行的指令地址。
CS: Code Segment,(代码段寄存器),存储代码段的起始地址。
SS: Stack Segment,(栈段寄存器),存储堆栈段的起始地址。
DS: Data Segment,(数据段寄存器),存储数据段的起始地址。
ES: Extra Segment,(附加段寄存器),通常用于存储附加数据段的起始地址。
PSW: Program Status Word,(程序状态字),存储 CPU 执行指令时的状态信息,例如标志位、中断使能等。也被称为标志寄存器。
2.1 通用寄存器
AX, BX, CX, DX 这 4 各寄存器通常用来存放一般性的数据, 被称为 通用寄存器.
这 4 各 16 位寄存器都可分为两个可独立使用的 8 位寄存器来用:
- AX 可分为 AH 和 AL
- BX 可分为 BH 和 BL
- CX 可分为 CH 和 CL
- DX 可分为 DH 和 DL
(“L” 指 Low, “H” 指 High)
2.2 字在寄存器中的存储
- 字节: 记为 byte, 一个字节由 8 个 bit 组成
- 字: 记为 word, 一个字由两个字节组成
2.3 几条汇编指令
在写一条汇编指令或一个寄存器的名称时 不区分大小写 , 如:
1 |
|
等价.
2.5 16 位结构的 CPU
其结构特性为:
- 运算器一次最多可以处理 16 位的数据
- 寄存器的最大宽度为 16 位
- 寄存器和运算器之间的通路为 16 位
2.6 8086CPU 给出物理地址的方法
用两个 16 位地址合成的方法来形成一个 20 位的物理地址.
两个地址位:
- 段地址
- 偏移地址
物理地址的计算方法位:
1 |
|
x16
也就是左移 4 位 ( $2^4$ ).
示意图为:
2.7 “段地址 x 16 + 偏移地址 = 物理地址” 的本质含义
可以理解为:
1 |
|
即, CPU 在访问内存时, 用一个基础地址 (段地址 x 16) 和一个相对于基础地址的偏移地址相加, 给出内存单元的物理地址.
2.8 段的概念
注意两点:
- 段地址 x 16 必然是 16 的倍数, 所以一个段的其实地址也一定是 16 的倍数
- 偏移地址为 16 位, 16 位地址的寻址能力为 64KB, 所以一个段的长度最大位 64KB
CPU 可以用不同的段地址和偏移地址形成同一个物理地址.
2.9 段寄存器
8086 CPU 有 4 个段寄存器:
CS: Code Segment,(代码段寄存器),存储代码段的起始地址。
SS: Stack Segment,(栈段寄存器),存储堆栈段的起始地址。
DS: Data Segment,(数据段寄存器),存储数据段的起始地址。
ES: Extra Segment,(附加段寄存器),通常用于存储附加数据段的起始地址。
2.10 CS 和 Ip
CS 和 IP 指示了 CPU 当前要读取指令的地址:
- CS 为代码段寄存器
- IP 为指令指针寄存器
在 8086 机中, 任意时刻, CPU 将 CS:IP 指向的内容当作指令执行.
读取一条指令后, IP 中的值自动增加. 增加的值为当次所读取的指令的长度.
1 |
|
2.11 修改 CS, IP 的指令
在 CPU 中, 程序员能够用指令读写的部件只有寄存器, 程序员可以通过改变寄存器中的内容实现对 CPU 的控制.
可以用 mov
(传送指令), 或 jmp
(跳转指令) 来修改 CS, IP 的内容.
如用 jmp 段地址:偏移地址
, 可以同时修改 CS, IP 的内容:
1 |
|
若想用 jmp
仅修改 IP 的内容, 可用 jmp 某一合法寄存器
:
1 |
|
其用寄存器中的值修改 IP.
2.12 代码段
要让 CPU 执行放在代码段中的指令, 必须要将 CS:IP
指向所定义的代码段中的第一条指令的首地址.
实验 1 查看 CPU 和 内存, 用机器指令和汇编指令编程
Alt + Enter
可将 DOSBOX 换为全屏.
常见的 DEBUG 命令:
R
, (Register): 查看, 改变 CPU 寄存器的内容D
, (Display): 显示内存中的内容E
, (Enter): 进入内存中修改内容U
, (Unassemble): 将内存中的机器指令翻译成汇编指令T
, (Trace): 跟踪执行程序, 执行一条机器指令A
, (Assemble): 将汇编指令转换为二进制指令并写入内存
第3章 寄存器(内存访问)
3.1 内存中字的存储
字单元 的概念, 存放一个字型数据 (16 位) 的内存单元, 由来两个地址连续的内存单元组成, 高地址内存单元中存放字型数据的高位字节, 低地址内存单元中存放字型数据的低位字节.
将起始地址为 N 的字单元简称为 N 地址字单元, 如一个字节单元由 2, 3 两个内存单元组成, 则这个字单元的起始地址为2, 也就可以称为 2 地址字单元.
DS 和 [address]
8086 CPU 中的 DS (Data Segment) 寄存器, 通常用来存放要访问的数据的段地址.
如, 读取 10000H
单元的内容:
1 |
|
mov
可以:
- 将数据直接送入寄存器
- 将一个寄存器中的内容送入另一个寄存器
- 将一个内存单元的内容送入一个寄存器
- 将一个寄存器的内容送入一个内存单元
[...]
表示一个内存单元.
[...]
中的 0 表示内存单元的偏移地址.
此时的段地址自动取自 ds
中的数据.
注意 , 8086 CPU 不允许直接将数据送入段寄存器. 因此需要一个寄存器来进行中转.
3.3 字的传送
如:
1 |
|
3.4 mov
, add
, sub
指令
mov
指令的用法:
也有:
1 |
|
add
和 sub
指令的用法:
3.6 栈
这里讨论的栈, 单指: 一种具有特殊访问方式的存储空间 (LIFO, Last In First Out)
两个操作:
- 入栈, 将一个新的元素放到栈顶
- 出栈, 从栈顶取出一个元素
CPU 提供的栈机制
两个基本指令:
push, 入栈
1
push ax
将
ax
中的数据送入栈中.pop, 出栈
1
pop ax
从栈顶取出数据送入
ax
.
字型数据用两个单元存放, 高地址单元存放高 8 位, 低地址单元存放低 8 位.
两个寄存器来控制栈:
- SS: Stack Segment,(栈段寄存器),存储堆栈段的起始地址。
- SP: Stack Pointer,(栈指针),指向当前栈顶位置。
任意时刻, SS:SP
指向栈顶元素. 因此 push
和 pop
指令执行时, CPU 从 SS 和 SP 中得到栈顶的地址.
push ax
的执行由两步完成:
SP = SP - 2
- 将
ax
中的内容送入SS:SP
指向的内存单元处,SS:SP
此时指向新栈顶
也可以看出, 入栈时, 栈顶从高地址向低地址方向增长
注意, 如果将 10000H ~ 1000FH
这段空间当作栈, 初始状态为空时, SS = 1000H
, SP = 1010H
pop ax
的执行过程同样分为两步:
- 将
SS:SP
指向内存单元处的数据送入ax
中 SP = SP + 2
,SS:SP
指向当前栈顶下面的单元
3.8 栈顶超界问题
SS
和 SP
只是记录了栈顶的地址, 依靠 SS
和 SP
可以保证在入栈和出栈时找到栈顶, 但并不能保证其不会超出栈空间.
当栈满时使用 push
指令入栈, 或在栈空时使用 pop
入栈都有可能发生栈顶超界问题.
3.9 push, pop 指令
可用形式:
如:
1 |
|
注意, push
, pop
等指令, 修改的只是 SP
, 也就是说, 栈顶的变化范围最大为 0 ~ FFFFH
(也就是 16 位寄存器能操作的范围)
3.10 栈段
将一段内存当作栈段, 仅仅是在编程时的一种安排, CPU 并不会由于这种安排, 就在执行 push
, pop
等栈操作指令时自动地将我们定义的栈段当作栈空间来访问.
实验2 用机器指令和汇编指令编程
注意一点 , Debug
程序的 T
命令在执行修改寄存器 SS
的指令时, 下一条指令也紧接着被执行.
第4章 第一个程序
2.1 4.1 一个源程序从写出到执行的过程
4.2 源程序
如:
1 |
|
这里有 3 种伪指令:
XXX segment
和XXX ends
segment
和ends
(end segment) 是一对成对使用的伪指令, 这是在写可被编译器编译的汇编程序时, 必须要用到的一对伪指令, 其定义一个段, 且需要一个名称来标识
一个有意义的汇编程序中至少要有一个段, 这个段用来存放代码.
end
end
是一个汇编程序的结束标记, 编译器在编译汇编程序时, 如果碰到了伪指令end
, 就结束对源程序的编译.assume
其假设某一段寄存器和程序中的某一个用segment...ends
定义的段相关联.
注意 codeseg
是一个标号, 一个标号指代了一个地址.
实现程序返回的代码
1 |
|
在 DOS 中:
- 正在运行的 command 程序将可执行文件加载入内存
- command 程序设置 CPU 的
CS:IP
指向程序的第一条指令, 从而使程序的以运行 - 程序运行结束后, 返回到 command 中, CPU 继续运行 command
第5章 [BX]
和 loop
指令
[bx]
同样表示一个内存单元, 它的偏移地址存放在 bx
中:
1 |
|
5.1 [BX]
有:
1 |
|
5.2 Loop 指令
使用格式为: loop + 标号
loop
指令的执行分为两步:
cx = cx - 1
- 判断
cx
的值, 不为零则转至标号处执行程序, 如果为零则向下执行
也就是说, cx
中存放循环次数.
如:
1 |
|
5.5 loop 和 [bx]
的联合应用
如:
1 |
|
5.6 段前缀
可以在访问内存单元时显示地给出内存单元的段地址所在的段寄存器:
1 |
|
这里的 ds:
, cs:
等就被称为 段前缀
5.7 一段安全的空间
在 8086 模式中, 随意向一段内存空间写入内容是横危险的, 因为这段空间中可能存放着重要的系统数据或代码.
关于 8086 CPU 的 8086 模式和实模式
8086 模式是指 Intel 8086 微处理器在运行时的一种操作模式,该模式可以访问 1MB 的内存空间。在 8086 模式下,CPU 可以同时使用 16 位和 8 位的寄存器,其指令集包括了 16 位和 8 位的指令。在这种模式下,CPU 可以通过段地址和偏移地址来访问内存。
实模式是指 Intel 8086 微处理器在启动时进入的初始模式,该模式下 CPU 可以访问 1MB 的内存空间,但是只能使用 16 位的寄存器和指令。在实模式下,内存被分为多个 64KB 的段,每个段可以通过一个 16 位的段寄存器来访问。在实模式下,CPU 可以通过物理地址来访问内存。
需要注意的是,实模式是 8086/8088 微处理器的初始模式,而 8086 模式则是后来的一种操作模式。在实模式下,CPU 可以通过一些特殊的指令将自己切换到 8086 模式下运行,然后就可以使用 8086 模式下的指令集和功能了。
在一般的 PC 机中, DOS 方式下. DOS 和其他合法程序一般都不会使用 0:200~0:2ff
的 256 个字节的空间. 所以可以认为这段空间是安全的.
第6章 包含多个段的程序
程序取得所需空间的两种方法:
- 加载程序的时候程序分配
- 程序在执行的过程中向系统申请
6.1 在代码段中使用数据
将一些数据存储在一组地址连续的内存单元中:
1 |
|
dw
(define word) 是定义字型数据.
由于这里 dw
定义的数据处于代码段的最开始, 所以偏移地址为 0.
可以指明程序的入口所在:
1 |
|
注意这里的 end start
, end
除了通知编译器程序结束外, 还可以通知编译器程序的入口在什么地方.
在编译, 链接后, 由 end start
指明程序入口, 被转化为一个入口地址, 存储在可执行文件的描述信息中.
当程序被加载如内存之后, 加载者从程序的可执行文件的描述信息中读到程序的入口地址, 设置 CS:IP
6.2 在代码段中使用栈
可以在程序中通过定义数据来取得一段空间, 然后将这段空间当作栈空间来用:
1 |
|
6.3 将数据, 代码, 栈放入不同的段
用定义代码段一样的方法来定义多个段, 然后在这些段里面定义需要的数据, 或通过定义数据来取得栈空间.
1 |
|
第7章 更灵活的定位内存地址的方法
7.1 and 和 or 指令
and
, 逻辑与, 如:
1 |
|
or
, 逻辑或, 如:
1 |
|
7.3 以字符形式给出的数据
在汇编程序中, 用 ‘…..’ 的方式指明数据是以字符的形式给出的, 编译器将它们转化为相对应的 ASCII 码, 如:
1 |
|
7.4 大小写转换的问题
由于小写字母的 ASCII 码值比大写字母的 ASCII 码值大 20H, 而改写一个字母的大小写, 实际上就是要改变他所对应的 ASCII 码.
另一个规律为, 就 ASCII 码的二进制形式来看, 除了第 5 位(位数从 0 开始计算) 外, 大写字母和小写字母的其他各位都一样, 大写字母 ASCII 码的第 5 位为 0, 小写字母的第 5 位为 1. 因此, 将一个字母的第 5 位改为 0 即可变为小写字母.
如:
1 |
|
7.5 [bx + idata]
用 [bx + idata]
来表示一个内存单元.
常用的格式为:
1 |
|
7.6 用 [bx + idata] 的方式进行数组的处理
1 |
|
7.7 SI 和 DI
si
和 di
是 8086 CPU 中和 bx
功能相近的寄存器, 但是 si
和 di
不能分成两个 8 位寄存器来使用.
下面 3 组指令功能相同:
1 |
|
7.8 [bx+si] 和 [bx+di]
用 [bx+si]
和 [bx+di]
来指明一个内存单元, 也可以写为:
1 |
|
7.9 [bx+si+idata] 和 [bx+di+idata]
常用格式为:
1 |
|
7.10 不同的寻址方式的灵活应用
第8章 数据处理的两个基本问题
计算机是进行数据处理, 运算的机器, 其包含两个基本问题:
- 处理的数据在什么地方
- 要处理的数据有多长
8.1 bx, si, di 和 bp
只有以下组合是正确的:
使用 bp
寄存器时, 若指令中没有显性给出段地址, 段地址就默认在 ss
中.
8.2 机器指令处理的数据在什么地方
3 个地方:
- CPU 内部
- 内存
- 端口
8.5 指令要处理的数据有多长
8086 CPU 的指令, 可以处理两种尺寸的数据, byte 和 word.
在没有寄存器名存在的情况下, 用操作符 X ptr
指明内存单元的长度, X 在汇编指令中可以为 word
或 byte
:
1 |
|
“ptr” 是 “pointer” 的缩写,表示指针。”word ptr” 则表示指针所指向的数据类型为 “word”
有些指令默认了访问的是字单元还是字节单元, 如 push [1000H]
, push
指令只进行字操作
8.7 div 指令
div
是除法指令, 需注意:
格式为:
1 |
|
8.8 伪指令 dd
dd
(define double word, 双字).
8.9 dup
dup
(duplicate), 用来进行数据的重复, 如:
1 |
|
相当于:
1 |
|
同样:
1 |
|
相当于:
1 |
|
第9章 转移指令的原理
可以修改 IP
, 或同时修改 CS
和 IP
的指令统称为 转移指令.
其分类为:
9.1 操作符 offset
操作符 offset
在汇编语言中是由编译器处理的符号, 它的功能是取得标号的偏移地址:
1 |
|
这里, offset
操作符取得了标号 start
和 s
的偏移地址 0 和 3.
9.2 jmp 指令
jmp
为无条件转移指令, 可以只修改 IP
, 也可以同时修改 CS
和 IP
.
需要给出两种信息:
- 转移的目的地址
- 转移的距离
9.3 依据位移进行转移的 jmp 指令
格式: jmp short + 标号
这里的 short
指进行的是段内短转移, 对 IP
的修改范围为 -128 ~ 127
如:
1 |
|
需要注意一个现象, CPU 在执行 jmp short
指令的时候并不需要转移的目的地址, 其对应的机器码中不包含目的地址.
jmp short + 标号
指令所对应的机器码中, 并不包含转移的目的地址, 而包含的是转移的位移 , 这个位移则是根据 “标号” 计算出来的.
1 |
|
而这里的功能则为:
1 |
|
另一种:
1 |
|
实现段内近转移, 功能为:
1 |
|
9.4 转移的目的地址在指令中的 jmp 指令
jmp far ptr + 标号
, 功能为:
1 |
|
其机器码中包含转移的目的地址. 高地址处的字是转移的目的段地址, 低地址处的字是转移的目的偏移地址.
9.5 转移地址在寄存器中的 jmp 指令
格式: jmp + 16位reg
功能: (IP) = (16位 reg)
9.6 转移地址在内存中的 jmp 指令
两种格式:
jmp word ptr + 内存单元地址
, 用于段内转移
如:1
2
3mov ax, 0123H
mov ds:[0], ax
jmp word ptr ds:[0]jmp dword ptr + 内存单元地址
, 用于段间转移
高地址处的字是转移的目的段地址, 低地址处的字是转移的目的偏移地址.1
2(CS) = (内存单元地址+2)
(IP) = (内存单元地址)如:
1
2
3
4mov ax, 0123H
mov ds:[0], ax
mov word ptr ds:[2], 0
jmp dword ptr ds:[0]
9.7 jcxz 指令
jcxz
指令为有条件转移指令.
所有的有条件转移指令都是 短转移. 在对应的机器码中包含转移的位移, 而不是目的地址, 且对 IP
的修改范围为: -128 ~ 127
功能:
9.8 loop 指令
loop
指令为循环指令, 所有的循环指令都是 短转移, 在对应的机器码中包含转移的位移, 而不是目的地址, 且对 IP
的修改范围为: -128 ~ 127
.
功能:
第10章 CALL 和 RET 指令
call
和 ret
都是转移指令, 都修改 IP
或同时修改 CS
和 IP
.
10.1 ret
和 retf
ret
指令用栈中的数据, 修改 IP
的内容, 从而实现近转移, 其分两步执行:
- (IP) = ((ss)*16 + (sp))
- (sp) = (sp) + 2
相当于:
1 |
|
retf
指令用栈中的数据, 修改 CS
和 IP
的内容, 从而实现远转移. 其分 4 步操作:
- (IP) = ((ss)*16 + (sp))
- (sp) = (sp) + 2
- (CS) = ((ss)*16 + (sp))
- (sp) = (sp) + 2
相当于:
1 |
|
示例, 下面程序中, ret
指令执行后, (IP) = 0
, CS:IP
指向代码段的第一条指令:
1 |
|
10.2 call 指令
CPU 执行 call
指令时, 进行两步操作:
- 将当前的
IP
或CS
和IP
压入栈中 - 转移
call
指令不能实现短转移, 除此之外, call
指令实现转移的方法和 jmp
指令的原理相同.
10.3 依据位移进行转移的 call
指令
格式为:
1 |
|
结果: 将当前 IP
压栈后, 转到标号处执行指令.
此时 CPU 的操作为:
- (sp) = (sp) - 2
((ss)*16+(sp)) = (IP) - (IP) = (IP) + 16 位位移
相当于:
1 |
|
10.4 转移的目的地址在指令中的 call 指令
格式为:
1 |
|
实现段间转移, 目的地址会出现在机器码中.
CPU 此时的操作为:
- (sp) = (sp) - 2
((ss)*16 + (sp)) = (CS)
(sp) = (sp) - 2 - (CS) = 标号所在段的段地址
(IP) = 标号在段中的偏移地址
相当于:
1 |
|
10.5 转移地址在寄存器中的 call 指令
格式为:
1 |
|
CPU 的操作为:
- (sp) = (sp) - 2
((ss)*16 + (sp)) = (IP)
(IP) = (16位reg)
相当于:
1 |
|
10.6 转移地址在内存中的 call 指令
格式为:
1 |
|
相当于:
1 |
|
或:
1 |
|
相当于:
1 |
|
10.7 call 和 ret 的配合使用
如:
1 |
|
10.8 mul 指令
mul
是乘法指令, 需注意:
格式为:
1 |
|
10.10 参数传递
用寄存器:
1 |
|
用内存:
1 |
|
第11章 标志寄存器
CPU 内部的寄存器中, 有一种特殊的寄存器, 具有以下 3 种作用:
- 用来存储相关指令
- 用来为 CPU 执行相关指令提供行为依据
- 用来控制 CPU 的相关工作方式
被称为 标志寄存器.
8086CPU 的标志寄存器有 16 位, 其中存储的信息通常被称为程序状态字 (PSW, Program Status Word)
flag 寄存器是 按位起作用的 , 也就是每一位有专门的含义, 记录特定的信息.
如:
11.1 ZF 标志
ZF
(Zero Flag), 零标志位, 记录相关指令执行后, 结果是否为 0, 如果结果为 0, 那么 zf = 1
, 反之.
11.2 PF 标志
PF
(Parity Flag), 奇偶标志位, 其记录相关指令执行后, 其结果的所有 bit 位中 1 的个数是否为 偶数 , 如果是, 则 pf=1
, 反之.
11.3 SF 标志
SF
(Sign Flag), 符号标志位, 其记录相关指令执行后, 其结果是否为 负 , 如果是, 则 sf=1
, 反之.
在我们将数据当作有符号数来运算时, 可以通过 SF
标志位来得知结果的正负, 如果我们将数据当作无符号数来运算, SF
的值则没有意义.
11.4 CF 标志
CF
(Carry Flag), 进位标志位, 一般情况下, 在进行无符号数运算的时候, 其记录了运算结果的最高有效位向更高位的进位值, 或借位值.
OF 标志
由于在进行有符号数运算时, 可能发生溢出而造成结果的错误, 则需要 CPU 对指令执行后是否产生溢出进行记录.
OF
(Overflow Flag), 溢出标志, 一般情况下, OF
记录了有符号数运算的结果是否发生了溢出, 如果发生溢出, OF=1
, 反之.
注意, CF
是对无符号数运算有意义的标志位. OF
是对有符号数运算有意义的标志位.
11.6 abc 指令
abc
(add carry) 是带进位加法指令, 他利用了 CF
上记录的进位值.
格式: abc 操作对象1, 操作对象2
功能: 操作对象1 = 操作对象1 + 操作对象2 + CF
11.7 sbb 指令
sbb
(sub borrow) 是带借位减法指令, 其利用了 CF
上记录的借位值.
格式: sbb 操作对象1, 操作对象2
功能: 操作对象1 = 操作对象1 - 操作对象2 - CF
11.8 cmp 指令
cmp
(compare) 是比较指令, 其相当于减法指令, 只是不保存结果, 且其会对标志寄存器产生影响.
格式: cmp 操作对象1, 操作对象2
功能: 操作对象1 - 操作对象2
, 但不保存结果, 仅仅根据计算结果对标志寄存器进行设置.
如:
1 |
|
相当于做 (ax) - (ax)
的操作, 但不保存结果, 根据这个结果来设置 flag 的相关位.
11.9 检测比较结果的条件转移指令
如:
11.10 DF 标志和串传送指令
DF
(Direction Flag), 方向标志位, 在串处理指令中, 控制每次操作后 si
, di
的增减.
df=0
, 每次操作后si
,di
递增df=1
, 每次操作后si
,di
递减
两种串传送指令:
movsb
(move string byte)movsw
(move string word)
其都是将 ds:si
指向的内存单元的字节/字送入 es:di
中, 然后根据标志寄存器 df
位的值, 将 si
和 di
递增或递减.
movsb
相当于:
- ((es)*16 + (di)) = ((ds)*16 + (si))
- 如果
df=0
, (si) = (si) + 1, (di) = (di) + 1
如果df=1
, (si) = (si) - 1, (di) = (di) - 1
这两个指令常配合 rep
(repete) 指令执行, 其根据 cx
的值, 重复执行后面的指令, 如:
1 |
|
相当于:
1 |
|
有两个指令来对 df
位进行设置.
cld
(clear direction flag), 将标志寄存器的df
位置置 0std
(set direction flag), 将标志寄存器的df
位置置 1
11.11 pushf 和 popf
pushf
的功能是将标志寄存器的值压栈.
popf
是从栈中弹出数据, 送入标志寄存器中.
注意这里的 pushf
, popf
和 push
, pop
公用一个栈.
第12章 内中断
CPU 检测到从外部或内部产生的一种特殊信息后, 立即对所接收到的信息进行处理, 这种信息称为中断信息. 中断的含义是, CPU 不再接着 (刚执行完的指令) 向下执行, 而是转去处理这个特殊信息.
12.1 内中断的产生
当 CPU 内部有下面情况发生的时候, 将产生相应的中断信息如:
- 除法错误
- 单步执行
- 执行
into
指令 - 执行
int
指令
8086CPU 用 中断类型码 来标识中断信息的来源. 其为一个字节型数据 (8位), 可以表示 256 种中断信息的来源 (中断源).
上述中断源的类型码如下:
- 除法错误, 0
- 单步执行, 1
- 执行
into
指令, 4 - 执行
int
指令,int n
, 其中n
为字节型立即数, 是提供给 CPU 的中断类型码
12.2 中断处理程序
用来处理中断信息的程序被称为 中断处理程序.
CPU 需要在中断信息和其处理程序的入口地址之间建立某种联系, 而中断类型码的作用就是用来定位中断处理程序.
12.3 中断向量表
CPU 用 8 位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址.
中断向量 指中断处理程序的入口地址, 而 中断向量表 指中断处理程序入口地址的列表. 其保存在内存中. 如:
对于 8086PC 机, 中断向量表指定放在内存地址 0 处, 从内存 0000:0000
到 0000:03FF
的 1024 个单元中存放着中断向量表. 一个表项占两个字, 高地址字存放段地址, 低地址字存放偏移地址.
12.4 中断过程
找到中断程序的入口地址的最终目的是用它设置 CS
和 IP
.
中断过程 , 指 CPU 的硬件用中断类型码找到中断向量, 并用它设置 CS
和 IP
的过程.
由于 CPU 在执行完中断处理程序后, 应该返回原来的执行点继续执行下面的指令, 所以在中断过程中, 在设置 CS
和 IP
之前, 还要将原来的 CS
和 IP
的值保存起来.
过程如下:
- 取得中断类型码 N
pushf
TF=0
,IF=0
push CS
push IP
(IP)=(N*4), (CS)=(N*4+2)
12.5 中断处理程序和 iret
指令
由于 CPU 随时都可能执行中断处理程序, 所以中断处理程序必须一直存储在内存某段空间中, 而中断处理程序的入口地址, 即中断向量, 必须存储在对应的中断向量表表项中.
编写中断处理程序的常规方法:
- 保存用到的寄存器
- 处理中断
- 恢复用到的寄存器
- 用
iret
指令返回
iret
指令相当于:
1 |
|
12.11 单步中断
CPU 在执行完一条指令后, 如果检测到 TF
位为 1, 则产生单步中断, 引发中断过程.
单步中断的中断类型码为 1, 其引发的中断过程为:
- 取得中断类型码 1
- 将标志寄存器入栈,
TF
,IF
设置为 0 CS
,IP
入栈(IP) = (1*4), (CS) = (1*4+2)
单步中断即为, 执行完当前指令后, 再执行单步中断程序.
12.12 响应中断的特殊情况
在有些情况下, CPU 在执行完当前指令后, 即便是发生中断, 也不会响应, 如: 在执行完向 ss
寄存器传送数据的指令后.
第13章 int 指令
int
(interrupt) 指令引发的中断属于内中断.
13.1 int
指令
格式: int n
相当于引发一个 n
号中断的中断过程:
- 取中断类型码 n
- 标志寄存器入栈,
IF=0
,TF=0
- CS, IP 入栈
(IP)=(n*4)
,(CS)=(n*4+2)
可以在程序中使用 int
指令调用任何一个中断的处理程序.
如:
1 |
|
int
指令的最终功能和 call
指令相似, 都是调用一段程序.
13.4 BIOS 和 DOS 所提供的中断例程
在系统板的 ROM 中存放着一套程序, 称为 BIOS (Basic Input/Output System, 基本输入输出系统), 其主要包含:
- 硬件系统的检测和初始化程序
- 外部中断和内部中断的中断例程
- 用于对硬件设备进行 I/O 操作的中断例程
- 其他和硬件系统相关的中断例程
可以用 int
指令直接调用 BIOS 和 DOS 提供的中断例程.
13.5 BIOS 和 DOS 中断例程的安装过程
第14章 端口
CPU 可以直接读写以下 3 个地方的数据:
- CPU 内部的寄存器
- 内存单元
- 端口
与 CPU 相连的其他芯片的寄存器被当作 端口 , 对它们进行统一编址, 从而建立一个统一的端口地址空间, 每一个端口在地址空间中都有一个地址.
14.1 端口的读写
端口地址也是通过地址总线来传送.
对端口的读写不能用 mov
, push
, pop
等内存读写指令, 其读写指令只有两条:
in
, 读取数据out
, 写入数据