汇编语言
Assembly
一、基础知识
1.1.机器语言
- 是机器指令的集合
- 展开来讲就是一台机器可以正确执行的命令
1.2.汇编语言的产生
- 主体是汇编指令
- 汇编语言和机器语言的差别在于指令的表示方法上。汇编指令是机器指令便于记忆的书写格式
- 汇编指令是机器指令的助记符,与机器语言/指令是一一对应的
寄存器
简单来说是
CPU
中可以存储数据的器件,一个CPU
中有多个寄存器。
1.3.汇编语言的组成
- 汇编指令 – 机器码的助记符 [ 每一个
CPU
都有自己的汇编指令集 ]- 伪指令 – 由编译器执行
- 其他符号 – 由编译器识别
1.4.存储器
CPU
是计算机的核心部件,它控制整个计算机的运作并进行运算,要想让一个CPU
工作,就必须向它提供指令和数据- 指令和数据在存储器中存放,也就是平时所说的内存
- 在一台计算机中内存的作用仅次于
CPU
- 离开了内存,性能再好的
CPU
也无法工作- 磁盘不同于内存,磁盘上的数据或程序如果不读到内存,就无法被
CPU
使用
1.5.指令和数据
- 指令和数据是应用上的概念
- 在内存或磁盘上,指令和数据没有任何区别,都是二进制信息
1.6.存储单元
- 存储器被划分为若干个存储单元,每个存储单元从零开始顺序编号
1.7.CPU
对存储器的读写
必须和外部器件/芯片进行三类信息的交互
- 存储单元的地址 – 地址信息
- 器件的选择,读过些命令 – 控制信息
- 读或写的数据 – 数据信息
- 物理上:一根根导线的集合
- 逻辑上:
- 地址总线
- 数据总线
- 控制总线
1.8.地址总线
CPU
是通过地址总线来指定存储单元- 地址总线上能传送多少个不同的信息,
CPU
就可以对多少个存储单元进行寻址
1.9.数据总线
CPU
与内存或其他器件之间的数据传送是通过数据总线来进行的- 数据总线的宽度决定了
CPU
和外界的数据传送速度
二、寄存器 – CPU
工作原理
2.1.CPU
概述
- 一个典型的
CPU
由运算器、控制器、寄存器等器件组成,这些器件靠内部总线相连。- 内部总线 – 实现
CPU
内部各个器件之间的联系- 外部总线 – 实现
CPU
和主板上其他器件的联系
寄存器
AX
、BX
、CX
、DX
、SI
、DI
、SP
、BP
、IP
、CS
、SS
、DS
、ES
、PSW
2.2.通用寄存器
8086
CPU
由14个寄存器,所有寄存器都是16位的
AX
、BX
、CX
、DX
– 通常用来存放一般性数据,被称为通用寄存器
2.3.字在寄存器中的存储
一个字可以存在一个16位的寄存器中,这个字的高位字节和地位字节自然就存在这个寄存器的高8位寄存器和低8位寄存器中
2.4.几条汇编指令
不区分大小写
- mov A, B – 将
B
的值赋给A
- add A, B – 将
A
和B
相加后存放在A
中
ADD 会影响 CF 标志寄存器的值
2.5.物理地址
CPU
访问内存单元时要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间
2.6.16位结构的CPU
- 运算器一次最多可以处理16位的数据
- 寄存器的最大宽度为16位
- 寄存器和运算器之间的通路是16位的
- 8086有20位的地址总线,可传送20位地址,寻址能力为1M
- 8086内部为16位结构,他只能传送16位的地址,表现出的寻址能力却只有64K
- 8086
CPU
采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址
- 物理地址 = 段地址 × 16 + 偏移地址
2.7.段的概念
- 错误认识 – 内存被划分成了一个一个的段,每一个段有一个段地址
- 其实 – 内存并没有分段,段的划分来自于
CPU
,由于8086CPU
用段地址 × 16 + 偏移地址 = 物理地址的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。- 在编程时可以根据需求,将若干个地址连续的内存单元看作一个段,用段地址 × 16定位段的起始地址,用偏移地址定位段中内存单元。
- 段地址 × 16必然是16的倍数,所以一个段的起始地址也一定是16的倍数
- 偏移地址为16位,16位地址的寻址能力为64K,所以一个段的长度最大为64K
CPU
可以用不同的段地址和偏移地址形成同一个物理地址
2.8.段寄存器
- 段寄存器就是提供段地址的
- 8086
CPU
有4个段寄存器
CS
–Code-Segment
DS
–Data-Segment
SS
–Stack-Segment
ES
–Extra-Segment
- 当8086
CPU
要访问内存时,由这4个段寄存器提供内存单元的段地址
2.9.CS
和IP
寄存器
- 是8086
CPU
中最关键的寄存器,他们指示了CPU
当前要读取指令的地址
CS
– 代码段寄存器IP
– 指令指针寄存器- 8086
CPU
工作过程的简要描述
- 从
CS:IP
只想内存单元读取指令,读取的指令进入指令缓冲器IP = IP + 所读取指令的长度
,从而指向下一条指令- 执行指令
- 转回第一步并重复过程
- 在8086
CPU
加点启动或复位后,CS
和IP
被设置为CS = FFFFH
,IP = 0000H
- 即,在启动时,
CPU
从内存FFFF0H
单元中读取指令执行FFFF0H
单元中的指令是8086PC机
开机后执行的第一条指令
2.10.修改CS
、IP
的指令
- 转移指令
- 同时修改 –
jmp
段地址 : 偏移地址- 只改
IP
–jmp
偏移地址
2.11.Debug
的使用
- -R – 查看寄存器的内容
- -R 寄存器 – 查看指定寄存器的内容,并可进行修改
- -D – 查看内存中的内容
- -E – 改写内存中的内容
- -U 段地址 : 偏移地址 – 将内存中的机器指令翻译成汇编指令
- -T – 执行一条机器指令
- -A –以汇编指令的格式在内存中写入机器指令
几个小操作
- -D FFF0:0 FF
- 可以查看到生产日期
- -E B810:0 ASCII
- B810 是显卡地址,通过输入
ASCII
码可以输出字符
三、寄存器 – 内存访问
3.1.DS
和[Address]
CPU
要读取一个内存单元的时候,必须先给出这个内存单元的地址- 在8086
PC
中,内存地址由段地址和偏移地址组成- 8086
CPU
中由一个DS
寄存器,通常用来存放要访问的数据的段地址DS
不能直接赋值,需要经由另一个寄存器
3.2.数据段
对于8086
PC机
,我们可以根据需要将一组内存单元定义为一个段(可以是代码段、数据段)可以将一组长度为
N(N<=64K)
、地址连续且起始地址为16倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段
3.3.栈
8086
CPU
的入栈/出栈操作都是以字为单位进行的两个基本操作:
Push
– 入栈,将一个新的元素放到栈顶Pop
– 出栈,从栈顶取出一个元素两个寄存器:
SS
– 段寄存器,存放栈顶的段地址SP
– 存放栈顶的偏移地址
Push
和Pop
等有关栈的操作指令,修改的只是SP
。也就是说,栈顶的变化最大范围为:0 ~ FFFFH
。
3.4.栈段
可将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。
将段地址放在
SS
中,将栈顶单元的偏移地址放在SP
中,这样CPU
在需要进行栈操作的时候,比如执行Push
、Pop
指令等,就将我们定义的栈段当作栈空间来用
四、编译与连接
4.1.可执行文件
- 程序 – 从源程序中的汇编指令翻译过来的机器码
- 数据 – 源程序中定义的数据
- 相关描述信息 – 如程序大小、所占内存空间等
4.2.程序的完成过程
- 编写
- 编译
- 连接
- 执行
4.3.几种指令
- 汇编指令 – 有对应的机器码的指令,可以被编译为机器指令,最终被
CPU
执行- 伪指令 – 没有对应的机器码的指令,最终不被
CPU
执行。由编译器翻译成汇编指令
4.4.定义一个段
segment
和codes
是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令
- 一个汇编程序由多个段组成,这些段被用来存放代码、数据或当作栈空间来使用
- 一个有意义的汇编程序中至少要有一个代码段
4.5.汇编代码的首尾
- assume – 将某一段寄存器和程序中的某一个段关联起来
- end – 是一个汇编程序结束的标记
4.6.标号
- 一个标号指代了一个地址
- 放在
segment
前面,作为一个段的名称,这个段的名称最终将被编译、连接程序处理为一个段的段地址
4.7.DOS
中的程序运行
Dos
是一个单任务操作系统我们可以将源程序文件中的所有内容称为源程序,将源程序中最终由计算机执行处理的指令或数据称为程序。程序最先以汇编指令的形式存在于源程序中,经编译、连接后转变为机器码,存储在可执行文件中。
4.8.程序的返回
1
2 MOV AX,4c00H
INT 21H
4.9.编译
将伪指令和汇编指令转换为机器码
4.10.连接
- 当源程序很大时,可以将它分为多个源程序文件来进行编译,每个源程序编译成为目标文件后,再用连接将他们整合在一起,生成可执行文件
- 程序中调用了某个库文件中的子程序,需要将这个库文件和该程序生成的目标文件连接到一起,生成一个可执行文件
- 一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以在只有一个源程序文件,又不需要调用时也必须用连接程序对目标文件进行处理,生成可执行文件
4.11.exe
文件在Dos
中的运行
- 是
Command
将exe
文件载入内存Command
设置CPU
的CS:IP
指向程序的第一条指令(即程序的入口),从而使程序得以运行- 程序运行结束后,返回
Command
中,CPU
继续执行Command
Debug
输入
debug fileName.exe
即可将文件载入,载入完成后cx
中放的是程序的长度加载过程:
- 找到一段起始地址为
SA 0000
的容量足够的空闲内存区- 在这段空间的前
256
个字节中创建一个称为程序的前缀(PSP
)的数据区,Dos
要利用PSP
来和加载程序进行通信- 从这段内容区的
256
字节开始(PSP
)后面,将程序装入,程序的地址被设为SA + 10H : 0
- 将该内存区的段地址存入
DS
,初始化其他相关寄存器后,设置CS : IP
指向程序的入口
五、[BX]
和loop
5.1.[BX]
- 将偏移值放入
BX
- 通过
[BX]
获取偏移值的内容
5.2.Loop
- 在
CX
中存放循环次数loop
指令中的标号所表示地址要在前面
六、包含多个段的程序
6.1.DW
Define Word
– 定义字型数据,每个数据2B
6.2.DB
Define Byte
– 定义字节数据,1B
6.3.End
CPU
会先去找程序中的End
,根据End
后面的标号找到程序的开头
6.4.多个段
8086CPU
一个段 64KB
- 通过
assume cs : code, ds: data, ss: stack
将cs
、ds
和ss
分别和code
、data
、stack
段相连后,CPU
并不会指向这些段。
- 因为
assume
只是伪指令,是让编译器读的,要在汇编完成之后才真正能指向
七、更灵活的使用内存空间
7.1.AND
按位与指令
7.2.OR
按位或命令
7.3.ASCII
码
是美国的一种编码方式
7.4.SI
和DI
寄存器
和
Ax
、Bx
、Cx
、Dx
是一样的,只是不能拆成高低一般,
SI
作为数据段的起始地址,DI
作为目标地址
八、数据处理的两个基本问题
8.1.BX
、SI
、DI
、BP
在
8086 CPU
中,只有这四个可以放在中括号里,而且若是组合放在中括号,只能如下
[BX和SI]
[BX和DI]
[BP和SI]
[BP和DI]
以下组合是错误的
[BX和BP]
[DI和SI]
BP
默认是在SS
中的
BX
默认是在DS
中的
8.2.机器指令处理的数据所在所致
- 读取
- 写入
- 运算
对机器指令来讲,不关心数据的值是多少,而是关心指令执行前一刻,它将要处理的数据所在位置
有三个概念用来表达数据的位置
- 立即数 – 对于直接包含在机器指令中的数据,称为立即数,在汇编指令中直接给出,存在于
CPU
指令缓冲区- 寄存器 – 指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名
- 段地址和偏移地址
8.3.寻址方式
- 直接寻址
- – [常数]
- 寄存器间接寻址
- – [寄存器]
- 寄存器相对寻址
- – [寄存器 + 常数]
- 基址变址寻址
- – [寄存器+寄存器
- 相对基址变址寻址
- – [寄存器+寄存器+常数]
8.4.处理的数据的长度
word ptr
指明了指令访问的内存单元是一个字的单元2B
byte ptr
指明了指令访问的内存单元是一个字节的单位1B
8.5.DIV
指令
DX
– 存放被除数的高 16位,除完后,存放商
AX
– 存放被除数的低 16位,除完后,存放余数
AL
– 除完后,存放商
AH
– 除完后,存放余数
8.6.DD
指令
double word
双字,4字节做除法运算时,应把前两个(高)字节放在
AX
,后两个(低)字节放在DX
8.7.DUP
指令
伪指令
db 3 dup (2)
–db 0, 0, 0
db 3 dup (0, 1, 2)
–db 0, 1, 2, 0, 1, 2, 0, 1, 2
db 3 dup ('abc', 'ABC')
–db 'abcABCabcABCabcABC'
九、转移指令
- 无条件转移指令
- 条件转移指令
- 循环指令
- 过程
- 中断指令
9.1.offset
取得标号得到偏移地址
1
2 start: mov ax, offset start ; 相当于 mov ax, 0
s: mov ax, offset s ; 相当于 mov ax, 3
9.2.jmp
需要给出以下某种信息
- 转移的目的地址
- 转移的距离(段间转移、段内短转移、段内进转移)
CPU
不需要目的地址就可以实现对IP
的修改
jmp short
对IP
的修改范围为-128~127
,即向前最多128个字节,向后最多127个字节jmp
实现近转移,通过位移进行跳转
9.3.jmp far ptr
CS
- 标号所在段的段地址IP
- 标号所在段中的偏移地址far ptr
指明了指令用标号的段地址和偏移地址修改CS
和IP
十、call
和 ret
10.1.call
10.2.ret
10.3.call far ptr
相当于
1 | push CS |
10.4.转移地址在寄存器中的 call
10.4.1 - call word ptr
1 | push IP |
10.4.2 - call dword ptr
十一、标志寄存器
8086CPU
的标志寄存器有16
位,其中存储信息通常被称为程序状态字PSW
15 | 14 | 13 | 12 | 11 | 10 | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
OF | DF | IF | TF | SF | ZF | AF | PF | CF |
11.1.ZF
第6位是ZF,零标志位。它记录相关指令执行后,其结果是否为0。如果(真),结果为0,那么ZF=1;如果(假),结果非0,那么ZF=0。
ZR
– 值为 1
NZ
– 值为 0
11.2.PF
奇偶标志位寄存器,结果中有奇数个 1 时
PF
为 0,偶数个 1 时PF
为 1
PE
– 值为 1PO
– 值为 0
11.3.SF
记录相关指令执行后,其结果是否为负,如果(真),结果为负,
SF=1
,如果(假),结果非负,SF=0
。
NG
值为 1PL
值为 0
11.4.CF
在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。
对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N-1位,就是它的最高有效位,而假想存在的第N位,就是相对于最高有效位的更高位
CY` – 值为 1
NC
– 值为 0
11.5.OF
溢出标志位。一般情况下,OF记录了有符号数运算的结果是否发生了溢出,如果(真),发生了溢出,OF=1,如果(假),没有发生溢出,0F=0。
OV
– 值为 1
NV
– 值为 0
11.6.ADC
Adc
是带进位加法指令,它利用了CF位上记录的进位值。
11.7.SBB
Sbb
是带借位减法指令,它利用了CF位上记录的借位值。
11.8.CMP
Cmp
是比较指令,它的功能相当于sub
指令,只是不保存结果。Cmp
指令执行后,将对标志寄存器产生影响,其它相关指令通过识别这些被影响的标志位来得知比较结果。JE
– 相等跳转JNE
– 不相等跳转JB
– 小于跳转JNB
– 不小于跳转JA
– 大于跳转JNA
– 不大于跳转
11.9.检测比较结果的条件转移指令
11.10.DF
标志和串传送指令
- 方向标志位。在串处理指令中,控制每次操作后
SI
、DI
的增减。DF=0
,每次操作后SI
、DI
递增;DF=1
,每次操作后SI
、DI
递减。DF
标志位与串传送指令(movsb、movsw)有关,而串传送指令与游戏修改无关,所以就不讲解了。MOVSB
– 以字节为单位传送,将DS:SI
指向的内存单元中的字节送入ES:DI
中,然后根据标志寄存器DF
的值,将SI
和DI
递增或者递减MOVSW
– 以字为单位传送,将DS:SI
指向的内存字单元中word
送入es:di
中,然后根据标志寄存器DF
位的值,将si
和di
递增 2 或递减 2CLD
– 将DF
设置为 0STD
– 将DF
设置为 1
11.11.Pushf
和 Popf
11.11.1.Pushf
- 将标志寄存器的值压入栈
11.11.2.Popf
- 从栈中弹出数据,送入标志寄存器
11.12.TF
- 跟踪标志位。用于程序调试。如果
TF=1
,则CPU
处于单步执行指令的工作方式,此时,每执行完一条指令,就显示CPU
各个寄存器的当前值及CPU
将要执行的下一条指令。如果TF=0
,则处于连续工作模式。
11.13.AF
- 辅助进位标志位。在下列情况下,AF的值被设置为1,否则其值为0。
- 在字操作时,发生低字节向高字节进位或借位时。
- 在字节操作时,发生低4位向高4位进位或借位时。
11.14.IF
- 中断允许标志位。用来决定
CPU
是否响应CPU
外部的可屏蔽中断发出的中断请求,当IF=1
,响应中断请求,当IF=0
,不响应中断请求。
11.15.LEA
和 NOP
11.15.1.LEA
Lea
为有效地址传送指令。- 将源操作数给出的有效地址传送到指定的寄存器中。
- 格式:
lea
操作对象1,操作对象2
LEA AX, [217A]
11.15.2.NOP
- 本指令不产生任何结果,仅消耗几个时钟周期的时间,接着执行后续指令,常用于程序的延时等。
十二、内中断
12.1.内中断的产生
- 中断是
CPU
处理外部突发事件的一个重要技术- 能够使
CPU
在运行过程中对外部事件发出的中断请求及时的处理,处理完成后又立即返回断点,继续进行原来的工作- 引起中断的原因或者说发出中断请求的来源叫中断源。根据中断源的不同,可以把中断分为硬件中断和软件中断两大类,而硬件中断又可以分为外中断和内中断两类
- 中断优先级
- 除法错误、溢出中断、软件中断
- 不可屏蔽中断
- 可屏蔽中断
- 单步中断
12.2.内中断处理程序
CPU
的设计者必须在中断信息和其处理程序的入口地址之间建立某种联系,使得CPU
根据中断信息可以找到要执行的处理程序- 中断信息中包含有标识中断源的类型码。根据
CPU
的设计,中断类型码的作用就是用来定位中断处理程序- 若要定位中断处理程序,需要知道他的段地址和偏移地址,而如何根据 8 位的中断类型码得到中断处理程序的段地址和偏移地址就要引入中断向量表
12.3.中断向量表
CPU
用 8 位的中断类型码,通过中断向量表找到相应的中断处理程序的入口地址- 是一个中断向量的索引
- 中断向量表在内存中保存,其中存放着 256( 28 ) 个中断源所对应的中断处理程序的入口
- 中断向量表指定存放在内存地址 0 处
- 从内存 0000 : 0000 到 0000 : 03FF 的 1024 个单元中存放着中断向量表
12.4.中断过程
- 找到这个入口地址的最终目的是用它设置
CS
和IP
,使CPU
执行中断处理程序- 用中断类型码找到中断向量,并用它设置
CS
和IP
,这个工作是由CPU
硬件自动完成的CPU
硬件完成这个工作的过程被称为中断过程
- 取得中断类型码
- 标志寄存器的值入栈(保护标志位)
- 设置标志寄存器的第 8 位
TF
和 第 9 位IF
的值为 0CS
的内容入栈IP
的内容入栈- 从内存地址为中断类型码 * 4 和中断类型码 * 4 + 2 的两个字单元中读取中断处理程序的入口地址设置
IP
和CS
12.5.中断处理程序
- 由于
CPU
随时都可能检测到中断信息,也就是说,CPU
随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存某段空间之中- 而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中
- 中断处理程序的编写方法和子程序的比较相似
- 保存用到的寄存器
- 处理中断
- 回复用到的寄存器
- 用
iret
指令返回
12.6.除法错误中断的处理
- 当
CPU
执行DIV
等除法指令时,如果发生了除法溢出错误,将产生中断类型码为 0 的中断信息,CPU
将检测到这个信息,然后引发中断过程,转去执行 0 号中断所对应的中断处理程序
12.7.编程处理 0 号中断
- 当发生除法溢出的时候,产生 0 号中断信息,从而引发中断过程
- 取得中断类型码 0
- 标志寄存器入栈,
TF
、IF
设置为 0CS
、IP
入栈(IP)=(0*4)
,(CS)=(0*4+2)
- 自己编写一段代码,自定义命名,这里为
do0
,因为CPU
随时可能将CS:IP
指向这个的入口,所以这个程序应该写在内存中
- 由于我们实在操作系统之上使用计算机,所有的硬件资源都在操作系统的管理之下,所以我们想要得到一块内存存放
do0
,应该向操作系统申请,不过此处会直接将程序直接放入安全地址0000:0200
到0000:02FF
中- 把程序的入口放入中断向向量表
- 因为除法溢出对应的中断类型码为 0,它的中断处理程序的入口地址应该从 04 地址单元开始存放,段地址存放在 0\4+2 字单元中,偏移地址存放在 0*4 字单元中
十三、INT
指令
- 可以在程序中使用
int
指令调用任何一个中断的中断处理程序
13.1.BIOS
和 DOS
中断的安装过程
- 电脑加点启动,初始化
CS
= 0FFFFH,IP
= 0,自动从 FFFF:0 单元开始执行程序。FFFF:0 处有一条跳转指令,CPU
执行该指令后,转去执行BIOS
中的硬件系统检测和初始化程序- 初始化程序将建立
BIOS
所支持的中断向量,即将BIOS
提供的中断例程的入口地址登记在中断向量表中- 硬件系统检测和初始化完成后,调用
int 19H
进行操作系统的引导。从此将计算机交由操作系统控制DOS
启动后,除完成其他工作外,还将它所提供的中断李晨装入内存,并建立相应的中断向量
十四、端口的读写
in
out
14.1.SHL
- 逻辑左移
- 将一个寄存器或者内存单元中的数据向左移位
- 将最后移除的一位写入
CF
14.2.SHR
- 逻辑右移
14.3.CMOS RAM
中存储的时间信息
- 秒 – 00H
- 分 – 02H
- 时 – 04H
- 日 – 07H
- 月 – 08H
- 年 – 09H
- 使用
BCD
进行表示CMOS
的端口是70H
,存放这CMOS RAM
单元的地址71H
为数据端口,存放从选定的CMOS RAM
单元中读取的数据,或要写入到其中的数据