Assembly Language(汇编语言)
第一章 基础知识
1.3 汇编语言的组成
汇编语言由以下三类指令组成:
1. 汇编指令:机器码的助记符,有对应的机器码。
2. 伪指令:没有对应的机器码,由编译器执行,计算机并不执行。
3. 其他符号:如+、-、*、/ 等,由编译器识别,没有对应的机器码。
汇编语言的核心是汇编指令,它决定了汇编语言的特性。
1.7 CPU对存储器的读写
CPU要想进行数据的读写,必须和外部器件(标准的说法是芯片)进行下面 3 类信息的交互。
- (地址信息)存储单元的地址。
- (控制信息)器件的选择,读或写的命令。
- (数据信息)读或写的数据。
计算机中专门有连接CPU和其他芯片的导线,通常称为 总线。
总线是一根根导线的集合,根据传送信息的不同,总线从逻辑上又分为 3 类。
1**.地址总线** 2.控制总线 3.数据总线
CPU从三号单元中读取数据的过程:
- CPU通过地址线将地址信息3发出
- CPU通过控制线发出内存读命令,选中存储芯片,并通知它,将要从中读取数据.
- 存储器将3号单元中的数据8通过数据线送入CPU
1.8 地址总线
一个CPU有N根地址线,则可以说这个CPU的地址总线的宽度为N。这样的CPU最多可以寻找2的N次方个内存单元。
地址总线的宽度决定了CPU的寻址能力。
1.9 数据总线
数据总线的宽度决定了CPU和外界的数据传送速度。N根数据总线一个可传送一个N位二进制数据。
数据总线的宽度决定了CPU与其他器件进行数据传送时的一次数据传送量。
(8086CPU的数据总线宽度为8,8086CPU的数据总线宽度为16)
1.10 控制总线
控制总线是一些不同控制线的集合。有多少根控制总线,就意味着CPU提供了对外部器件的多少种控制。所以控制总线的宽度决定了CPU对外部期间的控制能力。
控制总线的宽度决定了CPU对系统中其他器件的控制能力
比如内存读写,一个“读信号输出”的控制线负责由CPU向外传送读信号,CPU向该线上输出低电平表示将要读取数据。相应的“写信号输出”传送写型号“。
1.11 内存空间地址(概述)
一个CPU的地址总线宽度为10,那么可以寻址1024个内存单元,这1024个可以寻到的内存单元就构成这个CPU的内存地址空间。
1.12 主板
主板上有核心器件和一些主要器件,这些器件通过总线(地址总线、数据总线、控制总线)相连。
这些器件有:CPU、存储器、外围芯片组、扩展插槽等。(扩展插槽上一般有RAM内存条和各类接口卡)
1.13 接口卡
所有可用程序控制其工作的设备,必须收到CPU的控制。
CPU对外部设备都不能直接控制,如显示器、音箱、打印机等。
直接控制这些设备进行工作的是插在扩展插槽上的接口卡。CPU通过总线向接口卡发送命令,接口卡根据CPU的命令控制外设进行工作。从而实现CPU对外设的间接控制。
1.14 各类存储芯片
读写属性上分两类:随机存储器(RAM),只读存储器(ROM)。
功能和连接上分:
- RAM(主板和插槽上供CPU使用)
- 装有BIOS的ROM(主板和各类接口卡)
- 接口卡上的RAM(如显存)
1.15 内存地址空间
以上的存储器 :
- 都和CPU的总线相连
- CPU对它们进行读或写的时候都通过控制总线发出内存读写命令
CPU在操控它们的时候,把它们都当作内存来对待,把它们总的看作一个由若干存储单元组成的逻辑存储器,这个逻辑存储器就是我们所说的内存地址空间。
第二章 寄存器
8086CPU有14个寄存器,每个寄存器有一个名称。
这些寄存器是:AX、BX、CX、DX、 SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW。
AX,BX,CX,DX 称作为数据寄存器:(每个可分为H, L两个单独寄存器)
AX (Accumulator):累加寄存器,也称之为累加器;
BX (Base):基地址寄存器;
CX (Count):计数器寄存器;
DX (Data):数据寄存器;
SP 和 BP 又称作为指针寄存器:
SP (Stack Pointer):堆栈指针寄存器;
BP (Base Pointer):基指针寄存器;
SI 和 DI 又称作为变址寄存器:
SI (Source Index):源变址寄存器;
DI (Destination Index):目的变址寄存器;
控制寄存器:
IP (Instruction Pointer):指令指针寄存器;
PSW(Program Status Word):程序状态寄存器;
又称:FLAG:标志寄存器;
段寄存器:
- CS (Code Segment):代码段寄存器;
- DS (Data Segment):数据段寄存器;
- SS (Stack Segment):堆栈段寄存器;
- ES (Extra Segment):附加段寄存器;
2.2 字在寄存器中的存储
8086可以一次性处理一下两种尺寸的数据:
- 字节:记为byte,一个字节由8个bit组成,可以存在8位寄存器中。
- 字:记为word,一个字由两个字节组成,这两个字节分别成为这个字的高位字节和低位字节。
2.3 表格:几条汇编指令
| 汇编指令 | 作用对象 | 具体指令 | 高级语言 |
|---|---|---|---|
| mov(传送指令) | 大部分寄存器 | mov ax, bx | AX = BX |
| add | add ax, bx | AX = AX + BX | |
| sub | sub ax, bx | AX = AX - BX | |
| jmp(转移指令) | CS、IP | jump 段地址:偏移地址 | CS = 段地址, IP = 偏移地址 |
2.6 8086CPU给出物理地址的方法
地址加法器采用 物理地址 = 段地址*16 + 偏移地址。
8086CPU的寻址功能是:“基础地址 + 偏移地址 = 物理地址”。
2.9 段寄存器
段寄存器:CS、DS、SS、ES。
2.10 CS和IP
CS:代码段寄存器 IP:指令指针寄存器
在任意时刻,8086CPU将从内存 CS*16 + IP 单元开始,读取一条指令并执行。
(1)读取的指令进入指令缓冲器;(2)IP=IP+所读取指令的长度,从而转向下一条指令 (重复)
修改 CS、IP 需要使用 jmp 指令。
- 同时修改:jmp 段地址:偏移地址;
- 仅修改IP的内容:jmp 某一合法寄存器;
8086CPU读取和执行指令:
- 初始状态(CS:2000H,IP:0000H,CPU将从内存2000H*16+0000H处读取指令执行)
- CS、IP中的内容送入地址加法器(地址加法器完成:物理地址=段地址*16+偏移地址)
- 地址加法器将物理地址送入输入输出控制电路
- 输入输出控制电路将物理地址20000H送上地址总线
- 从内存20000H单元开始存放的机器指令B8 23 01通过数据总线被送入CPU
- 输入输出控制电路将机器指令B8 23 01送入指令缓冲器
- IP值自动增加,以使CPU可以读取下一条指令。
- 执行控制器执行指令 B8 23 01(即mov ax,0123H)
- 指令B8 23 01执行后AX中的内容为0123H
以此类推。
简要描述如下:
- 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器
- IP=IP+所读取指令的长度,从而指向下一条指令
- 执行指令。转到步骤 1,重复这个过程。
第三章 寄存器(内存访问)
3.2 DS和[address]
8086CPU自动读取 DS 中的数据为内存单元的段地址。
mov al,[0]; [...]中的0代表内存单元的偏移地址。
3.6 栈
栈 是一种具有特殊的访问方式的存储空间.
它的特殊性在于,最后进入这个空间的数据,最先出去,即先进后出(LIFO)(last in first out)。
3.7 CPU提供的栈机制
8086CPU提供出栈和入栈指令,最基本的两个是PUSH(入栈)、POP(出栈)。
8086入栈和出栈是以字为单位的。如 pop ax,表示从栈顶取出数据送入ax。
CS、IP 中存放着当前指令的段地址和偏移地址。
段寄存器 SS、和 寄存器 SP, 栈顶的地址存放在 SS 中,偏移地址存放在 SP 中。
任意时刻,SS:SP指向栈顶元素。push指令和pop指令执行时,,CPU从SS和SP中得到栈顶的地址。
push ax的执行,由以下两步完成:
- SP = SP - 2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面单元为新的栈顶;
- 将 ax 中的内容送入 SS:SP 指向的内存单元处,SS:SP、此时指向新栈顶。
8086CPU中,入栈时,栈顶从高地址向低地址方向增长。
3.9 push、pop指令
push、pop指令(实质上是一种内存传达指令)
push、pop指令格式:
- push 寄存器; 将一个寄存器中的数据入栈
- pop 寄存器; 出栈,用一个寄存器接收出栈的数据
- push 段寄存器;将一个段寄存器中的数据入栈
- pop 段寄存器; 出栈,用一个段寄存器接收出栈的数据
- push 内存单元; 将一个内存字单元处的字入栈(注意:栈的操作都是以字为单位)
- pop 内存单元; 出栈,用一个内存单元接收出栈的数据
指令执行时,CPU要知道内存单元的地址,可以在 push、pop 指令中只给出内存单元的偏移地址。
段地址在指令执行时,CPU 从 DS中取得。
补充:
- 当DS:IP指向一块内存时,表示是一个数据段
- 当CS:IP指向一块内存时,表示是一个代码段
- 当SS:IP指向一块内存时,表示是一个栈段
3.10 栈段
段的综述
我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。这完全是我们自己的安排。
我们可以用一个段存放数据,将它定义为“数据段”;
我们可以用一个段存放代码,将它定义为“代码段”;
我们可以用一个段当作栈,将它定义为“栈段”。
数据段,将它的段地址放在DS中,用 mov、add、sub 等访问内存单元的指令时,CPU 就将我们定义的数据段中的内容,当作数据来访问;
代码段,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令。
栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中,这样CPU在需要进行栈操作的时候,比如执行push、pop指令等,就将我们定义的栈段当作栈空间来用。
- DS :Data Segment;数据段寄存器
- SS :Stack Segment;堆栈段寄存器;SP:Stack Pointer;堆栈指针寄存器
- CS:Code Segment;代码段寄存器;IP :Instruction Pointer;指令指针寄存器
可见,不管我们如何安排,
- CPU将内存中的某段内容当做代码,是因 CS:IP 指向了那里;
- CPU将某段内存当作栈,是因为 SS:SP 指向了那里;
一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不是。关键在于CPU中寄存器的设置,即 CS、IP、SS、SP、DS 的指向。
第四章 第一个程序
4.1 一个源程序从写出到执行的过程
一、:编写汇编源程序。
二、:对源程序进行编译链接。(编译产生目标文件,再用链接程序对目标文件进行连接,生成可在操作系统中直接运行的可执行文件)
可执行文件内容:
- 程序(从源程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
- 相关的描述信息(比如,程序有多大,要占用多少内存空间等)
三、:执行可执行的文件中的程序。
4.2 源程序
assume cs:codesg
codesg segment
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00H
int 21H
codesg ends
end
一、 汇编语言源程序中,包含两种指令,一种是汇编指令,一种是伪指令。
伪指令 (没有对应的机器码的指令,由编译器来执行。)
- segment、ends 定义一个段
- end 汇编程序的结束标记
- assume 将有特定用途的段和相关的段寄存器关联
汇编指令 (有对应机器码的指令,可以被编译为机器指令)
二、 标号
在汇编源程序中,除了汇编指令和伪指令外,还有一些标号,比如“codesg”。一个标号代指了一个地址。比如 codesg在 segment 的前面,作为一个段的名称,这个段的名称最终将被编译、连接程序处理为一个段的段地址。
三、程序的结构
源程序是由一些段构成的。
四、程序返回
将 CPU 的控制权交还给使它得以运行的程序,我们称这个过程为:程序返回
mov ax,4c00H
int 21H
表格 与结束相关的概念
| 目的 | 相关指令 | 指令性质 | 指令执行者 |
|---|---|---|---|
| 通知编译器一个段结束 | 段名 ends | 伪指令 | 编译时,由编译器执行 |
| 通知编译器程序结束 | end | 伪指令 | 编译时,由编译器执行 |
| 程序返回 | mov ax, 4c00H int 21H | 汇编指令 | 执行时,由CPU执行 |
五、语法错误和逻辑错误
一般来说,程序在编译时被编译器发现的错误是语法错误。
4.4 编译
运行 masm.exe
- “[.ASM]” 默认的文件扩展名是 asm
- “[.OBJ]” 目标文件
- “[.LST]” 列表文件,这个文件是编译器将源程序编译为目标文件的过程中产生的中间结果
- “[.CRF]” 交叉引用文件
4.5 连接
运行 link.exe
- “[.OBJ]” 目标文件
- “[.EXE]” 可执行文件
- “[.MAP]” 映像文件 (连接程序将目标文件连接为可执行文件的中间结果)
- “[.LIB]” 库文件
4.6 以简化的方式进行编译的连接
在 masm 后面加上被编译的源程序文件的路径,文件名,在命令行的结尾加上分号,自动生成 obj 文件.
同理,link 生成 exe 文件。
4.8 谁将可执行文件中的程序装载进入内存并使它运行
程序由程序执行.
DOS中 command.com,在DOS中称为命令解释器,也就是DOS系统的shell.
command 处理各种输入:命令或要执行的程序的文件名.
4.9 程序执行过程的跟踪
用 Debug 来跟踪一个程序的运行过程.
DOS 系统中 .EXE 文件中的程序的加载过程:
一下为内存示意图(左对齐)
| PSP | 程序 |
|---|
找到一段起始地址,为 SA : 0000 (即起始地址的偏移地址为 0 ) 的容量足够的空闲内存区;
在这段内存区的前 256 个字节中,创建一个成为程序段前缀(PSP)的数据区,
DOS 要利用 PSP 来和被加载程序进行通信;
从这段内存区的 256 字节处开始(在 PSP 的后面),将程序装入,程序的地址被设为 SA+10H : 0;
空闲内存区从 SA : 0 开始,0~255 字节为 PSP ,从 256 字节处开始存放程序,为更好地区分 PSP 和程序,DOS 一般将它们划分到不同的段中,所以,有了这样的地址安排:(如内存示意图)
空闲内存区:SA : 0
PSP区:SA : 0
程序区:SA+10H : 0
注意:PSP 区和程序区虽然物理地址连续,却有不同的段地址。
将该内存区的段地址存入 ds 中,初始化其它相关的寄存器后,设置 CS:IP 指向程序的入口。
第五章 [BX]和loop指令
一,[bx]和内存单元的描述
要完整地描述一个内存单元,需要两种信息:1.内存单元的地址 2.内存单元的长度(类型)
用[0]表示一个内存单元时,0 代表单元的偏移地址,段地址默认在 ds 中,单元的长度(类型)
[bx]同样也表示一个内存单元,它的偏移地址在bx中。
二,我们定义的描述性的符号:“()”
用描述性的符号“()”来表示一个寄存器或一个内存单元中的内容。
比如:(ax)表示ax中的内容、(al)表示al中的内容
“()”中的元素可以有3种类型:1.寄存器名 2.段寄存器名 3.内存单元的物理地址(一个 20 位的数据)
“(X)“所表示的数据有两种类型:1.字节 2.字。
三,约定符号idata表示常量
5.2 Loop 指令
loop 指令的格式是:loop 标号,CPU执行loop指令的时候,要进行两步操作:1.(cx)=(cx)-1 2.判断cx中的值,不为零则转至标号处执行程序,如何为零则向下执行。
通常我们用 loop 指令来实现循环功能,cx中存放循环次数。
5.3 在Debug中跟踪用loop指令实现的循环程序
在汇编源程序中,数据不能以字母开头。所以在某些情况下要在前面加0。
5.4 Debug和汇编编译器 masm 对指令的不同处理
mov ax,[idata]
debug 将它解释为一个内存单元,“idata”是内存单元的偏移地址;而编译器将“[idata]”解释为“idata”。
在源程序中实现将内存 2000:0 中的单元中的数据送入 al、bl、cl、dl 中。
将偏移地址送入bx寄存器中,用[bx]的方式来访问内存单元.
mov ax, 2000h mov ds, ax ;段地址2000h送入ds mov bx, 0 ;偏移地址0送入bx mov al, [bx] ;ds:bx单元中的数据送入al在“[]”中直接给出内存单元的偏移地址,要在“[]”前面显式地给出段地址所在地段寄存器。
mov al, ds:[bx] 含义:(al)=((ds)*16+0)
如果在“[]”中用寄存器,比如bx,间接给出内存单元地偏移地址,则段地址默认在ds中。
5.6 段前缀
用于显式地指明内存单元地段地址地“ds:” “cs:” “ss:” “es:”,在编程语言中称为段前缀。
5.7 一段安全的空间
- 我们需要直接向一段内存中写入内存;
- 这段内存不应存放系统或其他程序地数据或代码,否则写入操作很可能引发错误;
- DOS方式下,一般情况,0:200~0:2ff 空间中没有系统或其他程序的数据或代码;
第六章 包含多个段的程序
6.1 在代码段中使用数据
使用 dw 等定义数据。
伪指令 end 描述了程序的结束和程序的入口。
若要CPU从何处开始执行程序,在源程序中用“end 标号”指明就可以了。
6.3 将数据、代码、栈放入不同的段
定义多个段的段名不同即可
对地址的引用
8086CPU不允许将一个数值直接送入寄存器。否则将被处理为一个表示段地址的数值。
“代码段”、“数据段”、“栈段”完全是我们的安排
CPU到底如何处理我们定义的段中的内容,是当作指令执行,当作数据访问,还是当作栈空间,完全是靠程序中具体的汇编指令,和汇编指令对CS:IP、SS:IP、DS等寄存器的设置来决定的。
第七章 更灵活的定位内存地址的方法
7.1 and 和 or 指令
and 指令:逻辑与指令,按位进行与运算。(&)
mov al, 01100011B and al, 00111011B执行后:al = 00100011B
通过该指令可将操作对象的相应位设为0,其他位不变。
若想将某一位变为0,则将只相应为设为0.
or 指令:逻辑或指令,按位进行或运算。(|)
mov al, 01100011B or al, 00111011B执行后:al = 01111011B
通过该指令可将操作对象的相应位设为 1,其他位不变。
若想将某一位变为 1,则将只相应为设为 1。
补充:其他操作:
xor 指令:异或(^)
相异 -> 1; 否则 -> 0;
not 指令:取反(~)
左移(<<)
将各二进制全部左移若干位(右补0)
右移(>>)
将各二进制全部右移若干位(正: 左补 0;负: 右补 1)。
无符号右移(>>>)
左0填,右舍弃。
7.3 以字符形式给出的数据
在汇编程序中,用 ‘......’ 的方式指明数据是以字符的形式给出的,编译器将把他们转化位相对应的ASCII码.
7.4 大小写转换问题
小写字母的 ASCII 码值比大写字母的 ASCII码值大20H。
大写字母 ASCII 码的第 5 位(包含0),大写字母和小写字母的其他各位都一样。大写位 0,小写位 1。
7.5 [bx+idata]
我们用 [bx] 的方式来指明一个内存单元,还可以用一种更为灵活的方式来指明内存单元:[bx+idata]表示一个内存单元,它的偏移地址为 (bx)+idata。(bx 中的数值加上 idata)
7.6 用 [bx+idata] 的方式进行数组的处理
C语言:a[i], b[i]
汇编语言:0[bx], 5[bx]
7.7 SI 和 DI
si 和 di 是 8086CPU 中和 bx 功能相近的寄存器,si 和 di 不能够分成两个 8 位寄存器来使用。
SI 和 DI 又称作为变址寄存器:
- SI (Source Index):源变址寄存器;
- DI (Destination Index):目的变址寄存器;
7.8 [bx+si] 和 [bx+di]
我们既可以用 [bx(si或bi)] 和 [bx(si或di)+idata] 的方式来指明一个内存单元,我们还可以用更为灵活的方式:[bx+si] 和[bx+di].
[bx+si] 表示一个内存单元,它的偏移地址为(bx+si)。
也可以写成一下格式(常用):
mov ax, [bx][si]
7.9 [bx+si+idata]和[bx+di+idata]
[bx+si+idata] 表示一个内存单元,它的偏移地址为 (bx)+(si)+idata(即 bx 中的数值加上 si 中的数值在加上 idata)
也可以写成一下格式(常用):
mov ax, [bx+200+si]
mov ax, [200+bx+si]
mov ax, 200[bx][si]
mov ax, [bx].200[si]
mov ax, [bx][si].200
7.10 不同的寻址方式的灵活使用
[idata] 用一个常量来表示地址,可用于直接定位一个内存单元;
[bx] 用一个变量来表示内存地址,可用于直接定位一个内存单元;
[bx+idata] 用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元;
[bx+si] 用两个变量表示地址;
[bx+si+idata] 用两个变量和一个常量表示地址;
补充:一般来说,在需要暂存数据的时候,我们都应该使用栈。
第八章 数据处理的两个基本问题
- 处理的数据在什么地方
- 要处理的数据有多长
定义描述性符号:reg 和 sreg
reg:register 寄存器
sreg:segment register 段寄存器
reg 的集合包括:ax、bx、cx、dx、ah、al、bh、bl、ch、cl、dh、dl、sp、bp、si、di;
sreg的集合包括:ds、ss、cs、es
8.1 bx、si、di 和 bp
在 8086CPU 中,只有这 4 个寄存器可以用在“[...]”中用来进行内存单元的寻址
在[...]中,这4个寄存器可以单个出现,或只能以4种组合出现:
bx和si、bx和di、bp和si、和di。
只要在 [...] 中使用寄存器 bp,而指令中没有显性地给出段地址,段地址就默认在 ss 中。
8.2 机器指令处理的数据在什么地方
绝大多数机器指令都是进行数据处理地指令,处理大致可分为 3 类:读取、写入、运算。
在机器指令这一层来讲,并不关心数据的值是多少,而关心指令执行前一刻,它将要处理地数据所在地位置。指令在执行前,所要处理的数据所在的位置。指令在执行前,所要处理的数据可以在 3 个地方:CPU 内部、内存、端口。
| 机器码 | 汇编指令 | 指令执行前数据的位置 |
|---|---|---|
| 8E1E0000 | mov bx, [0] | 内存,ds:0 单元 |
| 89C3 | mov bx, ax | CPU内部,ax 寄存器 |
| BB0100 | mov bx, 1 | CPU内部,指令缓冲器 |
8.3 汇编语言中数据位置的表达
汇编语言用 3 个概念来表达数据的位置。
立即数(idata)
直接包含在机器指令中的数据(执行前在CPU的指令缓冲器中),在汇编指令中直接给出
寄存器
指令要处理的数据在寄存器中,汇编指令中给出相应的寄存器名。
段地址(SA)和偏移地址(EA)
指令要处理的数据在内存中,在汇编指令中可用[x]的格式给出EA,SA在某个段寄存器中。存放段地址的寄存器可以是默认的。
| 段地址在 DS 中 | [0] | [di] | [bx+8] | [bx+si+8] |
|---|---|---|---|---|
| 段地址在 SS 中 | [bp] | [bp+8] | [bp+si] | [bp+si+8] |
8.4 寻址方式
表格:寻址方式小结
| 寻址方式 | 含义 | 名称 | 常用格式举例 |
|---|---|---|---|
| [idata] | EA=idata; SA=(ds) | 直接寻址 | 1.[idata] |
| [bx] | EA=(bx); SA=(ds) | 寄存器间接寻址 | 1.[bx] |
| [si] | EA=(si); SA=(ds) | 寄存器间接寻址 | |
| [di] | EA=(di); SA=(ds) | 寄存器间接寻址 | |
| [bp] | EA=(bp); SA=(ss) | 寄存器间接寻址 | |
| [bx+idata] | EA=(bx)+idata; SA=(ds) | 寄存器相对寻址 | 1.用于结构体: [bx].idata |
| [si+idata] | EA=(si)+idata; SA=(ds) | 寄存器相对寻址 | 2.用于数组: idata[si], idata[di] |
| [di+idata] | EA=(di)+idata; SA=(ds) | 寄存器相对寻址 | 3.用于二维数组: [bx] [idata] |
| [bp+idata] | EA=(bp)+idata; SA=(ss) | 寄存器相对寻址 | |
| [bx+si] | EA=(bx)+(si); SA=(ds) | 基址变址寻址 | 1.用于二维数组: [bx] [si] |
| [bx+di] | EA=(bx)+(di); SA=(ds) | 基址变址寻址 | |
| [bp+si] | EA=(bp)+(si); SA=(ss) | 基址变址寻址 | |
| [bp+di] | EA=(bp)+(di); SA=(ss) | 基址变址寻址 | |
| [bx+si+idata] | EA=(bx)+(si)+idata; SA=(ds) | 相对基址变址寻址 | 1.用于表格(结构)中的数组项 |
| [bx+di+idata] | EA=(bx)+(di)+idata; SA=(ds) | 相对基址变址寻址 | [bx].idata[si] |
| [bp+si+idata] | EA=(bp)+(si)+idata; SA=(ss) | 相对基址变址寻址 | 2.用于二维数组: |
| [bp+di+idata] | EA=(bp)+(di)+idata; SA=(ss) | 相对基址变址寻址 | idata[bx] [si] |
8.5 指令要处理的数据有多长
在机器指令中要指明,指令进行的是在操作还是字节操作。
通过寄存器名指明要处理的数据的尺寸。
在没有寄存器名存在的情况下,用操作符 X ptr 指明内存单元的长度。
(在没有寄存器参与的内存单元访问中,用 X ptr 显性指明是很有必要的)
其他方法。
有些指令默认了访问的是字单元还是字节单元。(如push只进行字操作)
X 在汇编指令中可以为 word 或 byte。
8.7 div 指令
div是除法指令。(div:division;除法)
- 除数:有8位和16位两种,在一个reg或内存单元中。
- 被除数:默认存放在AX或 DX和AX 中,如果除数为8位,被除数则为16位,默认在AX中存放;如果除数为16位,被除数为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。
- 结果:如果除数位8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。
格式如下:
div reg
div 内存单元
mov ax, 1001
mov bl, 100
div bl
执行后,(al) = 0AH(即10), (ah)=1(余数为1)
8.9 dup 指令
指令格式:
- db 重复的次数 dup (重复的字节型数据)
- dw 重复的次数 dup (重复的字型数据)
- dd 重复的次数 dup (重复的双字型数据)
dup:duplicate;重复
第九章 转移指令的原理
可以修改 IP,或同时修改 CS 和 IP 的指令统称为转移指令。
(概括地讲,转移指令就是可以控制CPU执行内存中某处代码地指令。)
8086CPU的转移行为有以下几类:
- 只修改 IP 时,称为段内转移,比如:jmp ax。
- 同时修改 CS 和 IP 时,称为段间转移,比如:jmp 1000:0。
由于转移指令对 IP 地修改范围不同,段内转移又分为:短转移和近转移。
- 短转移 IP 的修改范围为 -128~127
- 近转移 IP 的修改范围为 -32768~32767
8086CPU的转移指令分为一下几类。
- 无条件转移指令(如:jmp)
- 条件转移指令
- 循环指令(如:loop)
- 过程
- 中断
9.1 操作符 offset
操作符 offset 在汇编语言中是由编译器处理的符号,它的功能时取得标号的偏移地址。(offset 弥补,补偿)
assume cs:codesg
codesg segment
start:mov ax,offset start ;相当于mov ax,0
s:mov ax,offset s ;相当于mov ax,3
codesg ends
end start
offset 操作符取得了标号 start 和 s 的偏移地址 0 和 3.
9.2 jmp 指令
jmp为无条件转移指令,可以只修改 IP,也可以同时修改 CS 和 IP。
jmp 指令要给出两种信息:
- 转移的目的地址
- 转移的距离(段间转移、段内短转移、段内近转移)
9.3 根据位移进行转移的 jmp 指令
指令格式:jmp short 标号(转到标号处执行指令)
这种格式的 jmp 指令实现的时段内短转移,它对 IP 的修改范围为 -128~127。转移指令结束后,CS:IP 应该指向标号处的指令。
实际上的功能为: (IP)=(IP)+ 8位位移
- 8位位移=标号处的地址 - jmp指令后的第一个字节的地址;
- short 指明此处的位移为 8 位位移;
- 8 位位移的范围为 -128~127,用补码表示;
- 8 位位移用编译程序在编译时算出;
指令格式:jmp near 标号
这种格式的 jmp 指令实现的时段内近转移。
- 16 位位移=标号处的地址 - jmp 指令后的第一个字节的地址;
- near ptr 指明此处的位移为 16 位位移,进行的是段内近位移;
- 16 位位移的范围为 -32768~32767,用补码表示;
- 16 位位移由编译程序在编译时给出。
注:CPU 在执行 jmp 指令的时候并不需要转移的目的地址。
9.4 转移的目的地址在指令中的 jmp 指令
指令格式:jmp far ptr 标号(实现的是段间转移,又称为远转移)
(CS)=标号所在段的段地址;(IP)=标号在段中的偏移地址。
far ptr 指明了指令用标号的段地址和偏移地址修改 CS 和 IP。
(机器码中包含转移的目的地址。)
9.5 转移地址在寄存器中的 jmp 指令
指令格式: jmp 16位 reg
功能:(IP)=(16位 reg)
9.6 转移地址在内存中的 jmp 指令
一、jmp word ptr 内存单元地址(段内转移)
功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址。
内存单元地址可用寻址方式的任一格式给出。
二、jmp dword ptr 内存单元地址(段间转移)
功能:从内存单元处开始存放着两个字,高地址处的字是转移的目的的段地址,低地址处是转移的目的偏移地址。
(CS)=(内存单元地址+2) (IP)=(内存单元地址)
9.7 jcxz指令
jcxz指令:jump if CX is zero
指令格式:jcxz标号(如果(cx)=0,转移到标号处执行)。
操作:当(cx)=0时,(IP)=(IP)+8位位移;
- 8 位位移=标号处的地址 - jcxz 指令后的第一个字节的地址;
- 8 位位移的范围为 -128~127,用补码表示;
- 8 位位移由编译程序在编译时算出。
当(cx)≠ 0 时,什么也不做(程序向下执行)
相当于:if((cx)==0) jmp short 标号;
9.8 loop 指令
loop 指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移。
对 IP 的修改范围都为: -128~127.
指令格式: loop 标号(cx)=(cx)-1,如果(cx)≠ 0,转移到标号处执行。
操作:1)(cx)=(cx)-1 2)如果(cx)≠ 0,(IP)=(IP)+8 位位移
- 8 位位移 = 标号处的地址 - loop 指令后的第一个字节的地址;
- 8 位位移的范围为 -128~127,用补码表示;
- 8 位位移由编译程序在编译时算出。
如果(cx)=0,什么也不做,程序向下执行。
相当于:cx--; if((cx)!=0) jmp short 标号;
9.9 根据位移进行转移的意义
jmp short 标号; jmp near ptr 标号; jcxz 标号; loop 标号;
等几种汇编指令,它们对 IP 的修改是根据转移目的地址和转移起始地址之间的位移来进行的。在它们对应的机器码中不包含转移的目的地址,而包含的是到目的地址的位移。这种设计,方便了程序段在内存中的浮动装配。
9.10 编译器对转移位移超界的检测
根据位移进行转移的指令,它们的转移范围收到转移位移的限制,如果在源程序中出现了转移范围超界的问题,在编译的时候,处理器将报错。
注意:jmp 2000:0100 的转移指令,是Debug中使用的汇编指令,汇编编译器并不认识。如果在源程序中使用,编译时也会报错。
第十章 CALL 和 RET 指令
call 和 ret 指令都是转移指令,它们都修改 IP,或同时修改 CS 和 IP。
10.1 ret 和 retf
ret 指令用栈中的数据,修改 IP 的内容,从而实现近转移;
==retf 指令用栈中的数据,修改 CS 和 IP 的内容,从而实现远转移。==
CPU 执行 ret 指令时,进行下面两步操作:
- (IP)=((ss)*16+(sp))
- (sp)=(sp)+2
CPU 执行 retf 指令时,进行下面四步操作:
- (IP)=((ss)*16+(sp))
- (sp)=(sp)+2
- (CS)=((ss)*16+(sp))
- (sp)=(sp)+2
汇编语法解释:
ret: pop IP;
retf: pop IP; pop CS;
10.2 call 指令
CPU 执行 call 指令时,进行两步操作:
- 将当前的 IP 或 CS 压入栈中
- 转移
call 指令不能实现短转移。此外 call 指令实现转移方法和 jmp 指令的原理相同。
10.3 依据位移进行转移的 call 指令
call 标号(将当前的 IP 压栈后,转到标号处执行操作)
CPU 执行此种格式的 call 指令时,进行如下的操作:
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(IP)=(IP)+16 位位移
16 位位移=标号处的地址 - call 指令后的第一个字节的地址;
16 位位移的范围为 -32768~32767,用补码表示;
16 位位移由编译程序在编译时给出。
CPU 执行 “call 标号” 时,相当于进行:
push IP
jmp near ptr 标号
10.4 转移的目的地址
“call far ptr 标号” 实现的是段间转移。
相当于:
push CS
push IP
jmp far ptr 标号
10.5 转移地址在寄存器中的 call 指令
指令格式:call 16 位 reg
功能:
- (sp)=(sp)-2
- ((ss)*16+(sp))=(IP)
- (IP)=(16 位 reg)
相当于:
push IP
jmp 16 位 reg
10.6 转移指令在内存中的 call 指令
转移地址在内存中的 call 指令有两种格式.
- call word ptr 内存单元地址
相当于
push IP
jmp word ptr 内存单元地址
- call dword ptr 内存单元地址
相当于:
push CS
push IP
jmp dword ptr 内存单元地址
10.8 mul 指令
- 两个相乘的数,要么都是8位,要么都是16位。如果是8位,一个默认放在 AL 中,另一个放在 8 位 reg 或内存字节单元中;如果是 16 位,一个默认在 AX 中,另一个放在 16 位 reg 或内存字单元中。
- 结果:如果是 8 位乘法,结果默认放在 AX 中;如果是 16 位乘法,结果高位默认放在 DX 中,低位放在 AX 中。
指令格式:
mul reg
mul 内存单元
mul:multiply;相乘
10.11 批量数据的传递
寄存器 或 栈
10.12 寄存器冲突的问题
使用栈
第十一章 标志寄存器
8086CPU 中有一种特殊的寄存器,被称为标志寄存器(简称:flag),具有一下 3 种作用。
- 用来存储相关指令的某些执行结果;
- 用来为CPU执行相关指令提供行为依据;
- 用来控制CPU的相关工作方式。
8086CPU 的标志寄存器有 16 位,其中存储的信息通常被称为程序状态字(PSW)(program status word)
flag 寄存器是按位起作用的,它的每一位都有专门的含义,记录特定的信息。
表格:8086CPU 的 flag 寄存器的结构:
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| OF | DF | IF | TF | SF | ZF | AF | PF | CF | |||||||
| 溢出标志 | 方向标志 | 中断标志 | 陷阱标志 | 符号标志 | 零标志 | 辅助进位标志 | 奇偶标志 | 进位标志 | |||||||
| overflow flag | direcion flag | interrupt flag | trap flag | sign flag | zero flag | auxiliary carry flag | parity flag | carry flag |
11.1 ZF 标志
flag 的第 6 位是 ZF:零标志位。(zero flag)
ZF 记录相关指令执行后,其结果是否为 0。
如果结果为 0,那么 zf = 1;如果结果不为 0,那么 zf = 0。
11.2 PF 标志
flag 的第 2 位是 PF:奇偶标志位。(parity flag)
PF 记录相关指令执行后,其结果的所有 bit 位中 1 的个数是否为偶数。
如果 1 的个数为偶数, pf = 1,如果为奇数,那么 pf = 0。
11.3 SF 标志
flag 的第 7 位是 SF:符号标志位。(sign flag)
SF 记录相关指令执行后,其结果是否为负。
如果结果为负,sf = 1;如果非负,sf = 0。(有符号运算时,有意义。)
11.4 CF 标志
flag 的第 0 位是 CF:进位标志位。(carry flag)
一般情况下,在进行无符号运算时,CF 记录了运算结果的最高位有效位向更高位的进位值,或从更高位的借位值。
11.5 OF 标志
flag 的第 11 位是 OF:溢出标志位。(overflow flag)
OF 记录了有符号数运算,其结果是否发生了溢出。
如果发生溢出,OF = 1;如果没有,OF = 0。
CF 是对无符号数运算有意义的标志位,而 OF 是对有符号运算有意义的标志位。
CF 和 OF 所表示的进位和溢出,是分别对无符号数和有符号数运算而言的,它们之间没有任何关系。
11.6 adc 指令
adc 是带进位加法指令,它利用了 CF 位上记录的进位值。
指令格式:adc 操作对象 1,操作对象 2
功能:操作对象 1 = 操作对象 1 + 操作对象 2 + CF
比如指令 adc ax,bx 实现的功能是:(ax)=(ax)+(bx)+CF
利用 adc 指令,可以利用 adc 指令执行后重置的 CF 寄存器,对任意大的数据进行加法运算。
adc:add with carry;带进位加法
11.7 sbb 指令
sbb 是带借位减法指令,它利用了 CF 位上记录的借位值。
指令格式:sbb 操作对象 1,操作对象 2
功能:操作对象 1 = 操作对象 1 - 操作对象 2 - CF
比如指令 sbb ax,bx 实现的功能是:(ax)=(ax)-(bx)- CF
利用 sbb 指令,可以利用 sbb 指令执行后重置的 CF 寄存器,对任意大的数据进行减法操作。
sbb:substract with borrow;带错位减法
11.8 cmp 指令
cmp 是比较指令(compare),cmp 的功能相当于减法指令,只是不保存结果。
cmp 指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
cmp 指令格式:cmp 操作对象 1,操作对象 2
我们通过 cmp 指令执行后,相关标志位的值就可以看出比较的结果。
一、进行无符号比较时:cmp ax,bx 执行后:
- ZF = 1,说明(ax)=(bx)
- ZF = 0,说明(ax)!=(bx)
- CF = 1,说明(ax)<(bx)
- CF = 0,说明(ax)>=(bx)
- CF = 1 或 ZF = 1,说明(ax)<=(bx)
二、进行有符号比较时:cmp ah,bh 执行后:
- ZF = 1,说明(ah)=(bh)
- ZF = 0,说明(ah)!=(bh)
- SF = 1,OF = 0,说明(ah)<(bh)(没有溢出,逻辑上的真正结果正负=实际结果的正负)
- SF = 1,OF = 1,说明(ah)>(bh)(因为溢出导致了实际结果位负,那么逻辑上的真正的结果必然为正)
- SF = 0,OF = 1,说明(ah)<(bh)(因为溢出导致了实际结果为正,那么逻辑上的真正的结果必然为负)
- SF = 0,OF = 0,说明(ah)>=(bh)(逻辑上真正结果的正负 = 实际结果的正负)
11.9 检测比较结果的条件转移指令
“转移”指的是它能够修改 IP,而“条件”指的是 它可以根据某种条件,决定是否修改 IP。
常用的根据无符号书的比较结果进行转移的条件转移指令(通常和 cmp 配合使用)
| 指令 | 含义 | 检测的相关标志位置 |
|---|---|---|
| je(equal) | 等于则转移 | ZF = 1 |
| jne | 不等于则转移 | ZF = 0 |
| jb(below) | 低于则转移 | CF = 1 |
| jnb | 不低于则转移 | CF = 0 |
| ja(above) | 高于则转移 | CF = 0 且 ZF = 0 |
| jna | 不高于则转移 | CF = 1 且 ZF = 1 |
11.10 DF 标志和串传送指令
flag 的第 10 位是 DF:方向标志位。(direction flag)
在串处理指令中,控制每次操作后 si、di 的增减。
- DF = 0,每次操作后 si、di 递增
- DF = 1,每次操作后 si、di 递减
设置传送方向:
- cld:clear direction flag(清空 DF)DF = 0,正向传送。
- std:set direction flag(设置 DF)DF = 1,逆向传送。
串传送指令:
1)指令格式:movsb (mov string byte)
功能:movsb 的功能是将 ds:si 指向的内存单元中的字节送入 es:di 中,然后根据标志寄存器 df 位的位置,将 si 和 di 递增或递减。
2)指令格式:movsw (mov string word)
功能:movsw 的功能是将 ds:si 指向的内存单元中的字节送入 es:di 中,然后根据标志寄存器 df 位的位置,将 si 和 di 递增 2 或递减 2 。
通常 movsb 和 movsw 都和 rep 配合使用。
3)指令格式:rep movsb (repeat)
rep 的作用是根据 CX 的值,重复执行后面的串传送指令。由于每执行一次 movsb 指令 si 和 di 都会递增或递减指向后一个单元或前一个单元,则 rep movsb 就可以循环实现(cx)个字符的传送。
11.11 pushf 和 popf
pushf 的功能是将标志寄存器的值压栈,而 popf 是从栈中弹出数据,送入标志寄存器中。
pushf 和 popf,为直接访问标志寄存器提供了一种方法。
11.12 标志寄存器在 Debug 中的表示
在 Debug 中,标志寄存器是按照有意义的各个标志寄存器单独表示的。
表格 Debug 对我们已知的标志位的表示。
| 标志 | 值为 1 的标记 | 值为 0 的标记 |
|---|---|---|
| OF(溢出标志) | OV(overflow)溢出 | NV(not)没有溢出 |
| SF(符号标志) | NG(negative)负 | PL(plus)正 |
| ZF(零标志) | ZR(zero)零 | NZ(not)非零 |
| PF(奇偶标志) | PE(parity even)偶 | PO(parity odd)奇 |
| CF(进位标志) | CY(carry)进位 | NC(not carry )没有进位 |
| DF(方向标志) | DN(down)减少 | UP(up)增加 |
第十二章 内中断
任何一个通用的 CPU,比如 8086,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从 CPU 外部发送过来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理。可以称这种特殊的信息为:中断信息。
中断的意思是指,CPU 不再接着(刚执行完的指令)向下执行,而是转去处理这个特殊信息。
中断信息可以来自 CPU 的内部和外部。
这里所说的中断信息,是为了便于理解而采用的一种逻辑上的说法。它是对几个具有先后顺序的硬件操作所产生的事件的统一描述。“中断信息”是要求CPU上进行某种处理,并向所要进行的该种处理提供了必备的参数的通知信息。
12.1 内中断的产生
当 CPU 的内部有下面的情况发生的时候,将产生相应的中断信息。
- 除法错误,比如,执行 div 指令产生的除法溢出;
- 单步执行;
- 执行 into 指令;
- 执行 int 指令。
8086CPU 用中断类型码的数据来标识中断信息的来源。中断类型码为一个字节型数据,可以表示 256 种中断信息的来源。产生中断信息的事件,即中断信息的来源,可以简称为中断源。
8086CPU中的部分中断类型码如下:
- 除法错误:0
- 单步执行:1
- 执行 into 指令:4
- 执行 int 指令:该指令格式为 int n,指令中的 n 为字节型立即数,是提供给 CPU 的中断类型码。
into :interrupt if overflow;溢出中断指令
12.2 中断处理程序
CPU 在收到中断信息后,应该转去执行该中断信息的处理程序。
12.3 中断向量表
CPU 用 8 位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。
中断向量表是中断向量的列表。中断向量是中断程序的入口地址。
中断向量表在内存中保存,其中存放着 256 个中断源所对应的中断处理程序的入口。(CPU 用中断类型码,通过查找中断向量表,就可以得到中断程序的入口地址。)
中断向量表在内存中存放,对于 8086PC 机,中断向量表指定放在内存地址 0 处。从内存 0000:0000 到 0000:03FF的 1024 个单元中存放着中断向量表。(8086CPU 固定从这个地方读取中断向量表)
对于 8086CPU,这个入口地址包括段地址和偏移地址,所以一个表项占两个字,高地址字存放段地址,低地址字存放偏移地址。
12.5 中断过程
用中断类型码找到中断向量,并用它设置CS和P,这个工作是由CPU的硬件自动完成的。CPU 硬件完成这个工作的过程被称为中断过程。
CPU 收到中断信息后,要对中断信息进行处理,首先将引发中断过程。硬件在完成中断过程后,CS:IP将指向中断处理程序的入口,CPU开始执行中断处理程序。在设置 CS 和 IP 之前还要将它们的值保存起来,因为CPU 在执行完成中断处理程序后,还要继续执行被中断的程序。
下面是 8086CPU 在收到中断信息后,所引发的中断过程。
- (从中断信息中)取得中断类型码;
- 标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中);
- 设置标志寄存器的第 8 位 TF 和第 9 位 IF 的值为 0;
- CS 的内容入栈;
- IP 的内容入栈;
- 从内存地址为中断类型码×4 和中断类型码×4+2 的两个字单元中读取中断处理程序的入口地址设置 IP 和 CS
简洁的描述中断过程:
取得中断类型码 N;
pushf
TF = 0,IF = 0
push CS
push IP
(IP)=(N*4),
(CS)=(N*4+2)
最后一步完成后,CPU 开始执行由程序员编写的中断处理程序。
TF = 0 是为了防止单步中断
12.5 中断处理程序和 iret 指令
由于CPU随时都可能检测到中断信息,也就是说,CPU随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存某段空间之中。而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中。
中断程序编写的常规步骤(与子程序比较相似):
- 保存用到的寄存器;
- 处理中断;
- 恢复用到的寄存器;
- 用 iret 指令返回。
iret 指令的功能用汇编语法描述为:
pop IP
pop CS
popf
iret 通常和硬件自动完成的中断过程配合使用。在中断过程中,寄存器入栈的顺序是 标志寄存器、CS、IP,而 iret的出栈顺序是 IP、CS、标志寄存器,刚好和其相对应,实现了用执行中断处理程序前的 CPU 现场恢复 标志寄存器和 CS、IP 的工作。iret 指令执行后,CPU 回到执行中断处理程序前的执行点继续执行程序。
iret:interrupt return;中断返回
12.10 设置中断变量
将 do0 的入口地址 0:200,写入中断向量表的 0 号表项中,使 do0 成为 0 号中断的中断处理程序。
0 号表项的地址为 0:0,其中 0:0 字单元存放偏移地址,0:2 字单元存放段地址。
程序如下:
mov ax, 0
mov es, ax
mov word ptr es:[0*4],200h
mov word ptr es:[0*4+2],0
12.11 单步中断
基本上,CPU 在执行完一条指令后,如果检测到标志寄存器的 TF 位为 1,则产生单步中断,引发中断过程。
单步中断的中断类型码为 1,由它所引发的中断过程如下:
取得中断类型码 1;
标志寄存器入栈,TF、IF 设置为 0;
CS、IP 入栈;
(IP)=(1*4),
(CS)=(1*4+2)
Debug 的 t 命令,就是利用了 单步中断。
t : trace 跟踪
12.12 相应中断的特殊情况
一般情况下,CPU 在执行完当前指令后,如果检测到中断信息,就响应中断,引发中断过程。
在有些情况下,CPU在执行完当前指令后,即便是发生中断,也不会响应。以下是用来说明的一种情况。
在执行完向 SS 寄存器传送数据的指令后,即便是发生中断,CPU也不会响应。这样做的主要原因是,SS:SP 联合指向栈顶,而对它们的设置应该连续完成。如果在执行完设置 SS 的指令后,CPU 响应中断,引发中断过程,要在栈中压入 标志寄存器、CS 和 IP 的值。而 SS 改变,SP 并未改变,SS:SP 指向的不是正确的栈顶,将引起错误。所以CPU在执行完设置 SS 的指令后,不响应中断。这给连续设置 SS 和 SP 指向正确的栈顶提供了一个时机。即,我们应该利用这个特性,将设置 SS 和 SP 的指令连续存放,使得设置 SP 的指令緊接着设置 SS 的指令执行,而在此之间,CPU不会引发中断过程。
比如,我们要将栈顶设为 1000:0,应该:
mov ax,1000h
mov ss,ax
mov sp,0
如果在第二行后加入 mov ax,0 Debug 的 t 指令,看不到结果。(不响应单步中断)
第十三章 int 指令
另一种,重要的内中断,由 int 指令引发的中断。
13.1 int 指令
指令格式:int n,n 为中断类型码,它的功能是引发中断过程。
CPU 执行 int n 指令,相当于引发一个 n 号中断的中断过程,执行过程如下。
取中断类型码 n;
标志寄存器入栈,IF = 0,TF = 0;
CS、IP 入栈
(IP)=(n*4),
(CS)=(n*4+2)。
中断处理程序 也可以简称为 中断例程
13,3 对 int、iret 和栈的深入理解
可以利用 iret 指令(interrupt return 中断返回)和 int 指令,完成 loop 指令功能。
例:在中断例程中,可以从栈里取得标号 s 的段地址和标号 se 的偏移地址,而用标号 se 的偏移地址加上 bx 中存放的转移位移就可以得到标号 s 的偏移地址
利用 iret 指令,我们将栈中的 se 的偏移地址加上 bx 中的转移位移,则栈中的 se 的偏移地址就变为了 s 的偏移地址。我们再使用 iret 指令,用中的内容设置 CS、IP,从而实现转移到标号 s 处。
13.4 BIOS 和 DOS 所提供的中断例程
在系统板的 ROM 中存放着一套程序,称为 BIOS(基本输入输出系统),BIOS 中主要包含以下几部分内容。
- 硬件系统的检测和初始化程序;
- 外部中断和内部中断的中断例程;
- 用于对硬件设备进行 I/O 操作的中断例程;
- 其他和硬件系统相关的中断例程。
操作系统DOS也提供了中断例程,从操作系统的角度来看,DOS的中断例程就是操作系统向程序员提供的编程资源。
BIOS和DOS在所提供的中断例程中包含了许多子程序,这些子程序实现了程序员在编程的时候经常需要用到的功能。程序员在编程的时候,可以用int指令直接调用BIOS和 DOS 提供的中断例程,来完成某些工作。
和硬件设备相关的 DOS 中断例程中,一般都调用了BIOS的中断例程。
13.5 BIOS 和 DOS 中断例程的安装过程
BIOS 和 DOS 提供的中断例程的安装过程:
- 开机后,CPU一加电,初始化(CS) = 0FFFFH,(IP) = 0,自动从 FFFF:0 单元开始执行程序。FFFF:0 处有一条转跳指令,CPU 执行该指令后,转去执行 BIOS 系统中的硬件系统检测和初始化程序。
- 初始化程序将建立 BIOS 所支持的中断向量,即将 BIOS 提供的中断例程的入口地址登记在中断向量表中。注意,对于 BIOS 所提供的中断例程,只需将入口地址登记在中断向量表中即可,因为它们是固化到 ROM 中的程序,一直在内存中存在。
- 硬件系统检测和初始化完成后,调用 int19h 进行操作系统的引导。从此将计算机交由操作系统控制。
- DOS 启动后,除完成其他工作外,还将它所提供的中断例程装入内存,并建立相应的中断向量。
13.6 BIOS 中断例程应用
int10h 中断例程是 BIOS 提供的中断例程,其中包含了多个和屏幕输出相关的子程序。
int 10h 中断例程的设置光标位置功能:
mov ah,2 ;设置光标
mov bh,0 ;第 0 页
mov dh,5 ;dh 中放行号
mov dl,12 ;dl 中放列号
int 10h
(ah) = 2 表示调用第 10h 号中断例程的 2 号子程序,功能为设置光标位置,可以提供光标所在的行号(80×25 字符模式下:024)、列号(80×25 字符模式下:079),和页号作为参数。
bh 中页号的含义:内存地址空间中,B8000H
BFFFFH 共 32kB的空间,为80×25彩色字符模式的显示缓冲区。一屏的内容在显示缓冲区中共占4000个字节。显示缓冲区分为 8 页,每页4KB(≈4000B),显示器可以显示任意一页的内容。一般情况下,显示第 0 页的内容。也就是说,通常情况下,B8000HB8F9FH 中的 4000 个字节的内容将出现在显示器上。
int10h 中断例程的在光标位置显示字符功能:
mov ah,9 ;在光标位置显示字符
mov al,'a' ;字符
mov bl,7 ;颜色属性
mov bh,0 ;第 0 页
mov cx,3 ;字符重复个数
int 10h
(ah) = 9表示调用第 10h 号中断例程的 9 号子程序,功能为在光标位置显示字符,可以提供要显示的字符、颜色属性、页号、字符重复个数作为参数。
bl 中的颜色属性的格式如下:
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
|---|---|---|---|---|---|---|---|---|
| 含义 | BL | R | G | B | I | R | G | B |
| 闪烁 | 背 | 景 | 高亮 | 前 | 景 |
可以看出,和显存中的属性字节的格式相同。
注意 : 闪烁的效果必须在全屏 DOS 方式下才能看到。
13.7 DOS 中断例程应用
int 21h 中断例程是 DOS 提供的中断例程,其中包含了 DOS 提供给程序员在编程时调用的子程序。
int 21h 号中断例程的第 4ch 号子程序,功能为程序返回,可以提供返回值作为参数。使用这个功能的时候经常写做:
mov ax,4c00h
int 21h
int 21h 中断例程在光标位置显示字符串的功能:
ds:dx 指向字符串 ;要显示的字符串需要用"{{content}}quot;作为结束符
mov ah,9 ;功能号 9,表示在光标位置显示字符串
int 21h
(ah) =9 表示调用第 21 h号中断例程的 9 号子程序,功能为在光标位置显示字符串,可提供字符串的地址作为参数。
编程:在屏幕的 5 行 12 列显示字符串“Welcome to masm!”
assume cs:code
data segment
db 'Welcome to masm', '{{content}}#39;
data ends
code segment
start:mov ah,2 ;置光标
mov bh,0 ;第 0 页
mov dh,5 ;dh 中放行号
mov dl,12 ;dl 中放列号
int 10h
mov ax,data
mov ds,ax
mov dx,0 ;ds:dx 指向字符串的首地址 data:0
mov ah,9
int 21h
mov ax,4c00h
int 21h
code ends
end start
上述程序在屏幕的 5 行 12 列显示字符串 “Welcome tomasm!”,直到遇见“$”。
如果字符串比较长,遇到行尾,程序会自动转到下一行开头处继续显示;如果到了最后一行,还能自动上卷一行。
“$”本身并不显示,只起到边界的作用
DOS 为程序员提供了许多可以调用的子程序,都包含在 int21h 中断例程中。
第十四章 端口
各种存储器都和 CPU 的地址线、数据线、控制线相连。CPU 在操控它们的时候,把它们都当作内存来对待,把它们总地看做一个由若干存储单元组成的逻辑存储器,这个逻辑存储器我们称其为内存地址空间。
在 PC 机系统中,和 CPU 通过总线相连的芯片除各种存储器外,还有以下3种芯片:
- 各种接口卡(比如,网卡、显卡)上的接口芯片,它们控制接口卡进行工作;
- 主板上的接口芯片,CPU 通过它们对部分外设进行访问;
- 其他芯片,用来存储相关的系统信息,或进行相关的输入输出处理。
在这些芯片中,都有一组可以由CPU读写的寄存器。这些寄存器,它们在物理上可能处于不同的芯片中,但是它们在以下两点上相同:
- 都和 CPU 的总线相连,当然这种连接是通过它们所在的芯片进行的;
- CPU 对它们进行读或写的时候都通过控制线向它们所在的芯片发出端口读写命令。
CPU 将这些寄存器都当作端口,对它们进行统一编址,从而建立了一个统一的端口地址空间。每一个端口在地址空间中都有一个地址。(从 CPU 的角度来说)
CPU 可以直接读写以下 3 个地方的数据:、
- CPU 内部的寄存器;
- 内存单元;
- 端口。
14.1 端口的读写
在访问端口的时候,CPU 通过端口地址来定位端口。因为端口所在的芯片和 CPU 通过总线相连,所以,端口地址和内存地址一样,通过地址总线来传送。在PC系统中,CPU最多可以定位 64KB 个不同的端口。则端口地址的范围为0~65535。
对端口的读写不能用 mov、push、pop 等内存读写指令。端口的读写指令只有两条:in 和 out,分别用于从端口读取数据和往端口写入数据。
CPU 执行内存访问指令和端口访问指令时,总线上的信息:
1.访问内存
mov ax,ds:[8] ;假设执行前(ds) = 0
执行时与总线相关的操作如下所示:
- CPU 通过地址线将地址信息 8 发出;
- CPU 通过控制线发出内存读命令,选中存储器芯片,并通知它,将要从中读取数据;
- 存储器将 8 号单元中的数据通过数据线送入 CPU。
2.访问端口
in al,60h ;从 60h 号端口读入一个字节
执行时与总线相关的操作如下:
- CPU 通过地址线将地址信息 60h 发出;
- CPU 通过控制线发出端口读命令,选中端口所在的芯片,并通知它,将要从中读取数据;
- 端口所在的芯片将 60h 端口中的数据通过数据线送入CPU。
注意,在 in 和 out 指令中,只能使用 ax 或 al 来存放从端口中读入的数据或要发送到端口中的数据。访问 8 位端口时用 al,访问 16 位端口时用 ax。
对 0~255 以内的端口进行读写时:
in al,20h ;从 20h 端口读入一个字节
out 20h,al ;往 20h 端口写入一个字节
对 256~65535 的端口进行读写时,端口号放在 dx 中:
mov dx,3f8h ;将端口号 3f8h 送入 dx
in al,dx ;从 3f8h 端口读入一个字节
out dx,al ;向 3f8h 端口写入一个字节
14.2 CMOS RAM 芯片
可以通过对 CMOS RAM 的读写来体会对端口的访问。
CMOS:Complementary Metal Oxide Semiconductor;互补金属氧化物半导体
PC 机中,有一个 CMOS RAM 芯片,一般简称为 CMOS。此芯片的特征如下:
- 包含一个实时钟和一个有 128 个存储单元的 RAM 存储器 (早期的计算机为64个字节)。
- 该芯片靠电池供电。所以,关机后其内部的实时钟仍可正常工作,RAM中的信息不丢失。
- 128 个字节的 RAM 中,内部实时钟占用 0~0dh 单元来保存时间信息,其余大部分单元用于保存系统配置信息,供系统启动时 BIOS 程序读取。BIOS 也提供了相关的程序,使我们可以在开机的时候配置 CMOS RAM 中的系统信息。
- 该芯片内部有两个端口,端口地址为 70h 和 71h。CPU 通过这两个端口来读写CMOS RAM。
- 70h 为地址端口,存放要访问的 CMOS RAM 单元的地址;71h 为数据端口,存放从选定的 CMOS RAM 单元中读取的数据,或要写入到其中的数据。可见 CPU 对 CMOS RAM 的读写分两步进行,比如,读CMOS RAM 的 2 号单元:
- 将 2 送入端口 70h;
- 从端口 71h 读出 2 号单元的内容。
14.3 shl 和 shr 指令
shl 和 shr 是逻辑移位指令。
一、shl 是逻辑左移指令,它的功能为:(shl:shift left;左移)
- 将一个寄存器或内存单元中的数据向左移位;
- 将最后移出的一位写入 CF 中;
- 最低位用 0 补充。
shl 操作过程:1.左移 2.将最后移出的一位写入 CF 中 3.最低位用 0 补充
将 X 逻辑左移一位,相当于执行 X = X*2
二、shr 是逻辑右移指令,它和 shl 所进行的操作刚好相反。(shr:shift right;右移)
- 将一个寄存器或内存单元中的数据向右移位;
- 将最后移出的一位写入 CF 中;
- 最高位用 0 补充。
shr 操作过程:1.右移 2.将最后移出的一位写入 CF 中 3.最高位用 0 补充
将 X 逻辑右移一位,相当于执行 X = X/2
14.4 CMOS RAM 中存储的时间信息
在 CMOS RAM 中,存放着当前的时间:年、月、日、时、分、秒。这 6 个信息的长度都为 1 个字节,存放单元为:
秒:0 分:2 时:4 日:7 月:8 年:9
这些数据以 BCD 码的方式存放 BCD:binary-coded decimal;二进码十进数
BCD 码是以 4 位二进制数表示十进制数码的编码方法,如下所示:
| 十进制数码: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| 对应的 BCD 码: | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 |
比如,数值 26,用 BCD 码表示为:0010 0110
可见,一个字节可表示 两个BCD码。则 CMOS RAM 存储时间信息的单元中,存储了用两个 BCD 码表示的两位十进制数,高 4 位的 BCD 码表示十位,低 4 位的 BCD 码表示个位。比如,00010100b 表示 14。
从 CMOS RAM 的 8 号单元读出当前月份的 BCD 码。
要读取 CMOS RAM 的信息,首先要向地址端口 70h 写入要访问的单元的地址:
mov al,8
out 70h,al
然后从数据端口71h中取得指定单元中的数据:
in al,71h
第十五章 外中断
CPU 在计算机系统中,除了能够执行指令,进行运算以外,还应该能够对外部设备进行控制,接收它们的输入,向它们进行输出。也就是说,CPU除了有运算能力外,还要有 I/O (Input/Output,输入/输出)能力。
处理外设的输入,需要解决两个问题:1.外设的输入随时可能发生,CPU如何得知?2.CPU 从何处得到外设的输入?
15.1 接口芯片和端口
PC 系统的接口卡和主板上,装有各种接口芯片。这些外设接口芯片的内部有若干寄存器,CPU将这些寄存器当作端口来访问。
外设的输入不直接送入内存和 CPU,而是送入相关的接口芯片的端口中;CPU 向外设的输出也不是直接送入外设,而是先送入端口中,再由相关的芯片送到外设。CPU还可以向外设输出控制命令,而这些控制命令也是先送到相关芯片的端口中,然后再由相关的芯片根据命令对外设实施控制。
可见,CPU通过端口和外部设备进行联系。
15.2 外中断信息
外设随时都可能发生需要CPU及时处理的事件,CPU 提供中断机制来满足这种需要。
当 CPU 的内部有需要处理的事情发生的时候,将产生中断信息,引发中断过程。这种中断信息来自 CPU 的内部。
还有一种中断信息,来自于 CPU 外部,当 CPU 外部有需要处理的事情发生的时候,比如说,外设的输入到达,相关芯片将向 CPU 发出相应的中断信息。CPU 在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入。
在PC系统中,外中断源一共有以下两类:
可屏蔽中断
可屏蔽中断是 CPU 可以不响应的外中断。CPU 是否响应可屏蔽中断,要看标志寄存的 IF 位的设置。当 CPU检测到可屏蔽中断信息时,如果 IF = 1,则 CPU 在执行完当前指令后响应中断,引发中断过程;如果 IF=0,则不响应可屏蔽中断。
可屏蔽中断所引发的中断过程,除在第 1 步(取中断类型码 n)的实现上有所不同外,基本上和内中断的中断过程相同。因为可屏蔽中断信息来自于 CPU 外部,中断类型码是通过数据总线送入CPU的;而内中断的中断类型码是在CPU内部产生的。
现在,我们可以解释中断过程中将 IF 置为 0 的原因了。将 IF 置 0 的原因就是,在进入中断处理程序后,禁止其他的可屏蔽中断。当然,如果在中断处理程序中需要处理可屏蔽中断,可以用指令将 IF 置 1。8086CPU提供的设置正的指令如下:
sti,设置 IF = 1; sti:set interrupt;设置中断
cli,设置 IF = 0。 cli:clear interrupt;清空中断
不可屏蔽中断
不可屏蔽中断是 CPU 必须响应的外中断。当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程。
对于 8086CPU,不可屏蔽中断的中断类型码固定为 2,所以中断过程中,不需要取中断类型码。则不可屏蔽中断的中断过程为:
- 标志寄存器入栈,IF = 0,TF = 0;
- CS、IP 入栈;
- (IP) = (8),(CS)=(0AH)。
几乎所有由外设引发的外中断,都是可屏蔽中断。当外设有需要处理的事件(比如说键盘输入)发生时,相关芯片向 CPU 发出可屏蔽中断信息。不可屏蔽中断是在系统中有必须处理的紧急情况发生时用来通知 CPU 的中断信息。
15.3 PC 机键盘的处理过程
一、键盘输入
键盘上的每一个键相当于一个开关,键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描。
**按下一个键时,开关接通,该芯片就产生一个扫描码,**扫描码说明了按下的键在键盘上的位置。扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为 60h。
**松开按下的键时,也产生一个扫描码,**扫描码说明了松开的键在键盘上的位置。松开按键时产生的扫描码也被送入 60h 端口中。
一般将按下一个键时产生的扫描码称为通码,松开一个键产生的扫描码称为断码。扫描码长度为一个字节,通码的第 7 位为 0,断码的第 7 位为 1,即:
断码 = 通码 + 80h
二、引发 9 号中断
键盘的输入到达 60h 端口时,相关的芯片就会向 CPU 发出中断类型码为9的可屏甗断信息。CPU 检测到该中断信息后,如果 IF = 1,则响应中断,引发中断过程,转去执 int 9 中断例程。
三、执行 int 9 中断例程
BIOS 提供了 int 9 中断例程,用来进行基本的键盘输入处理,主要的工作如下:
- 读出 60h 端口中的扫描码;
- 如果是字符键的扫描码,将该扫描码和它所对应的字符码(即 ASCII 码)送入内存中的 BIOS 键盘缓冲区;如果是控制键(比如 Ctrl)和切换键(比如CapsLock)的扫描码,则将其转变为状态字节(用二进制位记录控制键和切换键状态的字节)写入内存中存储状态字节的单元;
- 对键盘系统进行相关的控制,比如说,向相关芯片发出应答信息。
BIOS 键盘缓冲区是系统启动后,BIOS 用于存放 int 9 中断例程所接收的键盘输入的内存区。该内存区可以存储 15 个键盘输入,因为 int 9 中断例程除了接收扫描码外,还要产生和扫描码对应的字符码,所以在 BIOS 键盘缓冲区中,一个键盘输入用一个字单元存放,高位字节存放扫描码,低位字节存放字符码。
0040:17 单元存储键盘状态字节,该字节记录了控制键和切换键的状态。
表格:键盘状态字节各位记录的信息
| 0 | 右 Shift 状态 | 置 1 表示按下 右 Shift 键 |
|---|---|---|
| 1 | 左 Shift 状态 | 置 1 表示按下 左 Shift 键 |
| 2 | Ctrl 状态 | 置 1 表示按下 Ctrl 键 |
| 3 | Alt 状态 | 置 1 表示按下 Alt 键 |
| 4 | ScrollLock 状态 | 置 1 表示 Scroll 指示灯亮 |
| 5 | NumLock 状态 | 置 1 表示 小键盘输入的是数字 |
| 6 | CapsLock 状态 | 置 1 表示 输入大写字母 |
| 7 | Insert 状态 | 置 1 表示 处于删除状态 |
15.4 编写 int 9 中断例程
从上面的内容中,可以看出键盘输入的处理过程:
- 键盘产生扫描码;
- 扫描码送入60h端口;
- 引发9号中断;
- CPU 执行 int 9 中断例程处理键盘输入。
上面的过程中,第 1、2、3 步都是由硬件系统完成的。我们能够改变的只有 int 9 中断处理程序。我们可以重新编写 int 9 中断例程,按照自己的意图来处理键盘的输入。
int 过程的模拟过程为:
- 标志寄存器入栈;
- IF = 0,TF = 0;
- call dword ptr ds:[0]。
通过对键盘输入的处理,了解了 CPU 对外设输入的通常处理方法,即:
- 外设的输入送入端口;
- 向 CPU 发出外中断(可屏蔽中断)信息;
- CPU 检测到可屏蔽中断信息,如果 IF = 1,CPU 在执行完当前指令后响应中断,执行相应的的中断例程;
- 可在中断例程实现对外设输入的处理。
端口和中断机制,是 CPU 进行 I/O 的基础。
第十六章 直接定址表
讲解如何有效合理地组织数据,以及相关的编程技术。
16.1 描述了单元长度的标号
一、地址标号:之前,我们一直在代码段中使用 地址标号 来标记指令、数据、段的起始地址,比下面的程序将 code 段中的 a 标号处的 8个数据累加,结果存储到 b 标号处的字中。
a: db 1,2,3,4,5,6,7,8
b: dw 0
二、数据标号:这种标号不但表示内存单元的地址,还表示了内存单元的长度,即表示在此标号处的单元,是一个字节单元,还是字单元,还是双字单元。使用这种包含单元长度的标号,可以使我们以简洁的形式访问内存中的数据。我们将这种标号称为数据标号,它标记了存储数据的单元的地址和长度。它不同于仅仅表示地址的地址标号。
a db 1,2,3,4,5,6,7,8
b dw 0
在 code 段中使用的 标号、b 后面没有 “:” ,它们是同时描述内存地址和单元长度的标号。标号 a,描述了地址code:0,和从这个地址开始,以后的内存单元都是字节单元;而标号 b 描述了地址 code:8,和从这个地址开始,以后的内存单元都是字单元。
因为这种标号包含了对单元长度的描述,所以在指令中,它可以代表一个段中的内存单元。
指令: mov ax,b
相当于: mov ax,cs:[8]
16.2 在其他段中使用数据标号
一般来说,我们不在代码段中定义数据,而是将数据定义到其他段中。在其他段中,我们也可以使用数据标号来描述 存储数据的单元的地址和长度。
注意,在后面加有 “:” 的地址标号,只能在代码段中使用,不能在其他段中使用
注意,如果想在代码段中直接用数据标号访问数据,则需要用伪指令 assume 将标号所在的段和一个段寄存器联系起来。否则编译器在编译的时候,无法确定标号的段地址在哪一个寄存器中。当然,这种联系是编译器需要的,但绝对不是说,我们因为编译器的工作需要,用 assume 指令将段寄存器和某个段相联系,段寄存器中就会真的存放该段的地址。我们在程序中还要使用指令对段寄存器进行设置。
可以将标号当作数据来定义,此时,编译器将标号所表示的地址当作数据的值
seg 操作符,功能为取得某一标号的段地址。(segment 段)
16.3 直接定址表
利用表,在两个数据集合之间建立一种映射关系,使我们可以用查表的方法根据给出的数据得到其在另一集合中的对应数据。这样做的目的一般来说有以下 3 个。
- 为了算法的清晰和简洁;
- 为了加快运算速度;
- 为了使程序易于扩充。
编程的时候要注意程序的容错性,即对于错误的输入要有处理能力。
我们将通过给出的数据进行计算或比较而得到结果的问题,转化为用给出的数据作为查表的依据,通过查表得到结果的问题。具体的查表方法,是用查表的依据数据,直接计算出所要查找的元素在表中的位置。像这种可以通过依据数据,直接计算出所要找的元素的位置的表,我们称其为直接定址表。
16.4 程序入口地址的直接定址表
我们可以在直接定址表中存储子程序的地址,从而方便地实现不同子程序的调用。
用根据功能号查找地址表的方法,程序的结构清晰,便于扩充。如果加入一个新的功能子程序,那么只需要在地址表中加入它的入口地址就可以了。
第十七章 使用 BIOS 进行键盘输入和磁盘读写
BIOS 为磁盘和键盘这两种外设的 IO 提供了最基本的中断例程,我们对它们的应用和相关的问题进行讨论。
17.1 int 9 中断例程对键盘输入的处理、
键盘输入将引发 9 号中断,BIOS 提供了 int 9 中断例程。CPU 在 9 号中断发生后,执行 int 9 中断例程,从 60h 端口读出扫描码,并将其转化为相应的 ASCII 码或状态信息,存储在内存的指定空间(键盘缓冲区或状态字节)中。
一般的键盘输入,在 CPU 执行完 int 9 中断例程后,都放到了键盘缓冲区中。键盘缓冲区中有 16 个字单元,可以存储 15 个按键的扫描码和对应的 ASCII 码。
简要地看一下 int 9 中断例程对键盘输入的处理方法:
- 初始状态下,没有键盘输入,键盘缓冲区空,此时没有任何元素。
- 按下 A 键,引发键盘中断;CPU执行 int 9 中断例程,从 60h 端口读出 A 键的通码;然后检测状态字节,看看是否有 Shift、Ctrl 等切换键按下;发现没有切换键按下,则将 A 键的扫描码 1eh 和对应的 ASCII 码,即字母“a”的 ASCII 码 61h,写入键盘缓冲区。缓冲区的字单元中,高位字节存储扫描码,低位字节存储ASCII码。
- 按下 B 键,引发键盘中断;CPU 执行 int 9 中断例程,从 60h 端口读出 B 键的通码;然后检测状态字节,看看是否有切换键按下;发现没有切换键按下,将 B 键的扫描码 30h 和对应的 ASCII 码,即字母“b”的 ASCII 码 62h,写入键盘缓冲区。此时缓冲区中的内容如下。
- 按下 C、D、E 键。
- 按下左 Shift 键,引发键盘中断;int 9 中断例程接收左 Shift 键的通码,设置 0040:17 处的状态字节的第 1位为 1,表示左 Shift 键按下。
- 按下 A 键,引发键盘中断;CPU 执行 int 9 中断例程,从 60h 端口读出 A 键的通码;检测状态字节,看看是否有切换键按下;发现左 Shift 键被按下,则将键的扫描码 1Eh 和 Shift_A 对应的 ASCII 码,即字母“A”的 ASCII 码 41h,写入键盘缓冲区。
- 松开左 Shift 键,引发键盘中断;int 9 中断例程接收左 Shift 键的断码,设置 0040:17 处的状态字节的第 1位为 0,表示左 Shift 键松开。
- 按下 A 键,引发键盘中断;CPU 执行 int 9 中断例程,从 60h 端口读出 A 键的通码;然后检测状态字节,看看是否有切换键按下;发现没有切换键按下,则将 A 键的扫描码 1Eh 和 A 对应的 ASCII 码,即字母“a”的 ASCII 码 61h,写入键盘缓冲区。
17.2 使用 int 16h 中断例程读取键盘缓冲区
BIOS 提供了 int 16h 中断例程供程序员调用。int 16h 中断例程中包含的一个最重要的功能是从键盘缓冲区中读取一个键盘输入,该功能的编号为 0。下面的指令从键盘缓冲区中读取一个键盘输入,并且将其从缓冲区中删除;
mov ah,0
int 16h
结果:(ah)= 扫描码,(al)= ASCII码
int16h 中断例程的 0 号功能,进行如下的工作:
- 检测键盘缓冲区中是否有数据;
- 没有则继续做第1步;
- 读取缓冲区第一个字单元中的键盘输入;
- 将读取的扫描码送入 ah,ASCII 码送入 al;
- 将已读取的键盘输入从缓冲区中删除。
BIOS 的 int 9 中断例程和 int16h 中断例程是一对相互配合的程序,int 9 中断例程向键盘缓冲区中写入,int 16h 中断例程从缓冲区中读出。它们写入和读出的时机不同,int 9 中断例程是在有键按下的时候向键盘缓冲区中写入数据;而int 16h 中断例程是在应用程序对其进行调用的时候,将数据从键盘缓冲区中读出。
在编写一般的处理键盘输入的程序的时候,可以调用 int16h 从键盘缓冲区中读取键盘的输入。
17.4 应用 int 13h 中断例程对磁盘进行读写
以 3.5 英寸软盘为例
3.5 英寸软盘分为上下两面,每面有 80 个磁道,每个磁道又分为 18 个扇区,每个扇区的大小为 512 个字节。
则:2面 × 80磁道 × 18扇区 × 512字节 = 1440KB ≈ 1.44MB
磁盘的实际访问由磁盘控制器进行,我们可以通过控制磁盘控制器来访问磁盘。只能以扇区为单位对磁盘进行读写。在读写扇区的时候,要给出面号、磁道号和扇区号。面号和磁道号从 0 开始,而扇区号从 1 开始。
如果我们通过直接控制磁盘控制器来访问磁盘,则需要涉及许多硬件细节。BIOS提供了对扇区进行读写的中断例程,这些中断例程完成了许多复杂的和硬件相关的工作。我们可以通过调用 BIOS中断例程来访问磁盘。
BIOS提供的访问磁盘的中断例程为 int13h;
一、读取 :
;读取 0 面 0 道 1 扇区的内容到 0:200
;入口参数:
(ah) = int 13h 的功能号(2 表示读扇区)
(al) = 读取的扇区数
(ch) = 磁道号
(cl) = 扇区号
(dh) = 磁头号(对于软盘即面号,因为一个面用一个磁头来读写)
(dl) = 驱动器号 软驱从 0 开始, 0: 软驱 A, 1: 软驱 B;
硬盘从 80h 开始, 80h: 硬盘 C, 81h: 硬盘 D
es:bx 指向接收从扇区读入数据的内存区
;返回参数
操作成功: (ah) = 0, (al) = 读入的扇区数
操作失败: (ah) = 出错代码
二、写入:
;写入 0 面 0 道 1 扇区的内容到 0:200
;入口参数:
(ah) = int 13h 的功能号(3 表示写扇区)
(al) = 写入的扇区数
(ch) = 磁道号
(cl) = 扇区号
(dh) = 磁头号(对于软盘即面号,因为一个面用一个磁头来读写)
(dl) = 驱动器号 软驱从 0 开始, 0: 软驱 A, 1: 软驱 B;
硬盘从 80h 开始, 80h: 硬盘 C, 81h: 硬盘 D
es:bx 指向将写入磁盘的数据
;返回参数
操作成功: (ah) = 0, (al) = 写入的扇区数
操作失败: (ah) = 出错代码
实验、课程设计、研究
一、实验系列
未提及:实验4 [bx]和loop的使用 实验5 编写、调试具有多个段的程序
实验6 实践课程中的程序 实验7 寻址方式在结构化数据访问中的应用
实验8 分析一个奇怪的程序 实验10 编写子程序
实验11 编写子程序 实验12 编写 0 号中断的处理程序
实验14 访问 CMOS RAM 实验16 编写包含多个功能子程序的中断例程
实验1 查看CPU和内存,用机器指令和汇编指令编程
表格:汇编指令编程
| Debug指令 | 英文全称 | 中文含义 | 功能 |
|---|---|---|---|
| A命令 | assemble | 汇编 | 以汇编指令的格式在内存中写入一条机器指令 |
| C命令 | compare | 比较 | 比较内存的两个部分 |
| D命令 | dump | 转储 | 显示一定范围内存地址的内容 |
| E命令 | enter | 输入 | 将数据输入到内存中指定的地址 |
| F命令 | fill | 填充 | 使用指定的值填充指定内存区域中的地址 |
| G命令 | go | 运行 | 运行当前在内存中的程序 |
| H命令 | hex | 十六进制 | 对指定的两个参数执行十六进制运算 |
| I命令 | input | 输入 | 从指定的端口读取并显示一个字节值 |
| L命令 | load | 载入 | 将某个文件或特定磁盘扇区的内容加载到内存 |
| M命令 | move | 移动 | 将一个内存块中的内容复制到另一个内存块中 |
| N命令 | name | 命名 | 指定 Debug l(加载)或 w(写入)命令的可执行文件的名称,或者指定正在调试的可执行文件的参数 |
| O命令 | output | 输出 | 将字节值发送到输出端口 |
| P命令 | proceed | 执行 | 执行循环、重复的字符串指令、软件中断或子例程;或通过任何其他指令跟踪 |
| Q命令 | quit | 退出 | 停止 Debug 会话,不保存当前测试的文件 |
| R命令 | register | 寄存器 | 显示或改变一个或多个 CPU 寄存器的内容 |
| S命令 | search | 搜索 | 在某个地址范围搜索一个或多个字节值的模式 |
| T命令 | trace | 跟踪 | 执行一条机器指令?(8086方式) |
| U命令 | unassemble(dis) | 反汇编 | 将内存中的机器指令翻译成汇编指令 反汇编字节并显示相应的原语句,其中包括地址和字节值。反汇编代码看起来象已汇编文件的列表 |
| W命令 | write | 写入 |
实验2 用机器指令和汇编指令编程
Debug的使用 及 中断机制。
Debug 的 T 命令在执行修改寄存器 SS 的指令时,下一条指令也紧接着被执行。
实验3 编程、编译、连接、跟踪
利用DEBUG
实验9 根据材料编程
一个字符在显示缓冲器中就要占据两个字节,分别存放字符的 ASCII 码和属性
实验13 编写、应用中断例程
int 7ch 号中断例程,功能为显示一个用 0 结束的字符串,中断例程安装在 0:200 处。
实验15 安装新的 int 9 中断例程
指令系统总结
8086CPU 提供以下几大类指令:
数据传送指令
比如,mov、push、pop、pushf、popf、xchg 等都是数据传送指令,这些指令实现寄存器和内存、寄存器和寄存器之间的单个数据传送。
算术运算指令
比如,add、sub、adc、sbb、inc、dec、cmp、imul、idiv、aaa 等都是算术运算指令,这些指令实现寄存器和内存中的数据的算数运算。它们的执行结果影响标志寄存器的 sf、zf、of、cf、pf、af 位。
逻辑指令
比如,and、or、not、xor、test、shl、shr、sal、sar、rol、ror、rcl、rcr 等都是逻辑指令。除了 not 指令外,它们的执行结果都影响标志寄存器的相关标志位。
转移指令
可以修改 IP,或同时修改 CS 和 IP 的指令统称为转移指令。转移指令分为以下几类:
- 无条件转移指令,比如,jmp;
- 条件转移指令,比如,jcxz、je、jb、ja、jnb、jna 等;
- 循环指令,比如,loop;
- 过程,比如,call、ret、retf;
- 中断,比如,int、iret。
处理机控制指令
这些指令对标志寄存器或其他处理机状器进行设置,比如,cld、std、cli、sti、nop、clc、cmc、stc、hlt、wait、esc、lock 等都是处理机控制指令。
串处理指令
这些指令对内存中的批量数据进行处理,比如,movsb、movsw、cmps、scas、lods、stos 等。若要使用这些指令方便地进行批量数据的处理,则需要和 rep、repe、repne 等前缀指令配合使用。
实验17 编写包含多个功能子程序的中断例程
逻辑扇区号和物理扇区号的关系如下:
逻辑扇区号 = (面号 × 80 + 磁道号)×18 + 扇区号 - 1
根据逻辑扇区号算出物理编号:
- int():描述性运算符,取商
- rem():描述性运算符,取余数
- 逻辑扇区号 = (面号 × 80 + 磁道号)× 18 + 扇区号 - 1
- 面号 = int(逻辑扇区号 / 1440)
- 磁道号 = int(rem(逻辑扇区号 / 1440)/ 18)
- 扇区号 = rem(rem(逻辑扇区号 / 1440)/18)+ 1
参数说明:
- 用 ah 寄存器传递功能号:0 表示读,1 表示写;
- 用 dx 寄存器传递要读写的扇区的逻辑扇区号;
- 用 es:bx 指向存储读出数据或写入数据的内存区。
二、课程设计系列
未提及:课程设计 Ⅰ
课程设计 Ⅱ
材料:
开机后,CPU 自动进入到 FFFF:0 单元处执行,此处有一条跳转指令。CPU 执行该指令后,转去执行 BIOS 中的硬件系统检测和初始化程序。
初始化程序将建立 BIOS 所支持的中断向量,即将 BIOS 提供的中断例程的入口地址登记在中断向量表中。
硬件系统检测和初始化完成后,调用 int19h 进行操作系统的引导。
如果设为从软盘启动操作系统,则 int19h 将主要完成以下工作:
- 控制 0 号软驱,读取软盘 0 道 0 面 1 扇区的内容到 0:7c00;
- 将 CS:IP 指向 0:7c00。
软盘的 0 道 0 面 1 扇区中装有操作系统引导程序。int 19h 将其装到 0:7c00 处后,设置 CPU 从 0:7c00 开始执行此处的引导程序,操作系统被激活,控制计算机。
如果在 0 号软驱中没有软盘,或发生软盘 I/O 错误,则 int 19h 将主要完成以下工作:
- 读取硬盘 C 的 0 道 0 面 1 扇区的内容到 0:7c00;
- 将 CS:IP 指向 0:7c00。
三、研究系列
未提及:研究实验1 搭建一个精简的 C 语言开发环境
研究实验2 使用寄存器
研究实验5 函数如何接收不定数量的参数
研究实验3 使用内存空间
对于内存空间来说,要使用它们一般都需要给出两个信息:
- 指明是内存空间所在、是哪个的信息;
- 指明内存空间有多大的类型信息。
研究实验4 不用 main 函数编程
C 程序必须从 main 函数开始,是 C 语言的规定,这个规定不是在编译时保证的(tc.exe对fc的编译是可以通过的),也不是连接的时候保证的(虽然,tc.exe 文件对f.obj文件不能连接成fexe,但link.exe却可以),而是用如下的机制保证的:
- C 开发系统提供了用户写的应用程序正确运行所必须的初始化和程序返回等关程序,这些程序存放在相关的 .obj 文件(比如,c0s.obj)中。
- 需要将这些文件和用户 .obj 文件一起进行连接,才能生成可正确运行的 .exe 文件。
- 连接在用户 .obj 文件前面的由 C 语言开发系统提供的 .obj 文件里的程序要对 main 函数进行调用。
基于这种机制,我们只要改写 c0s.obj,让它调用其他函数,编程时就可以不写main函数了。
原书附注
未提及:附注4 用栈传递参数 附注5 公式证明
附注1 Intel 系列微处理器的 3 种工作模式
- 实模式:工作方式相当于一个 8086
- 保护模式:提供支持多任务环境的工作方式,建立保护机制(这与 VAX 等小型机类似)。
- 虚拟 8086 模式:可从保护模式切换至其中的一种 8086 工作方式。这种方式的提供使用户可以方便地在保护模式下运行一个或多个原 8086 程序。
VAX (Virtual Address eXtension)是一种可以支持机器语言和虚拟地址的 32 位小型计算机。VAX 最初由 DEC公司在二十世纪七十年代初发明。
附注2 补码
补码:使用 00000000b01111111b 表示 0127,然后再用它们按位取反加 1 后的数据表示负数。
在补码方案中:
最高位为 1,表示负数
正数的补码取反加 1 后,为其对应的负数的补码;
负数的补码取反加 1 后,为其绝对值。
补码为有符号数的运算提供了方便,运算后的结果依旧满足补码规则。
附注3 汇编编译器(masm.exe)对 jmp 的相关处理
一、向前转移
s: :
:
:
:
jmp s (jmp short s、jmp near ptrs、jmp far ptr s)
编译器中有一个地址计数器(AC),编译器在编译程序过程中,每读到一个字节 AC 就加 1 。当编译器遇到一些伪操作的时候,也会根据具体情况使 AC 增加,如 db、dw 等。
在向前转移时,编译器可以在读到标号 s 后记下 AC 的值 as,在读到 jmp ... s 后记下 AC 的值 aj。编译器可以用 as - aj 算出位移量 disp。
disp:displacement;偏移地址
此时,编译器作如下处理:
如果 disp ∈ [-128,127],则不管汇编指令格式是下面的哪一种,都将它转变为 jmp short s 所对应的机器码.
jmp s jmp short s jmp near ptr s jmp far ptr sjmp short s 所对应的机器码格式为:EB disp(占两个字节)
如果 disp ∈ [-32768,32767],则:
对于 jmp short s 将产生编译错误;
对于 jmp s、jmp near ptr s 将产生 jmp near ptr s 所对应的机器码,
jmp near ptr s 所对应的机器码格式为:E9disp(占3个字节);
对于 jmp far ptr s 将产生相应的编码,
jmp far ptr s 所对应的机器码格式为:EA 偏移地址 段地址(占 5 个字节)。
编译产生错误是由 jmp short s 引起的,去掉 jmp short s 后再编译就可以通过。用 Debug 进行反汇编查看。
二、向后转移
jmp s (jmp short s、jmp near ptrs、jmp far ptr s)
:
:
s: :
在这种情况下,编译器先读到 jmp … s 指令。由于它还没有读到标号s,所以编译器此时还不能确定标号 s 处的 AC 值。也就是说,编译器不能确定位移量 disp 的大小。
此时,编译器将 jmp … s 指令都当作 jmp short s 来读取,记下 jmp … s 指令的位置和 AC 的值 aj,并作如下处理:
- 对于 jmp short s,编译器生成 EB 和 1 个 nop 指令(相当于预留 1 个字节的空间,存放 8 位 disp);
- 对于 jmp s 和 jmp near ptr s,编译器生成 EB 和两个 nop 指令(相当于预留两个字节的空间,存放 16 位 disp);
- 对于 jmp far ptr s,编译器生成 EB 和 4 个 nop 指令(相当于预留 4 个字节的空间,在放段地址和偏移地址)。
作完以上处理后,编译器继续工作,当向后读到标号 s 时,记下 AC 的值 as,并计算出转移的位移量:disp = as - aj。
此时,编译器作如下处理:
如果 disp ∈ [-128,127],则不管汇编指令格式是下面的哪一种,都在**前面记下的 jmp ... s 指令位置处添上 jmp shorts s 对应的机器码(格式为:EB disp)**
jmp s jmp short s jmp near ptr s jmp far ptr s注意:
- 对于 jmp s 和 jmp near ptr s 格式,在机器码 EB disp 后还有 1 条 nop 指令;
- 对于 jmp far ptr s格式,在机器码 EB disp 后还有 3 条 nop 指令。
如果 disp ∈ [-32768,32767],则:
对于 jmp short s 将产生编译错误;
对于 jmp s、jmp near ptr s,在前面记下的 jmp ... s 指令位置处添上 jmp near ptr s 所对应的机器码(格式为:E9 disp);
对于 jmp far ptr s 在前面记下的 jmp … s 指令位置处添上相应的代码。
编译产生错误是由 jmp short s 引起的,去掉 jmp short s 后再编译就可以通过。用 Debug 进行反汇编查看。
nop:no operation;无操作,即空指令
执行 nop 指令只使程序计数器 PC 加 1,所以占用一个机器周期。
附注4 用栈传递参数
用栈传递参数的原理:由调用者将需要传递给子程序的参数压入栈中子程序从栈中取得参数。
附注5 公式证明
X/n( X < 65536×65536,n ≠ 0)在计算过程中保证不会出现除法溢出。
Cmixed 附注
一、汇编词汇释义
- inc :increase 1;(== ++)
- dw:define word;定义字型数据
- db:define byte;定义字节数据
- idata:immediate data;立即数
- dd: double word ;双字节
- sub:subtract;减去
- mul:multiplication;乘法
- adc:add with carry;带进位加法
- sbb:substract with borrow;带错位减法
- rep:repeat;重复
- cld:clear direction flag(清空 DF)DF = 0,正向传送。
- std:set direction flag(设置 DF)DF = 1,逆向传送。
- into :interrupt if overflow;溢出中断指令
- iret:interrupt return;中断返回
- sti:set interrupt;设置中断
- cli:clear interrupt;清空中断
- disp:displacement;偏移地址
- nop:no operation;无操作,即空指令
- psw: program status word;程序状态字
- EA:Effective Address;有效地址 ,即偏移地址。
- SA:Segment Address;段地址
- tcon:timer control;定时器控制寄存器
- ie:interrupt enable;中断允许
- scon:serial control;串行控制器
- CMOS:Complementary Metal Oxide Semiconductor;互补金属氧化物半导体
- CRLF:Carriage-Return Line-Feed;回车 换行
- LF:Line-Feed;换行符
- BCD:binary-coded decimal;二进码十进数
- Capslock:capitals lock;大小写锁定
二、8086 CPU 标记寄存器的各个标记位:
OF overflow flag 溢出标志 操作数超出机器能表示的范围表示溢出,溢出时为1.
SF sign Flag 符号标志 记录运算结果的符号,结果负时为1.
ZF zero flag 零标志 运算结果等于0时为1,否则为0.
CF carry flag 进位标志 最高有效位产生进位时为1,否则为0.
AF auxiliary carry flag 辅助进位标志 运算时,第3位向第4位产生进位时为1,否则为0.
PF parity flag 奇偶标志 运算结果操作数位为1的个数为偶数个时为1,否则为0.
DF direcion flag 方向标志 用于串处理.DF=1时,每次操作后使SI和DI减小.DF=0时则增大.
IF interrupt flag 中断标志 IF=1时,允许CPU响应可屏蔽中断,否则关闭中断.
TF trap flag 陷阱标志 用于调试单步操作.
三、指令系统总结
8086CPU 提供以下几大类指令:
数据传送指令
比如,mov、push、pop、pushf、popf、xchg 等都是数据传送指令,这些指令实现寄存器和内存、寄存器和寄存器之间的单个数据传送。
算术运算指令
比如,add、sub、adc、sbb、inc、dec、cmp、imul、idiv、aaa 等都是算术运算指令,这些指令实现寄存器和内存中的数据的算数运算。它们的执行结果影响标志寄存器的 sf、zf、of、cf、pf、af 位。
逻辑指令
比如,and、or、not、xor、test、shl、shr、sal、sar、rol、ror、rcl、rcr 等都是逻辑指令。除了 not 指令外,它们的执行结果都影响标志寄存器的相关标志位。
转移指令
可以修改 IP,或同时修改 CS 和 IP 的指令统称为转移指令。转移指令分为以下几类:
- 无条件转移指令,比如,jmp;
- 条件转移指令,比如,jcxz、je、jb、ja、jnb、jna 等;
- 循环指令,比如,loop;
- 过程,比如,call、ret、retf;
- 中断,比如,int、iret。
处理机控制指令
这些指令对标志寄存器或其他处理机状器进行设置,比如,cld、std、cli、sti、nop、clc、cmc、stc、hlt、wait、esc、lock 等都是处理机控制指令。
串处理指令
这些指令对内存中的批量数据进行处理,比如,movsb、movsw、cmps、scas、lods、stos 等。若要使用这些指令方便地进行批量数据的处理,则需要和 rep、repe、repne 等前缀指令配合使用。
四、DEBUG指令释义
表格:汇编指令编程
| Debug指令 | 英文全称 | 中文含义 | 功能 |
|---|---|---|---|
| A命令 | assemble | 汇编 | 以汇编指令的格式在内存中写入一条机器指令 |
| C命令 | compare | 比较 | 比较内存的两个部分 |
| D命令 | dump | 转储 | 显示一定范围内存地址的内容 |
| E命令 | enter | 输入 | 将数据输入到内存中指定的地址 |
| F命令 | fill | 填充 | 使用指定的值填充指定内存区域中的地址 |
| G命令 | go | 运行 | 运行当前在内存中的程序 |
| H命令 | hex | 十六进制 | 对指定的两个参数执行十六进制运算 |
| I命令 | input | 输入 | 从指定的端口读取并显示一个字节值 |
| L命令 | load | 载入 | 将某个文件或特定磁盘扇区的内容加载到内存 |
| M命令 | move | 移动 | 将一个内存块中的内容复制到另一个内存块中 |
| N命令 | name | 命名 | 指定 Debug l(加载)或 w(写入)命令的可执行文件的名称,或者指定正在调试的可执行文件的参数 |
| O命令 | output | 输出 | 将字节值发送到输出端口 |
| P命令 | proceed | 执行 | 执行循环、重复的字符串指令、软件中断或子例程;或通过任何其他指令跟踪 |
| Q命令 | quit | 退出 | 停止 Debug 会话,不保存当前测试的文件 |
| R命令 | register | 寄存器 | 显示或改变一个或多个 CPU 寄存器的内容 |
| S命令 | search | 搜索 | 在某个地址范围搜索一个或多个字节值的模式 |
| T命令 | trace | 跟踪 | 执行一条机器指令?(8086方式) |
| U命令 | unassemble(dis) | 反汇编 | 将内存中的机器指令翻译成汇编指令 反汇编字节并显示相应的原语句,其中包括地址和字节值。反汇编代码看起来象已汇编文件的列表 |
| W命令 | write | 写入 |
x86_64 汇编
通用寄存器(GPR)
通用寄存器(General-Purpose Register, GPR):用于一般数据、地址等的存储,并且可以直接操作。
特殊寄存器(Special-Purpose Register, SPR):用于存储程序状态。
通用寄存器:
- eax:“累加器”寄存器,常用来保存算数运算的结果。
- ebx:“基址”寄存器。常用来存储用于保存变量的内存块的基址。
- ecx:“计数器”寄存器,传统上用于计数。
- edx:“数据”寄存器,常被用来存储数据。
- esi:“源索引”寄存器(源变址寄存器)。传统上,它用于存储源数组中的索引。
- ebp:“基址指针”寄存器。它的功能是存储当前栈帧的基址。
- esp:“栈指针”寄存器。它存储了当前栈顶部的地址。
特殊寄存器(SPR)
- eip:“指令指针”寄存器,用于存储下一条要执行的指令的地址。
- eflags:“标志”寄存器。存储标志,这些标志的值为真或假,保存着系统状态和先前执行指令的结果的信息。
低位访问去掉 e
x 结尾,16位寄存器可进一步划分为两个八位寄存器 l (低八位)和 h (高八位)。
64位寄存器
e 变为 r
增加了 r8~r15 八个寄存器。
低32位、低16位、低8位的访问,标记为 r8d、r8w、r8b。
| 数据移动指令 | 执行流程指令 | 比较指令 | 其他指令 | ||||
|---|---|---|---|---|---|---|---|
| add | and | call | mov | jmp | test | lea | |
| sub | or | return | 条件跳跃指令 | cmp | nop | ||
| mul | xor | push | |||||
| inc | not | pop | |||||
| dec | |||||||