学到的知识,很大的一部分会被忘却,而被忘记的知识的影子却保护你避免陷入很多的错觉。——伊顿公学校长威廉·考利
为什么要学习汇编语言?
汇编语言是很多相关课程的重要基础,比如:操作系统、接口技术等。它是底层编程语言,是计算机系统提供给用户最快最有效的语言,也是能对硬件直接编程的语言。因此,对空间和时间要求很高的程序,或需要直接控制硬件的程序,必须使用汇编语言进行程序设计。
汇编语言的特点
- 面向机器的低级语言,与处理器密切相关。通常是为特定的计算机或计算机系列专门设计的。
- 保持了机器语言的优点,具有直接和简捷的特点。
- 可有效地访问、控制计算机的各种硬件设备, 如磁盘、存储器、CPU、I/O端口等。
- 目标代码简短,占用内存少,执行速度快,是高效的程序设计语言。
- 经常与高级语言配合使用,应用十分广泛。
汇编语言的主要应用场合
- 要求执行效率高、反应快的领域,如:操作系统内核,实时系统等;
- 与硬件资源密切相关的软件开发,如:设备驱动程序等;
- 大型系统性能的瓶颈,或频繁被使用的子程序或程序段;高级绘图程序、视频游戏程序一般是用汇编语言编写的。
- 受存储容量限制的应用领域,如:家用电器的计算机控制功能等。
学习资源
BCD码
BCD码(Binary Coded Decimal)是一种二-十进制的编码,它使用4位二进制数表示一位十进制数。最常用的BCD码是8421码,又叫NBCD码(Natural Binary Coded Decimal Code),由于4位二进制数可表示16种状态,只取前10种状态0000-1001来表示十进制数码0-9,从左到右每位二进制数的权分别是8、4、2、1,因此又叫8421码。
例如:十进制数1258对应的BCD码是0001001001011000
;
反之,BCD码1001 1000 0111 0010
对应的十进制数是9872。
压缩BCD码:用一个字节表达两位BCD码,比如 0100 1001
表示49
。
非压缩BCD码:将8位二进制的高4位设置为0,仅用低4位表达一位BCD码。比如00000100 00001001
表示49
。
80x86微处理器
80X86是美国Intel公司生产的微处理器系列。
微处理器:把运算器和控制器集成在一个芯片上,构成的中央处理器(CPU)。
微机:即微计算机系统,由微处理器配上存储器、输入输出设备和系统软件等构成。各硬件用系统总线连接在一起。
系统总线包括数据总线(DB)、地址总线(AB)和控制总线(CB)三组。
基于微处理器的计算机系统构成
数据总线宽度
数据总线宽度16位:决定了数据的传输速率。
机器字长16位
机器字长为16位:可表示数的范围为0000~FFFFH(2^16 = 64K)
地址总线宽度
地址总线宽度为20位:寻址空间为2^20 = 1024KB = 1MB
中央处理器
CPU包含了三个部分:算术逻辑、控制逻辑、寄存器组。
总线接口部件BIU (Bus Interface Unit)
内部设有四个段地址寄存器,一个指令指针寄存器IP,一个6字节指令队列缓冲器,20位地址加法器和总线控制电路。
主要功能:根据执行部件EU的请求,负责从内存单元中预取指令,并将它们送到指令队列缓冲器暂存。即负责完成CPU与存储器或I/O设备之间的数据传送。
执行部件EU(Execution Unit)
执行部件中包含算术逻辑单元(ALU)、通用寄存器、状态标志寄存器、数据暂存寄存器和执行部件的控制电路。
主要功能:从BIU的指令队列中取出指令代码,经指令译码器译码后执行指令所规定的全部功能。执行指令所得结果或执行指令所需的数据,都由EU向BIU发出命令,对存储器或I/O接口进行读/写操作。
寄存器组
数据寄存器
名称 | 寄存器英文名 | 寄存器说明 |
---|---|---|
AX | Accumulator | 累加寄存器,常用于运算;在乘除等指令中指定用来存放操作数,另外,所有的I/O指令都使用这一寄存器与外界设备传送数据。 |
BX | Base | 基址寄存器,常用于存放存储单元地址。 |
CX | Count | 计数寄存器,一般作为循环或串操作等指令中的隐含计数器;常用于保存计算值,如在移位指令,循环(loop)和串处理指令中用作隐含的计数器。 |
DX | Count | 数据寄存器,常用来存放双字数据的高16位,或存放外设端口地址,比如双字的乘除法。 |
共同特点:
这四个十六位的寄存器可以分为:
- 高8位:AH,BH, CH,DH;
-
低8位:AL,BL,CL,DL。
- 这两组八位寄存器可以分别寻址,并单独使用。
指针寄存器
寄存器名称 | 寄存器英文名 | 寄存器说明 |
---|---|---|
SP | Stack Pointer | 堆栈指针寄存器。与SS(堆栈段寄存器)配合使用来确定堆栈段栈顶的位置,也就是说SP用于存放栈顶的偏移地址。 |
BP | Base Pointer | 基址指针寄存器,可用作SS的一个相对基址位置(用于存放堆栈段中某一存储单元的偏移地址。) |
说明:指针寄存器和变址寄存器只能按16位进行存取操作,主要用来形成操作数的地址,用于堆栈操作和变址运算中计算操作数的有效地址。
变址寄存器
寄存器名称 | 寄存器英文名 | 寄存器说明 |
---|---|---|
SI | Source Index | 源变址寄存器可用来存放相对于DS段之源变址指针 |
DI | Destination Index | 目的变址寄存器,可用来存放相对于 ES 段之目的变址指针 |
说明:
SI和DI一般与数据段寄存器DS联合使用,用来确定数据段中某一存储单元的地址。这两个变址寄存器有自动增量和自动减量的功能,所以用来变址是十分方便的。
在串处理中,SI和DI作为隐含的源变址和目的变址寄存器,此时SI和DI联用,DI和附加段寄存器ES联用,分别达到了在数据段和附加段寻址的目的。
指针寄存器和变址寄存器只能按16位进行存取操作,主要用来形成操作数的地址,用于堆栈操作和变址运算中计算操作数的有效地址。
指令指针IP(Instruction Pointer)
指令指针IP是一个16位专用寄存器(指令指针寄存器),它指向当前需要取出的指令字节,当BIU从内存中取出一个指令字节后,IP就自动加上指令长度的值,指向下一个指令字节。
注意:IP指向的是指令地址的段内地址偏移量,又称偏移地址(Offset Address)或有效地址(EA,Effective Address),程序不能直接访问IP。
任意时刻,CPU将CS:IP指向的内存单元中的内容看作指令。
标志寄存器FR(Flag Register)
8086有一个18位的标志寄存器FR,在FR中有意义的有9位,其中6位是状态位,3位是控制位。
条件标志
状态信息是由中央处理机根据计算结果自动设置的。
标志名称 | 说明 |
---|---|
OF |
溢出标志位OF(overflow flag)用于反映有符号数加减运算所得结果是否溢出。 如果运算结果超过当前运算位数所能表示的范围,则称为溢出。溢出时OF=1,否则OF= 0。 注意,这里所讲的溢出,只是对有符号数运算而言,对无符号数没有意义。 |
SF |
符号标志SF(sign flag)用来反映运算结果的符号位,它与运算结果的最高位相同。在微机系统中,有符号数采用补码表示法,所以,SF也就反映运算结果的正负号。正数SF=0,负数SF为1。 注意:SF是对有符号数运算有意义的标志位 |
ZF | 零标志ZF(zero flag)用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0。在判断运算结果是否为0时,可使用此标志位。 |
AF | 辅助进位标志AF(auxiliary flag)记录运算时第3位(字节运算)或第7位(字运算)产生的进位或借位值。例如,执行加法指令时第3位有进位时AF=1,否则AF=0。 |
PF | 奇偶标志PF(parity flag)用于反映运算结果中“1”的个数的奇偶性。当结果操作数中1的个数为偶数时PF=l,否则PF=0。 |
CF |
进位标志CF(carry flag)主要用来反映运算是否产生进位或借位。当最高有效位有进位或借位时CF=1,否则置CF=0。 注意:CF是对无符号数运算有意义的标志位 |
无符号数比较示例:
控制标志
控制标志是系统程序或用户程序根据需要用指令设置的。
标志名称 | 说明 |
---|---|
DF | 方向标志DF(direction flag)位用来决定在串操作指令执行时有关指针寄存器发生调整的方向。当DF位为1时,每次操作后使变址寄存器SI和DI减量;当DF为0时,则使SI和DI增量。 |
IF |
中断允许标志IF(interrupt flag)位用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求。但不管该标志为何值,CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求,以及CPU内部产生的中断请求。具体规定如下: (1)当IF=1时,CPU可以响应CPU外部的可屏蔽中断发出的中断请求; (2)当IF=0时,CPU不响应CPU外部的可屏蔽中断发出的中断请求。 总的来说:IF为1时,开中断,否则关中断。 |
TF |
跟踪标志TF(trap flag), 也叫做陷阱标志。该标志可用于程序调试。TF标志没有专门的指令来设置或清楚。 (1)如果TF=1,则CPU处于单步执行指令的工作方式,此时每执行完一条指令,就显示CPU内各个寄存器的当前值及CPU将要执行的下一条指令。 (2)如果TF=0,则处于连续工作模式。 总的来说:TF=1时,每条指令执行完后产生陷井,TF=0时,CPU正常工作不产生陷井。 |
Debug中标志位的符号表示
标志 | 1 | 0 |
---|---|---|
OF | OV | NV |
DF | DN | UP |
IF | EI | DI |
SF | NG | PL |
ZF | ZR | NZ |
AF | AC | NA |
PF | PE | PO |
CF | CY | NC |
标志寄存器FLAGS
这是一个存放条件码标志、控制标志和系统标志的寄存器。
段寄存器(Segment Register)
为了运用所有的内存空间,8086设定了四个段寄存器,专门用来保存段地址:
名称 | 全称 | 说明 |
---|---|---|
CS(Code Segment) | 代码段寄存器 | 代码段存放当前正在运行的程序 |
DS(Data Segment) | 数据段寄存器 | 数据段存放当前正在运行程序所有的数据,如果程序使用了串处理指令,则其操作数也会存放在数据段中。 处理串的时候,DS默认为源串。 |
SS(Stack Segment) | 堆栈段寄存器 | 定义了堆栈所在的区域。 |
ES(Extra Segment) | 附加段寄存器 | 这是一个辅助的数据区,也是串处理指令的目的操作数存放区。 处理串的时候,ES默认为目的串。 |
附录
存储器
存储器是计算机的记忆部件,用来存放程序和数据。按所在的位置,存储器可以分成主存储器和辅助存储器。
主存储器存放当前正在执行的程序和使用的数据,CPU可以直接存取,它由半导体存储器芯片构成,其成本高,容量小,但速度快。
辅助存储器可用来长期保存大量程序和数据,CPU需要通过I/O接口访问,它由磁盘或光盘构成,其成本低,容量大,但速度较慢。
存储单元
存储器被划分为若干个存储单元,每个存储单元有一个惟一的存储器地址,从0开始顺序编号,存储单元的地址是无符号数, n位二进制数共能表示2^n个存储单元的地址。
类型
Name | 全称 | 说明 |
---|---|---|
DB | define byte | 定义字节类型变量,一个字节数据占1个字节单元,读完一个,偏移量加1 |
BW | define word | 定义字类型变量,一个字数据占2个字节单元,读完一个,偏移量加2 |
DD | define double(word) | 定义双字类型变量,一个双字数据占4个字节单元,读完一个,偏移量加4 |
字地址
字地址:一个字存放到存储器要占用连续的两个字节单元。字的低字节(低8位)存放在低地址中,高字节(高8位)存放在高地址中,字单元的地址用低地址表示。
例如:34560H的字单元的内容是1234H,而地址为78780H时字单元的内容是3332H。记作:
- (34560H)=1234H
-
(78780H)= 3332H
同一个地址既可以看成字节单元,也可以看作是字单元、双字单元、或者是4字单元的地址,这要根据使用情况而定。字单元地址可以是偶数,也可以是奇数。但是,在8086和80286中,访问存储器(要求取数或者存数)都是以字为单元进行的,也就是说,机器是以偶地址访问存储器的。这样,对于奇地址的字单元,要取一个字需要访问二次存储器,当然,这样做需要花费较多的时间。
存储器有这样一个特性:它的内容取之不尽的。也就是说,从某个单元取出其内容后,该单元仍然保持着原来的内容不变,可以重复取出,只有存入新的信息后,原来保存的内容就自动丢失了。
各类存储器芯片
从读写属性上看分为两类:
- 随机存储器(RAM)和只读存储器(ROM)
- 从功能和连接上分类:
- 随机存储器RAM
- 装有BIOS的ROM
- 接口卡上的RAM
所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器;每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间;CPU在这段地址空间中读写数据,实际上就是在相对应的物理存储器中读写数据。
寄存器与存储器的比较
实模式存储器寻址
8086CPU的地址线是20位的,这样最大可寻址空间应为220=1MB,其物理地址范围从00000H~FFFFFH。
而8086CPU寄存器都是16位的,仅能表示地址范围 0000H ~ FFFFH(64KB) 。那么,这1MB(2^20)空间如何用16位寄存器表达呢?
根据要求可把1M字节地址空间划成若干逻辑段。每个逻辑段必须满足两个条件:
(1)逻辑段的起始地址(简称段首址)必须是16的倍数;
(2)逻辑段的最大长度为64K(2^6)。
按照这两个条件,1M字节地址空间最多可划分成64K个逻辑段,最少也要划分成16个逻辑段。逻辑段与逻辑段可以相连,也可以不连,还可以重叠。
逻辑地址(LA)和物理地址(PA)
物理地址
就是存储器的实际地址,它是指CPU和存储器进行数据交换时所使用的地址(20位)。
逻辑地址
是在程序中使用的地址(16位) ,它由段地址和偏移地址两部分组成。逻辑地址的表示形式为“段地址∶偏移地址”。
- 段地址:是指逻辑段在主存中的起始位置。
- 段内偏移地址:是指主存单元距离段首址的偏移量,简称偏移地址,用EA来表示,由于限定每段不超过64KB,所以偏移地址可以用16位数据表示。
-
物理地址形成:物理地址用PA表示, 将逻辑地址中的段地址左移4位,加上偏移地址就得到20位物理地址。
- 物理地址 = 段地址 x 16 + 偏移地址
段的概念
错误认识:
内存被划分成了一个一个的段,每一个段有一个段地址。
正确认识:
内存并没有分段,段的划分来自于CPU,由于8086CPU用“(段地址×16)+偏移地址=物理地址”的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。
8086PC工作过程的简要描述
- 1)从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器;
- 2)IP = IP + 所读取指令的长度,从而指向下一条指令;
- 3)执行指令。 转到步骤 (1),重复这个过程。
- 在 8086CPU 加电启动或复位后( 即 CPU刚开始工作时)CS和IP被设置为CS=FFFFH,IP=0000H,即在8086PC机刚启动时,CPU从内存FFFF0H单元中读取指令执行,FFFF0H单元中的指令是8086PC机开机后执行的第一条指令。
汇编指令
指令格式
- ASMARM的专栏 - 浅谈ARM 汇编中的标号(Labels)
1、双操作数指令(二地址指令)
- 格式:[标号:] 操作符 DST,SRC [;注释]
- 操作规定:
- 1)DST与SRC应为同种操作类型且类型明确,即同为字节类型或字类型。
- 2)DST不能是立即数。
- 3)SRC和DST不能同时为存储器操作数(mem) 。
- 4)操作结束后,运算结果存入DST中,SRC内容不变。
- 例如:
MOV AX,BX ;将BX寄存器中的16位数送到AX寄存器。
MOV AX,BL ;这句话是错误的
2、单操作数指令(一地址指令)
- 格式:[标号:] 操作符 DST [;注释]
- 操作规定:
- 1)DST类型必须明确即为字节类型或字类型,不能是模糊类型
- 2)操作对象为目的操作数,操作结束后结果存入DST中
- 3)DST不能是立即数,只能是寄存器操作数(reg) 或存储器操作数(mem)。
DEC CX ;将计数器CX的内容减1 。
3、无操作数指令(零地址指令)
- 格式:[标号:] 操作符 [;注释]
- 操作规定:指令中只有操作码,不含操作数,这种指令有两种可能:
- 1)无需任何操作数。如停机指令(HLT)、空操作指令(NOP)等。
- 2)所需操作数是隐含指定的,操作时取固定操作数进行操作。如进位位置0(CLC)、方向标志置0(CLD)。
寻址方式
定义:寻址方式是指寻找指令中操作数所在地址的方法。
常用的寻址方式有:立即寻址、直接寻址、寄存器寻址、寄存器间接寻址、变址寻址、基址加变址、隐含寻址等。
寻找指令中所需要操作数存放地址的方式或者程序转移时寻找转移地址的方式称为寻址方式,因而寻址方式有两大类:一类是数据寻址方式,另一类是转移地址寻址方式。
由于80x86指令涉及四种操作数:立即操作数(data)、寄存器操作数(reg)、存储器操作数(mem)和隐含操作数,因此,数据寻址方式又可对应四种寻址方式,即:立即寻址、寄存器寻址、存储器寻址和固定寻址。
数据寻址方式-立即寻址
操作数直接包含在指令中,它紧跟在指令操作码后面,它作为指令存放在存储器代码段中,这种操作数称为立即数。立即数可以是8位,也可以是16位。
MOV AX,1234H ;(AX)=1234H
立即寻址方式用来表示常数,它常用于给寄存器或内存单元赋初值。需要强调的是,立即寻址只能用于源操作数,不能用于目的操作数,且源操作数的长度应该与目的操作数的长度一致。
数据寻址方式-寄存器寻址
操作数直接存放在由指令指明的寄存器中。在汇编指令中直接书写寄存器名,16位寄存器操作数可以是AX、BX、CX、DX、SI、DI、BP、SP、DS、ES、SS、CS等;8位寄存器操作数只能是AH、AL、BH、BL、CH、CL、DH、DL。
指令指针寄存器IP和标志寄存器FLAGS一般不直接出现在程序中。
此寻址方式由于存取操作数直接从CPU内部寄存器中获得,不需访问存储器,因而指令执行的速度快。
寄存器寻址既可用于源操作数,又可用于目的操作数,应用频率最高。
注意:CS不能作为目的操作数,因为CS:IP控制着程序指令序列的执行顺序,不能在程序中由指令随意改变。
数据寻址方式-存储器寻址
存储器寻址方式的操作数都是存放在除代码段以外的存储区中,一般是数据段、附加段、堆栈段中的存储单元。指令中给出的是存储单元的地址或产生存储单元地址的表达式。
在汇编语言源程序中,存储单元地址是采用逻辑地址的形式表示的,即:段首址:段内偏移地址。段首址存放在某个段寄存器中,段内偏移地址即有效地址EA是由3个地址分量的某种组合求得,这3个地址分量是:位移量、基址(BX,BP)、变址(SI,DI) 。
这3个地址分量的不同组合,使形成有效地址EA的方法不同,相应有以下5种不同的存储器操作数寻址方式:直接寻址、寄存器间接寻址、寄存器相对寻址、基址变址寻址、相对基址变址寻址。
直接寻址方式
直接寻址是最简单的存储器寻址,操作数的有效地址EA由指令直接给出,只包含位移量。它主要用于存取简单变量。
MOV AX, [2000H]
EA=2000H, 假设(DS)=3000H, 那么PA=32000H
对使用直接寻址方式需说明以下几点:
- 操作数默认隐含的段为数据段 DS。
- 若操作数在代码段、堆栈段或附加段中,可使用段跨越前缀 。
MOV AX, ES: [2000H]
- 指令中操作数的EA既可以是一个数字地址,也可以是一个符号地址。当EA是一个数字时,一定要注意立即寻址方式与直接寻址方式的区别,直接寻址必须有[ ]符号。
- 操作数地址可由变量(符号地址)表示, 但要注意变量的属性。
VALUE DB 10
MOV AH, VALUE
MOV AX, VALUE
MOV AX, WORD PTR VALUE
直接寻址方式适合于处理存储器的单个存储单元。例如,要处理某个存放在存储器里面的变量,可以使用直接寻址方式把变量先取到一个寄存器中,然后在进一步处理。
80x86中,为了使指令字不要太长,规定双操作数指令的两个操作数中,只能有一个使用存储器寻址方式,这就是一个变量常常先要送到寄存器的原因了。
寄存器间接寻址
操作数的有效地址只包含基址寄存器或者变址寄存器内容的一种成分。因此,有效地址就在某个寄存器中,而操作数则在存储器中。
操作数的有效地址EA存放在SI、DI、BX或BP之一中,而操作数在存储器中。若用BX、SI或DI间接寻址时,则操作数默认在数据段中,用DS的内容作为段首址,操作数的物理地址为:
若指令中使用BP间接寻址时,则用堆栈段SS的内容作为段首址,操作数的物理地址为: PA=(SS)×16 +(BP)。
MOV AX, [BX] ;PA = 16d x (DS) + (BX)
MOV AX, ES:[BX] ;PA = 16d x (ES) + (BX),在这里,ES称为段跨越前缀
MOV AX, [BP] ;PA = 16d x (SS) + (BP)
注意:
- 8086不允许使用AX、CX、DX 来存放 EA,比如:
MOV AX, [CX]
就是错误的。 - SRC 和 DST 的字长一致,自动适应寄存器的长度。
MOV DL, [ BX ] ; [BX]指示一个字节单元
MOV DX, [ BX ] ; [BX]指示一个字单元
- 适用于数组、字符串、表格的处理(可以看成一个数组来使用),执行完一条指令后,只需修改寄存器内容就可以取出下一项处理。
寄存器相对寻址方式
寄存器相对寻址方式也可以称为直接变址寻址方式。
操作数的有效地址EA是指令中指定的基址寄存器或变址寄存器的内容与指令中给出的位移量之和,即
操作数的物理地址为:
若操作数不在默认段中,则应使用段跨越前缀明确指定。
MOV AX, COUNT[SI]
MOV AX, [COUNT+SI]
- 适于数组、字符串、表格的处理。
基址变址寻址
操作数的有效地址EA是指令中的基址寄存器的内容+变址寄存器的内容。
指令格式:
MOV AX, [BX][DI]
等价于
MOV AX, [BX+DI]
MOV AX, ES:[BX][SI]
- 适于数组、字符串、表格的处理
- 必须是一个基址寄存器和一个变址寄存器的组合
相对基址变址寻址
操作数的有效地址EA是指令中的基址寄存器的内容、变址寄存器的内容、位移量三个地址分量之和,即:
相对基址加变址寻址方式有多种等价的书写方式,书写格式:[BX+SI+1000H]、1000H[BX+SI]、1000H[BX][SI]和1000H[SI][BX]
等格式都是正确的,并且其寻址含义也是一致的,但格式:BX[1000H+SI]、SI[1000H+BX]
等是错误的,即所用寄存器不能在”[“,”]”之外,该限制对寄存器相对寻址方式的书写也同样起作用。
MOV AX, MASK [BX] [SI]
或
MOV AX, MASK [BX+SI]
或
MOV AX, [MASK+BX+SI]
这种寻址方式通常用于对二维数组的寻址。例如,存储器中存放着由多个记录组成的文件,则位移量可指向文件之首,基址寄存器指向某个记录,变址寄存器则指向该记录中的一个元素。这种寻址方式也为堆栈处理提供了方便,一般(BP)可指向栈顶,从栈顶到数组的首地址可以使用位移量来表示,变址寄存器可以用来访问数组中的某个元素。
附录
可以出现在[]
中的内容是有限制的,他们必须是下列中的一个(其中idata表示一个立即数):
idata
BX
BP
SI
DI
BX+SI
BX+DI
BP+SI
BP+DI
BX+idata
BP+idata
SI+idata
DI+idata
BX+SI+idata
BX+DI+idata
BP+SI+idata
BP+DI+idata
其中,使用到BP的默认段寄存器为SS,其他未DS
段寄存器的使用规定
汇编语言源程序
- 汇编源程序:由三部分组成:汇编指令、伪指令、其他标号与符号。
- 指令是能被计算机识别并执行的二进制代码,它规定了计算机能完成的某一操作。
- 伪指令是对汇编起某种控制作用的特殊命令,其格式与通常的操作指令一样,并加在汇编程序的任何地方,但是他们并不产生机器指令。
- 一个汇编源程序是由多个段组成的,这些段被用来存放代码、数据或当作栈空间来使用。
- 注意:一个有意义的汇编程序中至少要有一个段,这个段用来存放代码。
处理器选择伪操作
伪操作 | 说明 |
---|---|
.8086 | 选择8086指令系统 |
.286 | 选择80286指令系统 |
.286 P | 选择保护模式下的80286指令系统 |
.386 | 选择80386指令系统 |
.386 P | 选择保护模式下的80386指令系统 |
.486 | 选择80486指令系统 |
.486 P | 选择保护模式下的80486指令系统 |
.586 | 选择80586指令系统 |
.586 P | 选择保护模式下的80586指令系统 |
这类伪操作一般都是放在整个程序的最前面。如果不给出,则汇编程序一般认为其默认值为.8086。
段定义伪操作
在段定义时,如果定位类型用户未选择,就表示是隐含类型,其隐含类型是PARA。
PARA属定位类型,是对该段起始地址定位。一般,各个逻辑段的首地址在‘节’的整数边界上(每16个存储单元叫做一节),即每个逻辑段的起始地址是16的整数倍。对于PARA—指定定位段的起始地址必须在节的整数边界。
存储器的物理地址是由段地址和偏移地址组合而成的,汇编程序在把源程序转换为目标程序的时候,必须确定标号和变量(代码段和数据段的符号地址)的偏移地址,并且需要把有关信息通过目标模块传送给连接程序,以便连接程序把不同的段和模块连接起来,形成一个可执行的程序。
明确段与寄存器的关系:assume cs:code, ds:data, es:extra
data segment ; 定义数据段
…
data ends
;----------------------------------------
extra segment ; 定义附加段
…
extra ends
;----------------------------------------
code segment ; 定义代码段,其中的段寄存器名必须是CS、ES、DS、SS(对于386及其后继机型还有FS和GS)中的一个。
assume cs:code, ds:data, es:extra
start: ;----------------------程序开始的标号
mov ax, data
mov ds, ax ; 段地址 -> 段寄存器
…
mov ax,4c00h
int 21h
code ends
end start ;------------程序结束标志
说明:
- 1)data为段名称,也是段首地址,可自己定义
- 功能:定义一个段,segment说明一个段开始,ends 说明一个段结束。一个段必须有一个名称来标识,使用格式为:
- 段名 segment
- 段名 ends
- 功能:定义一个段,segment说明一个段开始,ends 说明一个段结束。一个段必须有一个名称来标识,使用格式为:
- 2)明确段和段寄存器的关系。
assume
只是说明关联关系,并没有对段寄存器赋值,除了CS(装入程序时由CPU给出),其他段寄存器要在程序中设置。 - 3)
mov ax,4c00h
和int 21h
最后两条指令所实现的功能是程序返回。 - 4)程序结束标志,格式:
END [label]
,标号label 指示程序开始执行的起始地址。
伪操作(伪指令)
伪操作是汇编程序对源程序进行汇编时处理的操作,完成处理器选择、存储模式定义、数据定义、存储器分配、指示程序开始结束等功能。
- 处理器选择伪操作;
- 功能:告诉汇编程序应该选择哪一种指令系统。
位置:一般放在整个程序的最前面,也可放在程序中所用指令的上一行。
如不给出,则默认为.8086。
- 功能:告诉汇编程序应该选择哪一种指令系统。
- 段定义伪操作;
- 程序开始和结束伪操作;
- Start
- end Start
- 数据定义及存储器分配伪操作;
- 表达式赋值伪操作;
- 地址计数器与对准伪操作;
- 基数控制伪操作。
数据定义和存储器分配伪操作
格式:[变量] 助记符 操作数 [ , 操作数 , … ] [ ; 注释]
- 助记符:DB DW DD DF DQ DT
- 例:操作数可以是常数或表达式
DATA_BYTE DB 10,4,10H,?
DATA_WORD DW 100,100H,-5,?
- 汇编程序在汇编期间在存储器中存入数据
- 操作数也可以是字符串
ARRAY DB ‘HELLO’
DB ‘AB’
DW ‘AB’
- 操作数也可以是地址
PAR1 DW 100,200
PAR2 DW 300,400
ADDR_TABLE DW PAR1,PAR2
- 操作数字段还可以使用复制操作符DUP来复制操作数
repeat_count DUP(operand,……,operand)
- repeat_count可以是一个表达式,它的值应该是一个正整数,用来指定括号中的操作数的重复次数。
- 操作数?可以保留存储空间,但是不存入数据。(仅仅申请一个存储空间)
VAR DB 100 DUP (?) ; 这里表示申请100个字单元大小的内存空间,但是不进行初始化
DB 2 DUP (0,2 DUP(1,2),3) ; 把0,1,2,1,2,3按字单元存放2次。
OPER1 DB ?, ?
OPER2 DW ?, ?
……
MOV OPER1, 0 ;字节指令
MOV OPER2, 0 ;字指令
OPER1 DB 1, 2
OPER2 DW 1234H, 5678H
……
MOV AX, OPER1+1 ×
MOV AL, OPER2 × 类型不匹配
MOV AX, WORD PTR OPER1+1
MOV AL, BYTE PTR OPER2
(AX)=3402H (AL)=34H
- 可以看出:同一个变量可以具有不同的类型属性。
LABEL 伪操作: name LABEL type
- LABEL可以使同一个变量具有不同的类型属性。
变量名 LABEL 类型
or 标号 LABEL 类型
其中变量的数据类型可以是BYTE,WORD,DWORD,标号的代码类型可以是NEAR或FAR。
数据定义及存储器分配伪指令格式中的 “变量 “是操作数的符号地址,它是可有可无 的,它的作用与指令语句前的标号相同,区别是变量后面不加冒号。如果语句中有变量,那么汇编程序将操作数的第一个字节的偏移地址赋于这个变量。
BYTE_ARRAY LABEL BYTE
WORD_ARRAY DW 50 DUP (?)
tos LABEL WORD
表达式赋值伪操作
说明:有时程序中多次出现同一个表达式,为了方便起见,可以使用赋值伪操作给表达式赋予一个名字。、
EQU是赋值伪指令。赋值语句仅在汇编源程序时,作为替代符号用。不产生目标代码,也不占有存储单元。
此后,程序中凡是用到该表达式之处,都可以用表达式名来代替了。可见,EQU的引入提高了程序的可读性,也使其更加易于修改。
格式:表达式名 EQU 表达式
功能:给表达式赋予一个名字,在程序中用表达式名代替该表达式。
ALPHA EQU 9
BETA EQU ALPHA+18
BB EQU [BP+8]
“ = ” 伪操作 (允许重复定义)
……
EMP = 7
……
EMP = EMP+1
……
说明:EQU指令类似于C语言的#define宏,在编译前被转化。
地址计数器与ORG伪操作
地址计数器$
地址计数器 $:保存当前正在汇编的指令的偏移地址
JNE $+6 ; 转向地址是 JNE 的首址 +6
JMP $+2 ; 转向下一条指令
- $ 用在指令中时,表示本条指令的第一个字节的地址;
- $ 用在伪操作的参数字段时表示地址计数器的当前值。
- 例如:
ARRAY DW 1, 2 , $+4 , 3 , 4 , $+4
-
- 通过图片可以看出,地址0078保存的数据为当前地址加上4;
- 地址007E是当前地址加上4。
ORG
ORG 伪操作:用来设置当前地址计数器的值。即:指定一个地址,后面的程序或数据从这个地址值开始分配。
SEG1 SEGMENT
ORG 10
VAR1 DW 1234H
ORG 20
VAR2 DW 5678H
ORG $+8; 当前地址加8,
VAR3 DW 1357H
SEG1 ENDS
EVEN
EVEN伪操作使下一个变量或者指令开始于偶数字节地址。一个字的地址最好从偶地址开始,所以对于字数组为了保证其从偶地址开始,可以在其前面使用EVEN伪操作来达到这一目的。
EVEN ;使下一地址从偶地址开始
A DB ‘morning’
EVEN ;使下一地址从偶地址开始
B DW 2 DUP (?)
ALIGN
ALIGN伪操作为保证双字数组边界从4的倍数开始创造了条件:
ALIGN 4 ; 保证下一个地址是4的倍数
ALIGN 2 ; 与EVEN等价
说明:
- ALIGN boundary(boundary的值必须是2的幂)。
基数控制伪操作
汇编程序默认的数是十进制数,因而除非专门指定,汇编程序把程序中出现的数均看作为十进制数。为此,当使用其他基数的时候,需要专门给以标记:
基数 | 标记 |
---|---|
二进制 | B |
十进制 | D |
十六进制 | H |
八进制 | O 或者 Q |
. RADIX 表达式 ; 规定无标记数的基数
MOV BX, 0FFH
MOV BX, 178
.RADIX 16
MOV BX, 0FF ;这里没有标记(不是0FFH),但是因为前面定义了`.RADIX 16`,所以这里仍然表示16进制的数。
MOV BX, 178D
汇编语言程序格式
汇编语言源程序中每个语句可以由四项组成,格式如下:
[name] operation operand [; comment]
其中
- 名字(name)项是一个符号。
- 操作(operation)项是一个操作码的助记符,它可以是指令、伪操作或者宏指令名。
- 操作数(operand)项是一个或多个表达式组成,它提供为执行所要求的操作而需要的信息。
- 注释(comment)项是用来说明程序或者语句的功能。;为识别注释项的开始。;也可以从一行的第一个字符开始,此时整行都是注释,常用来说明下面一段程序的功能。
名字项
操作项
操作数项由一个或者多个表达式组成,多个操作数项之间一般用逗号分开。
操作数项可以是常数,寄存器,标号,变量或者由表达式组成。表达式是常数,寄存器,标号,变量与一些操作符相结合的序列,可以有数字表达式和地址表达式两种。
算术操作符
算术操作符有+,-,*,/、MOD
。其中MOD是指除法运算后得到的余数。算术操作符可以用于数字或者地址表达式中,但当它用于地址表达式的时候,只有其结果有明确的物理意义的时候才是有效的结果。例如:两个地址相乘和相除是没有意义的。在地址表达式中,可以使用+或者-,但也必须注意其物理意义,例如把两个不同段的地址相加也是没有意义的。经常使用的是地址±数字量,它是有意义的,例如,SUM+1是指SUM字节单元的下一个字节单元的地址(注意:不是指SUM单元的内容加1),而SUM-1则是指SUM字节单元的前一个字节单元的地址。
逻辑与移位操作符
逻辑操作符有AND,OR,XOR和NOT;移位操作符有SHL和SHR。他们都是按位操作的,只能用于数字表达式中。
关系操作符
名称 | 全称 | 说明 |
---|---|---|
EQ | Equal | 相等 |
NE | Not equal | 不想等 |
LT | Less than | 小于 |
GT | Great than | 大于 |
LE | Less and equal | 小于或等于 |
GE | Great and equal | 大于或等于 |
说明:
- 关系操作符的两个操作数必须都是数字,或者同一段内的两个存储器地址。
- 计算结果应为逻辑值,结果为真,表示为0FFFFH;结果为假的时候,则表示为0。
数值回送操作符
它主要有的TYPE,LENGTH,SIZE,OFFEST,SEG等等。这些操作符把一些特征或者存储器地址的一部分作为数值回送。
TYPE
格式为:TYPE expression
如果该表达式是变量,则汇编程序将回送变量的以字节数表示的类型:DB=1,DW=2,DD=4,DF=6,DQ=8,DT=10。如果表达式是标号,则汇编程序将回送代表该标号类型的数值:NEAR=-1,FAR=-2。如果表达式是常数,则应回送0。
操作数项(表达式)
- (1) 算术操作符: +、- 、*、 /、Mod
- (2) 逻辑操作符: AND、OR、XOR、NOT,移位操作符: SHL、SHR
- (3) 关系操作符: EQ、NE、LT、LE、GT、GE
- (4) 数值回送操作符: TYPE、 LENGTH、 SIZE 、OFFSET、SEG、
- TYPE 变量 / 标号 / 常数
- DB、DW、DD、DF、DQ、DT、NEAR、FAR、常数
- 1、2、4、6、8、10、-1、-2、0(与上面的变量对应)
- (5) 属性操作符: PTR、段操作符、SHORT、THIS、HIGH、LOW、HIGHWORD、LOWWORD
LENGTH
- LENGTH 变量
- 功能:回送由DUP定义的变量的单元数,其它情况回送1
-
DR2 DW 10H DUP(0,2 DUP(2))
,MOV CL,LENGTH DR2
CL的值为10H -
DR1 DB 10H,30H
,MOV BL,LENGTH DR1
BL的值为1。
SIZE
- SIZE
- 变量功能:LENGTH * TYPE
OFFSET和SEG
- OFFSET / SEG 变量 / 标号
- 功能:回送变量或标号的偏址 / 段址
Debug
-
使用DEBUG调试和运行可执行文件
- 在初次使用DEBUG时,可参照下列步骤进行:
- 1、调用DEBUG,装入用户程序
- 2、U命令反汇编程序,记录代码段与数据段首地址
- 3、R命令观察寄存器初始状态
- 4、以单步工作方式T开始运行程序 ,设置段寄存器的值。
- 5、D观察用户程序数据段初始内容
- 6、继续以单步工作方式运行程序
- 7.G连续工作方式运行程序
- 8.E或A修改程序和数据
- 9.运用断点调试程序 G
80x86指令系统
空操作指令指令格式:NOP
说明:NOP是英语“No Operation”的缩写。NOP无操作数,所以称为“空操作”。
执行NOP指令只使程序计数器PC加1,所以占用一个机器周期。
通用数据传送指令
数据传送指令:MOV、XCHG、LEA、LDS、LES、PUSH、POP、PUSHF、POPF、CBW、CWD、CWDE。
栈的基本操作
8086CPU的入栈和出栈操作都是以字为单位进行的。
push与pop指令的执行过程
PUSH指令每次只能压入一个字(16位)。
push ax
(1)SP = SP–2;
(2)将AX中的内容送入SS:SP指向的内存单元处。
pop ax
(1)将SS:SP指向的内存单元处的数据送入ax中;
(2)SP = SP+2。
说明:
- 每执行一次PUSH,SP指针都会减1。
MOV指令
指令写法:MOV DST,SRC
执行操作:(DST)<-(SRC)
其中DST表示目的操作数,SRC表示源操作数。可以在CPU内部或者在存储器之间传送数据。
说明:
- SRC和DST的操作类型必须明确而且一致。
- DST不能是立即数,而且也不能是CS。
- 数据传送指令是不能把数据传送给CS的,因为CS是代码段寄存器,CS如果被修改程序就无法执行。
- DST、SRC也不能同时是存储器寻址。
- DST、SRC也不能同时是段寄存器。
- 立即数不能直接送段寄存器,必须通过寄存器(比如AX)送达段寄存器。
- 指令的执行不影响标志位。
-
立即数可以直接送到存储器,但应指定存储器的类型。如:
- mov byte ptr[di], 3
- mov word ptr[si], 3000
- mov dword ptr[bx], 0FFFFFFh
XCHG指令
指令格式: XCHG OPR1, OPR2
功能: 将操作数地址中的内容互换。
执行操作: (OPR1) <-> (OPR2)
注意:
- 指令的执行并不影响标志位。
- 不允许使用段寄存器,不允许使用立即数,不支持两个存储单元之间的数据交换。
- 允许字或者字节操作。
累加器专用传送指令
XLAT
功能:将表格中的一个字节内容送到AL累加器中。常用于将一种代码转换为另一种代码。
这条指令根据AL寄存器提供的位移量,将BX指使的字节表格中的代码换存在AL中。(AL)<-((DS)*16+(BX)+(AL))
说明:本指令并不影响标志位。
格式:
XLAT TABLE ;TABLE为表格的起始地址)
XLAT ;(AL)<-((BX)+(AL))
执行指令前要将TABLE先送入BX,将待查字节与在表格中距离表首地址位移量送AL。
地址传送指令
指令 | 全称 | 说明 |
---|---|---|
LEA | load effective address | 有效地址送寄存器 |
LDS | load DS with pointer | 指针送寄存器和DS |
LES | load ES with pointer | 指针送寄存器和ES |
LFS | load FS with pointer | 指针送寄存器和FS |
LGS | load GS with pointer | 指针送寄存器和GS |
LSS | load SS with pointer | 指针送寄存器和SS |
LEA
功能:有效地址送寄存器。
全称:load effective address
LEA BX,LIST ;------取LIST的偏移地址送BX
MOV BX, OFFSET LIST ;------功能与LEA相同
LEA BX, [BX+SI] ;------取基址变址寻址的有效地址给BX
MOV BX, OFFSET [BX+SI] ;
——× 注意:OFFSET只能与简单的符号地址相连。
算术指令
算术运算指令会根据运算结果影响状态标志,主要影响6个标志位:ZF、CF、AF、SF、OF和PF。
加法指令
指令 | 使用 | 说明 |
---|---|---|
ADD | ADD DST,SRC | 功能:加法,将SRC和DST的值相加之后存放在DST中。 执行操作: (DST) <- (SRC) + (DST) |
ADC | ADC DST, SRC | 功能:带进位的加法指令,将SRC和DST的值和进位标志位(CF)相加之后存放在DST中。 执行操作:(DST) <- (SRC) + (DST) + CF |
INC | INC OPR | 功能:加一指令。 执行操作:(OPR) <- (OPR) + 1 |
说明:加法指令都会影响条件标志位,但INC指令不影响CF标志。
INC影响的条件标志位包括:SF,ZF,OF,AF,PF。
adc指令的作用
在执行 adc 指令的时候加上的 CF 的值是由 adc指令前面的指令决定的,也就是说,关键在于所加上的CF值是被什么指令设置的。
下面的指令和add ax , bx具有相同的结果:
add al,bl
adc ah,bh
看来CPU提供 ADC指令的目的,就是来进行加法的第二步运算的。ADC指令和ADD指令相配合就可以对更大的数据进行加法运算。
注意:有符号的双精度数的溢出,应根据ADC指令的OF位判断,而作低位加法用的ADD指令的溢出是无意义的。
用16位寄存器编写程序:
MOV AX, word ptr d1 ;由于d1是双字类型,必须使用强制类型说明符。
MOV DX, word ptr d1+2 ;(DX,AX)构成一个32位数据
ADD AX, word ptr d2 ;低字相加,有可能会产生“进位”
ADC DX, word ptr d2+2 ;高字相加。
MOV word ptr d1, AX ;低字送给d1的低字
MOV word ptr d1+2, DX ;高字送给d1的高字
说明:
OF位可以用来表示带符号数的溢出,CF位可以用来表示无符号数的溢出。
条件标志(或者称呼为)位中最主要的是CF,ZF,SF,OF四位,分别表示了进位、结果为零,符号和溢出的情况。
执行加法指令的时候,CF位是根据最高有效位是否向最高位的进位来设置的。有进位的时候CF=1,无进位的时候CF=0。
OF位则根据操作数的符号及其变化情况来设置的:若两个操作数的符号相同,而结果的符号与之相反则OF=1,否则OF=0。
- 注意区分OF是根据原先的符号位来判断的。
溢出位OF既然试试根据数的符号及其变化来设置的,当然它是用来表示带符号数的溢出的,从其设置条件来看结论也是明显的。
CF位可以用来表示无符号数的溢出。一方面,由于无符号数的最高有效位只有数值意义而无符号意义,所以该位产生的进位应该是结果的实际的进位值,但是在有限数位的范围内就说明了结果的溢出情况;另一方面,它所保存的进位值有时候还是有用的。例如。双字长数运算的时候,可以利用进位值把低位字的进位计入高位字中。
减法指令
指令 | 使用 | 说明 |
---|---|---|
SUB | SUB DST,SRC | 功能:减法,将SRC和DST的值相减之后存放在DST中。 执行操作: (DST) <- (SRC) + (DST) |
SBB | SUB DST, SRC | 功能:带借位减法指令,将SRC和DST的值和进位标志位(CF)相加之后存放在DST中。 执行操作:(DST) <- (SRC) + (DST) - CF CF为进位位的值。 |
DEC | DEC OPR | 功能:加一指令。 执行操作:(OPR) <- (OPR) + 1 |
NEG | NEG OPR | 功能:加一指令。 执行操作:(OPR) <- (OPR) + 1 把操作数按位求反后末尾加1。 |
CMP | CMP OPR1, OPR2 | 功能:加一指令。 执行操作: (OPR1) - (OPR2) 执行减法操作,不保存结果。往往根据比较发生转移。 |
说明:
减法运算的条件码情况和加法类似。CF位说明无符号数相减的溢出,同时它又确实是被减数的最高有效位向高位的借位值。OF位则说明带符号数的溢出。
减法的CF值反映了无符号数运算中的借位情况,因此当作为无符号运算时,若减数>被减数,此时有借位,则CF=1;否则CF=0。或者,也可以简单地用二进制减法的运算中的最高有效位向高位的进位的情况来判别:有进位的时候CF=0,没有进位的时候CF=1。
减法的OF位的设置方法为:若两个数的符号相反,而结果的符号与减数相同则相同,则OF=1;除了上述情况外OF=0。OF=1说明带符号数的减法溢出,结果是错误的。
NEG指令的条件码按照求补后的结果设置,只有当操作数为0的时候,求补运算的结果使得CF=0,其他情况都为CF=1。所以,只有当字运算时对-128求补的时候,以及字运算的时候对-32768求补以及双字运算的时候对-2^31求补的情况下OF=1,其他则均为OF=0。
乘法指令
指令 | 使用 | 说明 |
---|---|---|
MUL | 无符号数乘法指令 | MUL SRC |
IMUL | 带符号数乘法指令 | IMUL SRC |
类型 | 说明 |
---|---|
字节操作数 | (AX) <- (AL) * (SRC) |
字操作数 | (DX, AX) <- (AX) * (SRC) |
注意:
- AL (AX) 为隐含的乘数寄存器。
- AX (DX,AX) 为隐含的乘积寄存器。 CPU会根据乘数是8位、16位,还是32位操作数,来自动选用被乘数:AL、AX或EAX。
- SRC不能为立即数。
- 除CF和OF外,对条件标志位无定义。
- 当乘积的高半部分不为0时,CF=1,OF=1。
除法指令
指令 | 使用 | 说明 |
---|---|---|
DIV | 无符号数除法指令 | DIV SRC |
IDIV | 带符号数除法指令 | IDIV SRC |
类型 | 说明 |
---|---|
字节操作数 | (AL) <- (AX) / (SRC) 的商 (AH) <- (AX) / (SRC) 的余数 |
字操作数 | (AX) <- (DX, AX) / (SRC) 的商 (DX) <- (DX, AX) / (SRC) 的余数 |
注意:
- AX (DX,AX) 为隐含的被除数寄存器。
- AL (AX) 为隐含的商寄存器。
- AH (DX) 为隐含的余数寄存器。
- SRC不能为立即数。
- 对所有条件标志位均无定义。但是可能产生溢出。
- 执行除法指令后,对AF,CF,OF,PF,SF,ZF标志位的影响都不确定。
标志寄存器传送指令
指令 | 全称 | 说明 |
---|---|---|
LAHF | load AH with flags | 标志送AH |
SAHF | store AH into flags | AH送标志寄存器 |
PUSHF/PUSHFD | push the flags or eflags | 标志进栈 |
PUPF/POPFD | pop the flags or eflags | 标志出栈 |
注意:
这组指令中的LAHF和PUSHF/PUSHFD都不影响标志位。SAHF和POPF/POPFD则由装入的值来确定标志位的值,但是POPFD指令不影响VM,RF,IOPF,VIF和VIP的值。
STC----是置进位标志指令,执行的结果是将进位标志CF置1
- CLC----是清进位标志指令,其执行结果是置CF标志为0
类型转换指令
指令 | 使用 | 说明 |
---|---|---|
CBW | 字节转换为字 | AL符号扩展到AH中,形成AX中的字。即如果(AL)中的最高有效位是0,则(AH)=0;如(AL)的最高有效位为1,则(AH)=0FFH。 执行操作: 若(AL)的最高有效位为0,则(AH)= 00H 若(AL)的最高有效位为1,则(AH)= FFH |
CWD/CWDE | 字转换为双字 | AX符号扩展 -> (DX,AX)双字 执行操作: 若(AX)的最高有效位为0,则(DX)= 0000H 若(AX)的最高有效位为1,则(DX)= FFFFH |
CWQ | 双字转换为4字 | EAX的内容符号扩展到EDX,形成EDX:EAX中的4字。 |
BSWAP | 字节交换指令 | 格式:BASWAP r32。 该指令只能用于486及其后继机型。r32指32位寄存器。 执行的操作:使指令指定的32位寄存器的字节次序变反。具体的操作为:1、4字节互换,2 |
注意:
- 隐含对AL 或AX 进行符号扩展
- 本组指令都不影响条件标志位
逻辑指令
逻辑运算指令
指令 | 全称 | 说明 |
---|---|---|
AND | and | 逻辑与 格式:AND DST,SRC |
OR | or | 逻辑或 格式:OR DST,SRC |
NOT | not | 逻辑非 格式:NOT DST,SRC |
XOR | exclusive or | 异或 格式:XOR DST,SRC |
TEST | test | 作用:测试 格式:TEST OPR1,OPR2 执行的操作:(OPR1)∧(OPR2) 说明:两个操作数相与的结果不保存,只是根据其特征置条件码。 |
注意:
- 在以上的五种指令中,NOT不允许存放立即数。
- 其他4条指令除非源操作数是立即数,至少有一个操作数必须存放在寄存器中,另一个操作数则可以使用任意寻址方式。
- NOT不影响标志为。
- 其他4种指令将使CF位和OF位为0,AF位无定义,而SF位、ZF位和PF位则根据运算结果设置。
- 这些指令对处理操作数的某些位很有用,例如可以屏蔽某些位(将这些位置0),或者使某些位置1或者测试某些位等等。
AND
要求屏蔽0、1两位,可以用AND指令并设置常数0FCH。
MOV AL,0BFH
AND AL,0FCH
这两条指令的运行结果使得(AL)=0BCH。因此,使用AND指令可以使得操作数的某些位被屏蔽。只需要把AND指令的源操作数设置成一个立即数,并把需要屏蔽的位设置为0,这样指令执行的结果就可以把操作数的相应位置0,其他位保持不变。
OR
要求第5位置1,可以使用OR指令。
MOV AL,43H
MOV AL,20H
这两条指令执行了之后,(AL)=63 H。因此,用OR指令可以使得操作数的某些位置1,其他位保持不变。只需要把OR指令的源操作数设置为一个立即数,并把需要置1的位设置为1,就可以达到目的了。
TEST
要测试操作数的某些位是否为0,可以使用TEST指令,同样把TEST指令的源操作数设置成一个立即数,其中需要测试的位应该设置为1。
MOV AL,40H
TEST AL,0AFH
这里要求测试第0,1,2,3,5,7位是否为0,根据测试的结果设置条件码为CF=OF=0,SF=0,ZF=1,说明了所需要测试的位均为0。如果在这两条指令之后跟一条件转移指JNZ,结果如果不是0就转移,结果如果是0就顺序往下执行,这样就可以根据测试的情况产生不同的程序分支,转向不同的处理方案了。
NOT
要测试操作数的某位是否为1,可以先把该操作数求反,然后使用TEST指令测试。如要测试AL寄存器中第2位是否为1,若为1则转移到EXIT中去执行,可以用下列指令序列:
MOV DL,AL
NOT DL
TEST DL,0000 0100B
JE EXIT
如AL寄存器的内容为0FH,为了避免破坏操作数的原始内容,把它复制到了DL中去测试,执行完TEST指令之后,结果为全0而有ZF=1,说明操作数的第2位为1引起的转移到EXIT去执行。
XOR
要是操作数的某些位变反,可以使用XOR指令,只要把源操作数的立即数字段的相应位置设置为1就可以达到目的。如果求第0,1位变反,可以使用下面的指令:
MOV AL,11H
XOR AL,3
则指令执行后,(AL)=12H,达到了第0,1位变反而其他位不变的目的。
XOR指令还可以用来测试某一个操作数是否与另外一个确定的操作数相等。这种操作在检查地址是否匹配的时候是经常使用的。
XOR AX,042EH
JZ MATCH
这两条指令是用来检查AX的内容是否等于042EH,若相等则转移到MATCH去执行匹配的情况需要做的工作,否则执行JZ下面的程序。
位测试并修改指令
386及其后继机型增加了本组指令。
位扫描指令
386及其后继机型增加了本组指令。
移位指令-这里编辑未完成
指令 | 全称 | 说明 |
---|---|---|
SHL | shift logical left | 逻辑左移 |
SAL | shift arithmetic left | 算术左移 |
SHR | shift logical right | 逻辑右移 |
SAR | shift arithmetic right | 算术右移 |
ROL | rotat left | 循环左移 |
ROR | rotat right | 循环右移 |
RCL | rotate left through carry | 带进位循环左移 |
RCR | rotate right through carry | 带进位循环右移 |
SHLD | shift left double | 双精度左移 |
SHRD | shift right double | 双精度右移 |
注意:
- OPR可用除立即数以外的任何寻址方式
- 移位次数CNT=1,
SHL OPR, 1
- CNT>1,
MOV CL, CNT
,SHL OPR, CL
- 条件标志位:
- CF = 移入的数值
- OF = 1 CNT=1时,最高有效位的值发生变化
- OF = 0 CNT=1时,最高有效位的值不变
- 循环移位指令:
- 不影响 SF、ZF、PF、AF
- 移位指令:
- 常用来作乘以2或除以2 的操作。
- SAL:有符号数乘以2,SAR有符号数除以2;
- SHL:无符号数乘以2,SHR: 无符号数除以2。
移位指令 | 移位填充方式 |
---|---|
逻辑左移 | 右边统一添0,移出来的那一位放进CF |
算术左移 | 右边统一添0,移出来的那一位放进CF |
逻辑右移 | 左边统一添0,移出来的那一位放进CF |
算术右移 | 左边添加符号位上的数,移出来的那一位放进CF |
示例:1010101010
,其中[]
是添加的位
逻辑左移一位:010101010[0]
算术左移一位:010101010[0]
逻辑右移一位:[0]101010101
算术右移一位:[1]101010101
控制转移指令
一般情况下指令都是顺序逐条执行的,但是实际上程序不可能全部顺序执行而经常需要改变程序的执行流程。
无条件转移指令JMP
- 段内直接短转移:
- JMP SHORT OPR
- 执行操作:(IP) ← (IP) + 8位位移量
- short:表示实现的是段内直接短转移,即位移量为8位数据,它对IP的修改范围为 -128~127,也就是说,它向前转移时可以最多越过128个字节,向后转移可以最多越过127个字节。
- 段内直接近转移:(转向的符号地址)
- JMP NEAR PTR OPR
- 执行操作:(IP) ← (IP) + 16位位移量
- 段内间接转移:(除立即数以外的寻址方式)
- JMP WORD PTR OPR
- 执行操作: (IP) ← (EA)
- 段间直接远转移:(转向的符号地址)
- JMP FAR PTR OPR
- 执行操作:
- (IP) ← OPR 的段内偏移地址
- (CS) ← OPR 所在段的段地址
- 段间间接转移:(存储器寻址方式)
- JMP DWORD PTR OPR
- 执行操作:
- (IP) ← (EA)
- (CS) ← (EA+2)
注意:
-
执行JMP指令的时候,IP改变两次。
- 第一次是在读取指令的时候,JMP指令被读取放到了指令缓冲期中,这个时候IP改变了一次;之后,JMP指令执行的时候进行了跳转,IP又改变了一次。所以IP总共改变了两次。
条件转移指令
注意:只能使用段内直接寻址的8 位位移量。
1)根据单个条件标志的设置情况转移:
格式 | 全称 | 转移条件 | 说明 |
---|---|---|---|
JZ(JE) OPR | jump if zero,or equal | ZF = 1 | 结果为0(相等)则转移 |
JNZ(JNE) OPR | jump if not zero, or not equal | ZF = 0 | 不为0(不相等)转移 |
JS OPR | jump if sign | SF = 1 | 为负转移 |
JNS OPR | jump if not sign | SF = 0 | 为正转移 |
JO OPR | jump if overflow | OF = 1 | 溢出转移 |
JNO OPR | jump if not overflow | OF = 0 | 不溢出转移 |
JP OPR | jump if parity, or parity even | PF = 1 | 有偶数个1则转移 |
JNP OPR | jump if not parity, or parity odd | PF = 0 | 有奇数个1 则转移 |
JC OPR | jump if carry | CF = 1 | 有进位转移 |
JNC OPR | jump if not cary | CF = 0 | 无进位转移 |
2)比较两个无符号数,并根据比较结果转移的指令(与比较指令CMP 联用)
符号表示 | 格式 | 全称 | 转移条件 | 说明 |
---|---|---|---|---|
< | JB (JNAE,JC) OPR | jump if below | CF = 1 | 有借位,被减数小于减数则转移 |
≥ | JNB (JAE,JNC) OPR | jump if not below | CF = 0 | 没有借位, 被减数大于或等于减数则转移 |
≤ | JNA (JBE) OPR | jump if not above, jump if below of equal | CF∨ZF = 1 | 被减数小于或等于减数则转移 |
> | JA (JNBE) OPR | jump if above, jump if not below or not equal | CF ∨ ZF = 0 | 被减数大于减数则转移 |
说明:
- 适用于地址或双精度数低位字的比较。
无符号数比较示例:cmp ax,bx
cmp指令对有符号数的比较: cmp ah, bh
-
1、如果SF=1,而OF=0, (ah)<(bh)
- OF=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负;SF=1表示实际结果为负,所以逻辑上真正的结果为负,所以(ah)<(bh)。
-
2、如果SF=0,而OF=1 ,(ah)<(bh)
- OF=1 ,说明有溢出,逻辑上真正结果的正负≠实际结果的正负;SF=0表示由于溢出导致了实际结果非负,那么逻辑上真正的结果必然为负。这样,SF=0,OF = 1 ,说明了(ah)<(bh)。
-
3、如果SF=0,而OF=0, (ah)≥(bh)
- OF=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负;SF=0表示实际结果非负,所以逻辑上真正的结果必然非负。所以(ah)≥(bh)。
-
4、如果SF=1,而OF=1, (ah)>(bh)
- OF=1 ,说明有溢出,逻辑上真正结果的正负≠实际结果的正负; SF=1 表示由于溢出导致了实际结果为负,那么逻辑上真正的结果必然为正。这样,SF=1,OF = 1 ,说明了(ah)>(bh)。
3)比较两个带符号数,并根据比较结果转移的指令
符号表示 | 格式 | 全称 | 转移条件 | 说明 |
---|---|---|---|---|
< | JL (JNGE) OPR | jump if less | SF∀OF = 1 | 小于,或者不大于或等于则转移。 |
≥ | JNL (JGE) OPR | jump if not less | SF∀OF = 0 | 不小于,或者大于或等于则转移。 |
≤ | JNG (JLE ) OPR | jump if not greater | (SF∀OF)∨ZF = 1 | 不大于或者小于或等于则转移。 |
> | JG (JNLE) OPR | jump if greater | (SF∀OF)∨ZF = 0 | 大于或者不小于或等于则转移。 |
说明:
-
适用于带符号数的比较
4)测试 CX 的值为 0 则转移的指令
格式 | 全称 | 测试条件 | 说明 |
---|---|---|---|
JCXZ OPR | jump if CX register is zero | (CX)=0 | CX寄存器的内容为零则转移。 |
循环指令
Loop
// 计算0+1+2+3+4+5+6+7+8+9的值
int sum=0;
for(int i=0;i<10;i++)
sum=sum+i;
相当于:
// 计算0+1+2+3+4+5+6+7+8+9的值
mov ax,0; ax相当于sum
mov bx,0; bx相当于i
mov cx,10
;标号s代表一个地址
s: add ax,bx
inc bx
loop s; 判断
指令的格式是:loop 标号
CPU 执行loop指令的时候,要进行两步操作:
① (cx)=(cx)-1;
② 判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行,退出循环。
可见,cx中的值影响着loop指令的执行结果。用loop指令来实现循环功能时,cx 中要存放循环次数。
一条循环指令LOOP AGAIN
可以用修改循环计数和判断转移条件的两条指令替代DEC CX
,JNZ AGAIN
。JNZ(或JNE)结果不为零(或不相等)则转移,测试条件为ZF=0。
我们可以总结出用cx和loop 指令相配合实现循环功能的三个要点:
- 1)在cx中要存放循环次数;
- 2)loop 指令中的标号所标识地址要在前面;
- 3)要循环执行的程序段,要写在标号和 loop 指令的中间。
- 4)loop指令的执行不影响条件码标志位。
LOOP示例
MOV CX,0
S:
ADD AX,BX
LOOP S
以上指令序列执行后ADD AX,BX指令被执行了多少次?
答案是:65536次。
循环 LOOP (loop)
指令的汇编格式:LOOP label
指令的基本功能:
①(CX)←(CX)-1
② 若(CX)≠0
,则(IP)←(IP)当前+位移量
,否则循环结束。
指令的特殊要求:循环指令都是短转移格式的指令,也就是说,位移量是用8位带符号数来表示的,转向地址在相对于当前IP值的-128 ~ +127字节范围之内。
解析:LOOP指令是先执行CX自减的功能,之后才进行循环的。只要CX不为0,循环就不会终止。因此在上面中,第一次执行的时候,CX自减为0FFFFH,这时CX就不为0的,循环不会被终止。由此,我们可以算出总共运行了65536次。
可提前结束的循环指令
功能 | 格式 | 测试条件 |
---|---|---|
当为0或相等时循环 | LOOPZ / LOOPE 标号 | ZF=1且(CX)≠0 |
不为0或不相等循环 | LOOPNZ / LOOPNE 标号 | ZF=0且(CX)≠ 0 |
执行步骤:
- 1)
(CX) ← (CX) - 1
; - 2) 检查是否满足测试条件,如满足则实现循环;不满足则退出循环。
注意:
- CX 中存放循环次数;
- 与比较指令
CMP
联合使用可提前退出循环。
串处理指令
串处理指令 | 指令 | 串重复前缀 | 设置方向标志指令 |
---|---|---|---|
串传送 | MOVSB / MOVSW | REP | CLD |
存入串 | STOSB / STOSW | REPE / REPZ | STD |
从串取 | LODSB / LODSW | REPNE / REPNZ | |
串比较 | CMPSB / CMPSW | ||
串扫描 | SCASB / SCASW | ||
串输入 | INSB / INSW | ||
串输出 | OUTSB / OUTSW |
说明:
字符串操作指令的实质是对一片连续存储单元进行处理,这片存储单元是由隐含指针DS:SI或ES:DI来指定的。
REP
与 REP
配合工作的MOVS / STOS / LODS / INS / OUTS
。
REP
重复串操作直到计数寄存器CX的内容为0为止。
执行操作:
- 1) 如 (CX)=0 则退出 REP,否则转(2)
- 2) (CX) <- (CX) -1
- 3) 执行 MOVS / STOS / LODS / INS / OUTS
- 4) 重复 (1) ~ (3)
MOVS串传送指令
MOVS 串传送指令:
MOVS DST, SRC
MOVSB ;(字节)
MOVSW ;(字)
MOVSD ;(双字,计数器为ECX,386及后继机型)
例:MOVS ES: BYTE PTR [DI], DS: [SI]
执行操作:
- 1)
((DI)) ← ((SI))
- 2) 字节操作:
(SI)←(SI)±1, (DI)←(DI)±1
,字操作:(SI)←(SI)±2, (DI)←(DI)±2
- (方向标志 DF=0 时用 + ,DF=1 时用 - 。)
REP MOVS
:将数据段中的整串数据传送到附加段中。源串(数据段)→ 目的串(附加段)
执行 REP MOVS 之前,应先做好:
- 1) 源串首地址(末地址)→
SI
- 2) 目的串首地址(末地址)→
DI
- 3) 串长度 →
CX
- 4) 建立方向标志
CLD ( STD )
- CLD 使 DF=0,从前往后处理,地址自动增量;
- STD 使 DF=1 ,由后向前处理,地址自动减量)
代码实例
; -------- 定义数据段
data segment
mess1 db ‘personal_computer’
data ends
; -------- 定义附加段
extra segment
mess2 db 17 dup (?)
extra ends
code segment
assume cs:code,ds:data.es:extra
mov ax, data
mov ds,ax
mov ax, extra
mov es, ax
; ------ 总共5条指令。
lea si, mess1
lea di, mess2
mov cx, 17
cld
rep movsb
…
code ends
反向的指令:
lea si, mess1+16
lea di, mess2+16
mov cx, 17
std
rep movsb
为了在同一段内处理数据,可以在DS和ES中设置同样的地址。
data segment
mess1 db ‘personal_computer’
mess2 db 17 dup (?)
data ends
code segment
mov ax, data
mov ds, ax
mov es, ax
lea si, mess1
lea di, mess2
mov cx, 17
cld
rep movsb
…
code ends
STOS存入指令
STOS DST
STOSB (字节)
STOSW (字)
执行操作:
字节操作:((DI))←(AL), (DI)←(DI)±1
字操作:((DI))←(AX), (DI)←(DI)±2
例:把 附加段 中mess2开始的 10 个字节缓冲区全部置为 20H
lea di, mess2
mov al, 20H
mov cx, 10
cld
rep stosb
或者:
lea di, mess2
mov ax, 2020H
mov cx, 5
cld
rep stosw
LODS从串取指令
LODS SRC
LODSB ;(字节)
LODSW ;(字)
执行操作:
- 字节操作:(AL)←((SI)), (SI)←(SI)±1
- 字操作:(AX)←((SI)), (SI)←(SI)±2
注意:
- LODS 指令一般不与 REP 联用;
- 有时缓冲区中的一串字符需要逐次取出来测试,可使用本指令。
INS 串输入指令:
INS DST, DX
INSB ; (字节)
INSW ;(字)
执行操作:
字节操作:((DI))←((DX)), (DI)←(DI)±1
字操作:((DI))←((DX)), (DI)←(DI)±2
功能:把端口号在DX的I/O空间的字节、字或双字传送到附加段中的由DI所指向的存储单元中,并根据DF的值和数据类型修改DI的内容。
; 从0F03H端口输入10个字符到mess1字节缓冲区
lea di, mess1
mov dx, 0F03H
mov cx, 10
cld
rep insb
OUTS 串输出指令:
OUTS DX , SRC
OUTSB ;(字节)
OUTSW ;(字)
执行操作:
字节操作:((DX))←((SI)), (SI)←(SI)±1
字操作:((DX))←((SI)), (SI)←(SI)±2
功能:把由SI所指向的存储单元中的字节、字或双字传送到端口号在DX的I/O端口中去,并根据DF的值和数据类型修改SI的内容。
; -------- 把mess1字节缓冲区中的10个字符从0F03H端口输出
lea si, mess1
mov dx, 0F03H
mov cx, 10
cld
rep outsb
与 REPE / REPZ(REPNE / REPNZ)配合工作的
CMPS 和 SCAS
指令 | 说明 |
---|---|
REPE / REPZ | 当相等 /为零时重复串操作 |
REPNE / REPNZ | 当不相等 /不为零时重复串操作 |
执行操作:
- 1) 如
(CX)=0
或ZF=0 (ZF=1)
则退出串操作,否则转2) - 2)
(CX)←(CX) -1
- 3) 执行
CMPS / SCAS
- 4) 重复 (1) ~ (3)
CMPS 串比较指令:
CMPS SRC, DST
CMPSB ;(字节)
CMPSW ;(字)
执行操作:
- 1)
((SI)) - ((DI))
根据比较结果置条件标志位:相等 ZF=1,不等 ZF=0 -
2) 字节操作:
(SI)←(SI)±1, (DI)←(DI)±1
,字操作:(SI)←(SI)±2, (DI)←(DI)±2
汇编语言中,CMP和CMPS都是比较指令,不同主要有:
1、CMP比较指令是执行两个数的相减操作,包括有符号数。CMPS比较指令是执行两个字符串的相减操作,所有数据认为是无符号数。
2、CMP比较指令必须有两个显式操作数。CMPS比较指令可以有两个显式操作数,也可以使用指令CMPSB或CMPSW分别表示字节串比较或字串比较而隐含操作数。
3、使用CMP比较指令比较连续的数据时,必须由程序改变其中一个操作数。使用CMPS比较指令比较连续的字符时,对由SI寻址的源串中数据与由DI寻址的目的串中数据进行比较,执行完一条比较指令,SI,DI将自动调整.
REPE
例:比较两个字符串,找出它们不相匹配的位置。
lea si, mess1
lea di, mess2
mov cx, 5
cld
repe cmpsb
反向比较:
lea si, mess1+4
lea di, mess2+4
mov cx, 5
std
repe cmpsb
SCAS 串扫描指令
SCAS DST
SCASB (字节)
SCASW (字)
执行操作:
字节操作:(AL) - ((DI)), (DI)←(DI)±1
字操作:(AX) - ((DI)), (DI)←(DI)±2
例:从一个字符串中查找一个指定的字符
mess db ‘COMPUTER’
lea di, mess ; -------- (ES:DI)保留串地址
mov al, ‘T’ ; -------- 搜索字符
mov cx, 8 ; -------- 字符长度
cld
repne scasb ; -------- 这里在循环的判断是否(CX)=0或ZF=1。条件成立即中止
执行完后
- (di):相匹配字符的下一个地址
- (cx):剩下还未比较的字符个数
子程序设计
int i=0;
int main(){
func();
return 0;
}
void func(){
i++;
}
等价于:
ASSUME CS:CODE
CODE SEGMENT
START:
MOV AX,0 ;AX相当于i
CALL FUNC ;调用FUNC子程序
MOV AH,4CH
INT 21H
FUNC PROC NEAR
INC AX ;i++
RET
FUNC ENDP
CODE ENDS
END START
子程序:在许多应用程序中,常常需要多次使用某功能的指令序列。这时,为了减少重复编写程序,节省内存空间,把这一功能的指令序列组成一个相对独立的程序段。在程序运行时,如果需要使用这个给定的功能,就转移到这个独立的程序段,待这个独立的程序段指令序列执行完后,又返回到原来位置继续运行程序。我们把这个相对独立的程序段就叫子程序或过程。
调用程序:编制程序时,按需要转向子程序,称为子程序调用,或称为过程调用。调用子程序的程序称为调用程序或主程序。主、子程序是相对而言的。但子程序一定是受调用程序或主程序调用的。子程序定义的位置可以放在主程序的前面或后面。
过程定义伪操作
过程名 PROC NEAR or FAR
……
RET
过程名 ENDP
说明:
- 过程名是子程序入口的符号地址;
- RET是子程序返回的命令;
- NEAR属性:调用程序和子程序在同一代码段中,可省略。(段内调用)
- FAR属性:调用程序和子程序不在同一代码段中。(段间调用)
- 同一个子程序可以被段内调用,也可以段间调用。
子程序的调用与返回指令
1)CALL 子程序调用指令:隐含使用堆栈保存返回地址
指令格式:CAL DST ;其中DST为过程的目标地址(即过程名)
。
指令功能:
- 把CALL指令的下一条指令地址(称为返回点或断点) 推入堆栈保存,然后转到目标地址(DST)。
- CALL指令可以在段内、段间调用,寻址方式分为直接和间接两种。
段内直接近调用:CALL DST
DST给出子程序的入口地址(子程序为near属性),比如:CALL subp
执行操作:
- (SP) ← (SP) - 2
- ( (SP)+1,(SP) ) ← (IP)
- 上面两句相当于
PUSH IP
- 上面两句相当于
- (IP) ← DST
- 实际操作是把从指令中得到的距目标过程相对偏移量加到指令指针IP上(得到子程序的入口地址),实现过程调用。
段内间接近调用:CALL DST
执行操作:
PUSH IP
- (IP) ← (EA)
- DST给出寄存器或存储单元的内容(转向地址)
- 比如: CALL word ptr [bx]
段间直接远调用:CALL FAR PTR DST
DST给出子程序的入口地址(子程序为far属性),比如:CALL far ptr subp
执行操作:
PUSH CS
PUSH IP
- (IP) ← DST偏移地址
- (CS) ← DST段地址
段间间接远调用:CALL FAR PTR DST
DST给出存储单元的内容(转向地址),比如: CALL dword ptr [bx]
执行操作:
PUSH CS
PUSH IP
- (IP) ← (EA)
- (CS) ← (EA+2)
2)RET 返回主程序指令
说明:属于无条件转移指令。可以在段内或段间返回。
段内近返回:RET
执行操作:
POP IP
段内带立即数近返回:RET EXP
执行操作:
POP IP
(SP)←(SP)+EXP
段间远返回:RET(F)
执行操作:
POP IP
-
POP CS
现场保护和恢复
要保护的寄存器:应该是在子程序中将被使用,返回调用程序后仍然需要使用其原有内容的那些寄存器。即保护调用程序和子程序两者在使用上发生冲突的那些寄存器。但在编程时,一时很难弄清哪些是有冲突的寄存器,一种较为简单的方法是把所有的寄存器均加以保护。
一般在子程序中进行寄存器保护较好。即在子程序的开始部分,先进行相关寄存器(主要是在子程序中使用的各寄存器)的保护。然后再进行子程序的处理操作。在执行完子程序后,返回前,先恢复各寄存器内容后,再返回调用程序。
subt proc near
push ax
push bx
push cx
push dx
……
……
pop dx
pop cx
pop bx
pop ax
ret
subt endp
子程序的参数传送
入口参数:子程序需要从主程序获取的参数。使子程序可以对不同数据进行相同功能的处理。
出口参数:是子程序返回给主程序的参数。使子程序可以将不同的结果送至主程序
实现的方法是把子程序所需要的入口参数,由调用程序预先放入指定的寄存器中。在进入子程序后,子程序就可直接对这些寄存器内容进行操作了。同样子程序的运行结果,也可置入寄存器中,把它们作为子程序的出口参数寄存器使用。由于寄存器数目有限,适用于参数较少的情况。
- 1) 通过寄存器传送参数
- 2) 通过存储器传送参数
- 3) 通过地址表传送参数地址
- 4) 通过堆栈传送参数或参数地址
参数的传递方法并不是固定不变的,即它们是可以综合使用的。依实现的需要和情况的不同,可以灵活使用其中一种方式,也可以同时使用几种方式的混合。有的时候还可能并不需要参数传递。
; 十六进制到十进制的转换(通过寄存器传送参数)
; hexidec:接收键盘输入的十六进制数,在屏幕上输出相应的十进制数
hexidec segment ; 1610
assume cs: hexidec
main proc far
start:
push ds
sub ax, ax
push ax
call hexibin ; 16转2
call crlf
call binidec ; 2转10
call crlf
ret
main endp
……
……
……
hexidec ends
; 按位取数
binidec proc near
mov cx, 10000d
call dec_div
mov cx, 1000d
call dec_div
mov cx, 100d
call dec_div
mov cx, 10d
call dec_div
mov cx, 1d
call dec_div
ret
binidec endp
dec_div proc near
mov ax, bx
mov dx, 0
div cx
mov bx, dx
mov dl, al
add dl, 30h
mov ah, 2
int 21h
ret
dec_div endp
; hexibin:接收4位十六进制数的输入
; binidec:输出5位的十进制数值
; 入口参数为BX。
; 出口参数为BX。
hexibin proc near
mov bx, 0
mov cx,4
newchar:
mov ah, 1
int 21h
cmp al, 30h ; 0~9的16进制表示为30~39
jl exit
cmp al, 3ah ; 和10比较,如果小于10的就跳转到add_to
jl add_to
cmp al, 41h ; 和A比较
jl exit
cmp al, 47h ; 和G比较
jge exit
cmp al, 61h ; 和a比较
jl exit
cmp al, 67h ; 和g比较
jge exit
add_to:
push cx
mov cl, 4
shl bx, cl
mov ah, 0
add bx, ax
pop cx
loop newchar
exit:
ret
hexibin endp
; 回车换行
Crlf proc near
push ax
push dx
mov dl, 0dh
mov ah,2
int 21h
mov dl, 0ah
mov ah,2
int 21h
pop dx
pop ax
ret
Crlf endp
end start
宏
宏:源程序中一段有独立功能的程序代码。在使用之前先定义一次,以后就可以多次调用。
宏指令:用户自定义的指令。在编程时,将多次使用的功能用一条宏指令来代替。
汇编语言源程序包含:
- 指令:程序运行时执行的语句
- 伪指令(伪操作):汇编时执行
- 宏指令:汇编时展开
高级语言的宏
C语言中以#define作为标志的编译预处理命令称为宏定义命令。其不带参数的格式为:
#define 标识符 字符串
其中的标识符叫宏名,字符串叫宏体。带参的宏一般形式为:
#define 宏名(参数表) 字符串
如:
#define PI 3.1415926
#define area(r) (3.1415926*(r)*(r))
系统对宏的处理是这样的:当遇到宏名时,就用宏体替换,即所谓的宏替换。这一过程是由预编译程序完成的(不必用户自己操作),而后才将宏替换后的程序交编译程序进行编译。
宏定义、宏调用和宏展开
; 宏定义:
macro_name MACRO [哑元表] ; 形参/虚参
……
…… ; 宏定义体
ENDM
宏调用: (必须先定义后调用)macro_name [实元表] ; 实参
宏展开: 宏定义体->复制到宏指令位置,实参代虚参。
宏和子程序的对比
名称 | 优点 | 缺点 |
---|---|---|
子程序 | 模块化,省内存 | 开销大 |
宏 | 参数传送简单,执行效率高 | 占用内存空间大 |
宏定义中的参数
- 1、宏定义可以无变元(无参数);
- 2、变元可以是常数、寄存器、存储单元名以及用寻址方式能找到的地址或表达式;
- 3、变元可以是操作码或操作码的一部分(必须用&作为分隔符);
- 4、变元可以是ASCII串(字符串)。
- 注意:宏调用时若实参个数少于形参个数会出现编译错误,若实参个数多于形参,则按形参的顺序填入实参,多余部分被忽略。
宏汇编操作符
符号 | 使用 | 说明 |
---|---|---|
& | 符号1 & 符号2 | 宏展开时,合并前后两个符号形成一个符号。 &可以作为哑元的前缀。 |
;; | 宏展开的时候,;; 后面的注释不予展开。 | |
% | % 表达式 | 汇编程序将%后面的表达式转换为当前基数下的数字,并在展开期间用这个数取代哑元。 |
LOCAL伪操作
LOCAL伪操作为每个标号建立唯一的符号(??0000~??FFFF),必须紧跟在MACRO语句之后,中间不允许有任何操作包括注释。
; 宏定义
absol MACRO oper
LOCAL next
cmp oper,0
jge next
neg oper
next:
ENDM
; 宏调用
……
absol var
……
absol bx
……
说明:
反汇编出来内容如下:
宏展开:
……; `absol var`的内容
1 cmp var,0
1 jge ??0000
1 neg var
1 ??0000: ; next的地址
……
……; `absol bx`的内容
1 cmp bx,0
1 jge ??0001
1 neg bx
1 ??0001: ; next的地址
……
从上面可以看出使用了LOCAL伪操作之后,每次调用absol后,next的地址都是不同的。即 LOCAL伪操作为每个标号建立唯一的符号。
列表伪操作
名称 | 说明 |
---|---|
.LALL | 在LST清单中列出宏展开后的全部语句(包括注释)。 |
.SALL | 在LST清单中不列出任何宏展开后的语句。 |
.XALL | 缺省的列表方式,只列出宏体中产生目标代码的语句。 |
宏库的建立和调用
建立宏库
>EDIT MACRO . MAC
macro1 MACRO [哑元表]
……
ENDM
macro2 MACRO [哑元表]
……
ENDM
……
macroN MACRO [哑元表]
……
ENDM
调用宏库
>EDIT EXP.ASM
include MACRO.MAC
……
macro1 [实元表]
……
macro2 [实元表]
…… ; 删除不用的宏定义`purge macroN`
macroN [实元表]
……
输入输出程序设计
不同外设具有的端口数各不相同,计算机中为每一个端口都赋予一个惟一编号——称为端口地址(或端口号PORT)。 8086CPU采用I/O端口独立编址的方式,采用16位地址最多能管理64K个端口,即端口占64KB地址空间,端口号为0~65535。必须使用专门的I/O指令访问端口。
CPU与I/O接口进行通信是通过接口电路内部的一组寄存器实现的,这些寄存器称为端口,包括:数据端口、状态端口和命令端口。
累加器专用传送指令IN/OUT
(只能用AX或AL与端口传送信息)
输入指令
- IN (I/O -> CPU)
长格式:(PORT是端口地址(00~FFH))
- IN AL, PORT (字节)
- IN AX, PORT (字)
执行操作:
- (AL) <- ( PORT ) (字节)
- (AX) <- ( PORT+1, PORT )(字)
短格式:
- MOV DX, PORT
- IN AL, DX (字节)
- IN AX, DX (字)
执行操作:(端口号>255时,先送到DX)
- (AL) <- ( (DX) ) (字节)
- (AX) <- ( (DX)+1, (DX) )(字)
输出指令
- OUT (CPU -> I/O)
长格式:
- OUT PORT, AL (字节)
- OUT PORT, AX (字)
功能:将寄存器中内容输出到指定端口。
短格式:
- MOV DX , PORT
- OUT DX, AL (字节)
- OUT DX, AX (字)
访问端口
in al,60h;
从60h号端口读入一个字节。
执行时与总线相关的操作:
- ① CPU通过地址线将地址信息60h发出;
- ② CPU通过控制线发出端口读命令,选中端口所在的芯片,并通知它,将要从中读取数据;
- ③ 端口所在的芯片将60h端口中的数据通过数据线送入CPU。
- 注意:在in和out 指令中,只能使用 ax 或al来存放从端口中读入的数据或要发送到端口中的数据。
I/O 设备的数据传送方式
- 1、查询方式(程序控制方式)
- 2 、中断方式
- 3 、DMA方式(直接存储器存取方式/成组传送方式)
查询方式
CPU和内存通过端口与外部设备进行通信。CPU在执行主程序过程中,当需要进行I/O操作时,很难保证输入设备已经准备好了数据,或者是输出设备已经处在可以接收数据的状态。因此,一般要在外部设备准备就绪并且I/O接口已经做好数据传送的情况下,才能进行数据传送,这种传送方式称为查询传送方式。
查询过程使CPU很容易与不同速度的外设实现速度配合,使接口电路十分简单,适用于较少数据传输情况下使用。但要占用CPU大量时间去查询I/O设备的状态。
中断传输方式
采用中断方式, CPU执行主程序,等待中断的发生。I/O设备与CPU并行操作,进行数据传输的准备工作。当输入设备将数据准备好,或者输出设备空闲时,便通过I/O接口向CPU发申请中断。CPU在每执行完一条指令之后都会检查是否有中断请求,只要满足中断响应条件,CPU就暂停执行当前的程序,转向执行中断处理程序,进行数据传送,等传送完成后,CPU返回到被中断的主程序,继续进行原来的工作。
中断方式:需要保护现场和恢复现场,数据传输由CPU完成。
DMA方式——成组数据传送方式
主要由硬件DMA控制器实现其传送功能,用于一些高速的I/O设备(比如磁盘),能使I/O设备直接与存储器进行成批数据的快速传送。
DMA方式:用DMA控制器来控制存储器和I/O设备之间的数据传送时,并不经过CPU,传输过程中CPU不占用总线,CPU处于原地等待。这样,传输时就不需要保存断点等额外操作了。另外,整个控制数据块传送的过程,包括地址增量和计数器减量的操作,都是由硬件控制完成的,因而大大缩短了数据传送的控制时间,提高了整个系统的处理效率。
程序直接控制 I/O 方式
I/O 指令是主机与外设进行通信的最基本途径。DOS 功能调用和BIOS例行程序中的输入/输出功能也是由IN和OUT指令完成的。
例:循环测试某状态寄存器的2位是否为1
AGAIN: IN AL, STATUS_PORT; --------状态寄存器的端口地址(00~FFH)
TEST AL, 00000100B
JZ AGAIN
IN AL,DATA_PORT; --------数据寄存器的端口地址(00~FFH)
MOV AL, DATA
OUT DATA_PORT, AL
轮流查询几种I/O设备
轮流查询几种I/O设备:
DEV1: IN AL, STAT1
TEST AL, STAT1_BIT
JZ DEV2
CALL FAR PTR PROC1
DEV2: IN AL, STAT2
TEST AL, STAT2_BIT
JZ DEV3
CALL FAR PTR PROC2
DEV3: IN AL, STAT3
TEST AL, STAT3_BIT
JZ DEV1
CALL FAR PTR PROC3
- 优:由程序安排或修改 设备的优先次序
- 缺:查询等待浪费CPU大量有效时间
- 使用I/O指令直接控制输入输出比调用DOS功能或BIOS例行程序效率更高,但其对硬件的依赖性很大,所以一般的程序设计还是尽可能使用DOS或BIOS功能调用。
I/O程序举例: CMOS RAM 芯片
PC机中有一个CMOS RAM芯片,其有如下特征:
- 1)包含一个实时钟和一个有128个存储单元的RAM存储器。
- 2)该芯片靠电池供电。所以,关机后其内部的实时钟仍可正常工作, RAM 中的信息不丢失。
- 3)128 个字节的 RAM 中,内部实时钟占用0~0DH单元来保存时间信息,其余大部分单元用于保存系统配置信息,供系统启动时BIOS程序的读取。
- BIOS也提供了相关的程序,使我们可以在开机的时候配置CMOS RAM 中的系统信息。
- 4)该芯片内部有两个端口,端口地址为70h和71h。CPU 通过这两个端口读写CMOS RAM。
- 5)70h为地址端口,存放要访问的CMOS RAM单元的地址; 71h为数据端口,存放从选定的CMOS RAM 单元中读取的数据,或要写入到其中的数据。可见,CPU对CMOS RAM的读写分两步进行。
- 比如:读CMOS RAM的2号单元:
- 1、将2送入端口70h
mov al, 2
,out 70h, al
- 2、从71h读出2号单元的内容
in al, 71h
- 1、将2送入端口70h
- 比如:读CMOS RAM的2号单元:
CMOS RAM中存储的时间信息
在CMOS RAM中,存放着当前时间:
类型 | 地址 |
---|---|
秒 | 00H |
分 | 02H |
时 | 04H |
星期 | 06H |
日 | 07H |
月 | 08H |
年 | 09H |
这6个信息的长度都为1个字节,存储了用两个 BCD码表示的两位十进制数,高 4 位的BCD码表示十位,低4 位的BCD 码表示个位。
比如:00010100b表示14。
读取CMOS RAM的信息
要读取 CMOS RAM的信息,我们首先要向地址端口70h写入要访问的单元的地址:
mov al,8
out 70h,al
然后从数据端口71h中取得指定单元中的数据:in al,71h
中断传送方式
中断:使cpu中止正在执行的程序而转去处理特殊事件的操作。
中断源:引起中断的事件。8086/8088CPU最多有256个中断源,这些中断源根据来自CPU的内部还是外部分为两大类:内部中断源和外部中断源。
外中断(硬中断):
- 外设的 I/O 请求 —— 可屏蔽中断INTR
- 电源掉电 / 奇偶错 —— 非屏蔽中断NMI
所谓不可屏蔽中断是指该中断请求不能通过软件的方式对其屏蔽,一旦出现NMI中断请求,CPU必须立即响应。
内中断(软中断10H
):
- INT 指令 / CPU 错(除法错、溢出)
- 为调试程序设置的中断(t、g命令)
80x86 中断源
(图中引线端标示的数字为分配的终端类型号N(0-255))
8259A外部有28个引脚。有9片8259A可构成64级中断源。
中断向量表
各类型(0~0FFH)中断处理程序的入口地址表
中断的条件
(从外设发出中断请求到CPU响应中断,有两个控制条件起决定性作用):
- 设置CPU中断允许位:
- FLAGS 中的
IF = 1
的时候允许中断 ( STI 开中断)IF = 0
的时候禁止中断 ( CLI 关中断)
- FLAGS 中的
- 设置中断屏蔽位:
- 中断屏蔽寄存器的中断屏蔽位
= 0
的时候允许I/O设备请求中断 ,= 1
的时候禁止I/O设备请求中断。
- 中断屏蔽寄存器的中断屏蔽位
修改中断处理程序
DATAS SEGMENT
DATAS ENDS
STACKS SEGMENT
;此处输入堆栈段代码
STACKS ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX
MOV AL,0
MOV AH,35H
INT 21H
PUSH ES
PUSH BX ;保存原向量
PUSH DS
MOV AX,SEG FUNCTION
MOV DS,AX
MOV DX,OFFSET FUNCTION
MOV AL,0
MOV AH,25H
INT 21H ;设置新的向量
POP DS
;--------------------主程序部分
……
;------------------
POP DX
POP DS
MOV AL,0
MOV AH,25H
INT 21H ;恢复原向量
MOV AH,4CH
INT 21H
;--------------中断处理程序
FUNCTION PROC NEAR
……
FUNCTION ENDP
CODES ENDS
END START
CPU中断过程
- 1)取中断类型:CPU ← type N
- 2)保护现场:FLAGS、CS、IP入栈
- 3)IF=0 (关中断), TF=0(禁止单步中断)
- 4)计算中断向量地址,取中断向量:
(4×N)→ IP
,(4×N+2)→ CS
- 5)转中断处理程序
- 以上步骤都由硬件完成。采用向量中断的方法,大大加快了中断处理的速度。因为计算机可直接通过中断向量表转向相应的处理程序,而不需要CPU去逐个检测和确定中断原因。
INT
格式: int n ; n为中断类型码
。
功能:是引发n号中断过程。
CPU 执行int n
过程如下:
- 1)取中断类型码n;
- 2)标志寄存器入栈,
IF = 0,TF = 0
; - 3)CS、IP入栈;
- 4)
(IP) = (n*4)
,(CS) = (n*4+2)
。
- 从此处转去执行n号中断的中断处理程序。
或者这么理解:
- 系统功能号送到寄存器AH中
- 入口参数送到指定的寄存器中
- 用
INT 21H
指令执行功能调用 - 根据出口参数分析功能调用执行情况
AH | 功能 | 入口参数 | 出口参数 |
---|---|---|---|
4CH | 返回DOS | 无 | 无 |
1 | 键盘输入一个字符到AL中 | 无 | AL=字符 |
2 | 输出DL寄存器的字符到显示器 | DL(存放一个字符) | 无 |
9 | 输出一个以“$”结尾的字符串到显示器 | DS:字符串所在的段地址 DX:字符串首地址 | 无 |
0AH | 从键盘输入一个字符串到指定缓冲区 | DS:缓冲区所在的段地址 DX:缓冲区首地址 | 缓冲区相应位置 |
- 更多请见: 汇编常用的INT 21H系统调用
IRET
可见,INT
指令的最终功能和call指令相似,都是调用一段程序。一般情况下,系统将一些具有一定功能的子程序,以中断处理程序的方式提供给应用程序调用。
我们在编程的时候,可以用int指令调用这些子程序,而在子程序中安排iret指令返回。我们将这样的中断处理子程序简称为中断例程。
IRET
指令的执行过程相当于:
pop ip
pop cs
pop flags
中断程序的编写步骤
中断处理程序的编写与子程序类似,先保护现场,再完成功能,然后恢复现场,最后用IRET指令返回,返回地址是中断发生时紧接着的下一条指令。
中断处理子程序:
保存寄存器内容,如允许中断嵌套,则开中断 ( STI )
中断处理功能
关中断(CLI)
送中断结束命令( EOI )给中断命令寄存器
恢复寄存器内容
IRET中断返回
主程序:
1、设置中断向量
2、设置 CPU 的中断允许位IF
3、设置设备的中断屏蔽位
注意:程序员在编程的时候可以调用系统设置好的中断例程,也可以自己编写中断处理程序。 中断类型号0、1、3、4是固定的内部中断,向量2是非屏蔽中断,向量5~31是保留给系统使用的中断,向量32~255则是用户可用的中断。
示例
编写一个中断处理程序,要求在主程序运行期间,每隔 10秒显示一次字符串‘ bell ’。
.model small
.stack
.data
cnt dw 182
mes db 'bell',0ah,0dh,'$'
.code
start:
mov ax, @data
mov ds, ax
mov al, 1ch
mov ah, 35h
int 21h ;取向量1ch
push es
push bx ;保存原向量
push ds
mov dx, offset ring
mov ax, seg ring
mov ds, ax
mov al, 1ch
mov ah, 25h
int 21h ;设置新向量
pop ds
in al, 21h;中断屏蔽寄存器
and al, 11111110b
out 21h, al ;增加定时器中断
sti ;开中断
BIOS和DOS中断
BIOS是固化在PC机内存地址0FE000开始的8KBROM中的基本输入输出系统的例行程序,它为PC系列的不同微处理器提供了兼容的系统加电自检、引导装入、主要I/O设备的处理程序以及接口控制等功能模块,一般以中断处理程序的形式存在。BIOS可以处理所有的系统中断,如键盘、显示器、磁盘、打印、日期与时间等。BIOS是模块化的结构形式,每个功能模块的入口地址都在中断向量表中,对这些中断调用是通过软中断指令INT来实现的。
DOS是IBM PC机的磁盘操作系统,由软盘或硬盘提供。它的两个DOS模块IBMBIO.COM和IBMDOS。COM使BIOS使用起来更方便。其中模块IBMBIO.COM是一个输入输出设备处理程序,提供DOS到BIOS的低级接口,模块IBMDOS。COM包括一个文件管理程序和一些处理程序,把信息传送给IBMBIO.COM,形成BIOS调用。因为DOS模块提供了更多更必要的测试,使DOS操作比使用相应功能的BIOS操作更简易,而且DOS对硬件的依赖性更少些。
用户编程原则
- ①尽可能使用DOS的系统功能调用,提高程序可移植性。
- ②在DOS功能不能实现的情况下,考虑用BIOS功能调用。(比如读打印机状态:BIOS中断17H的功能2)
- ③在DOS和BIOS的中断子程序不能解决问题时, 才使用IN/OUT指令直接控制硬件。(比如声音控制)
中断例程调用方法
一般来说,中断例程中包含多个子程序,内部用AH传递子程序的编号来决定执行哪个子程序。
键盘I/O
- 大多数有用的程序都需要处理用户的输入。
- 键盘输入寄存器的端口地址为60H,控制寄存器的端口地址为61H。
- 键盘上的每个键都有一个扫描码(01~83)。
- ◢ 据扫描码可确定操作的是哪个键、是按下键还是释放键;
- 扫描码用一个字节表示。低7位是扫描码的数字编码(即在键盘上的位置), 最高位D7位表示键的操作状态:
- 当按下键时, D7=0 ,取得通码;
- 当释放键时, D7=1,取得断码。
- 键盘通过键盘接口电路与计算机连接。 当在键盘上“按下”或“放开”一个键时,如果键盘中断是允许的(21H端口的1位等于0),就会产生一个类型9的中断,并转入到BIOS的键盘中断处理程序。
BIOS键盘中断处理程序功能
- ◢ 从键盘接口(60H)读取操作键的扫描码;
- ◢ 将扫描码转换成字符码(大部分键的字符码即相应字符的ASCII码,没有相应ASCII码的键字符码为0。 );
- ◢ 将键的扫描码、字符码存放在ROM BIOS数据区的键盘缓冲区KB_BUFFER( 0040:001A ), 供其它有关键盘的中断子程应用。
BIOS键盘中断(INT 16H)
AH | 功能 | 返回参数 |
---|---|---|
0 | 从键盘读一字符 | AL=字符码,AH=扫描码 |
1 | 读键盘状态并检查是否有字符输入 | 如按下ZF=0,AL=字符码,AH=扫描码,否则ZF=1,缓冲区空 |
2 | 取键盘状态字节 | AH=00,AL=键盘状态字节(KB_FLAG) |
比如指令序列:
MOV AH, 0
INT 16H ;等待按键输入然后取得扫描码和字符码
MOV BX,AX ;用BX传递参数
CALL BINIHEX ;调用子程序将BX转16进制并显示
使用int 16h中断例程读取键盘缓冲区
- int 16h 中断例程的 0 号功能,进行如下的工作:
- 1)检测键盘缓冲区中是否有数据;
- 2)没有则继续做第1 步;
- 3)读取缓冲区第一个字单元中的键盘输入;
- 4)将读取的扫描码送入ah,ASCII 码送入al;
- 5)将己读取的键盘输入从缓冲区中删除。
- 可见,B1OS 的int 9 中断例程和int 16h 中断例程是一对相互配合的程序,int 9 中断例程向键盘缓冲区中写入,int 16h 中断例程从缓冲区中读出。
DOS键盘中断(INT 21H)
AH | 功能 | 调用参数 | 返回参数 |
---|---|---|---|
1 | 从键盘输入一个字符并回显在屏幕上 |
AL = 字符 | |
6 | 读键盘字符,不回显 | DL = 0FFH | 若有字符可取,AL= 字符,ZF=0 若无字符可取, AL=0,ZF=1
|
7 | 从键盘输入一个字符,不回显 |
AL = 字符 | |
A | 输入字符到缓冲区 |
DS:DX = 缓冲区首址 |
(DX+1)= 实际输入字符数 |
B | 检验键盘状态 |
AL=0 表示有输入 ,AL=FF 表示无输入 |
0AH功能的注意事项
- ◢ 输入的字符均带回显,且光标随字符移动。当输入回车符结束时,也回显回车符。表现为功能调用结束后,光标回到了行首。
- ◢ 回车符0DH作为一个输入的字符存放在字符串尾,但计算输入个数时,不包括回车键。实际最多能输入的字符数 = 限制的最多数-1 (回车符占一个)
- ◢ 执行完0AH功能后,DS和DX的值不变, DS:DX仍指向缓冲区的首地址。
- ◢ 整个缓冲区的大小应为:
限制的最多数 +2
,max DB 11, ? , 11 dup (?)
,缓冲区必须定义为字节类型,不能定义为字类型。
显示器I/O
- 1、显示器通过显卡(显示适配器)连接到计算机上。
- 2、单色显示适配器只能显示黑白两色。只能显示ASCII码字符和一些简单字符图形。
- 3、彩色显示适配器能以红、绿、蓝彩色显示以点绘制的图形以及ASCII字符。
- 4、显示器有两种显示方式:
- 文本方式:
- 指以字符为单位显示的方式,字符通常是指字母、数字、普通符号和一些特殊符号(如矩形块等)。
- 图形方式:
- 指以点为单位显示的方式。一个点就是一个像素。
- 文本方式:
- 5、屏幕上各象素的显示信息,存放在显示缓冲区(显存)中。
文本方式
将屏幕划分为 m列和n行 (m × n),在每个网格位置上显示像素,一个字符是一个像素。在这种显示方式下,显示缓冲存储区中存放的是字符的ASCII码和对应的显示属性,每个字符占用两个字节的空间。
图形方式:
将屏幕划分为 m×n的点阵,在每个点的位置显示像素,一个点是一个像素。显示缓冲存储区中存放的是“像素”点的信息,它的值为“0”或者“1”,为“0”就不在屏幕上打点,为“1”则在屏幕上打点。
文本方式属性字节的含义
DOS显示功能调用中断(INT 21H)
AH | 功能 | 调用参数 |
---|---|---|
2 | 显示一个字符(检验Ctrl-Break) | DL = 字符 光标跟随字符移动 |
6 | 显示一个字符(不检验Ctrl-Break) | DL = 字符 光标跟随字符移动 |
9 | 显示字符串 | DS:DX=串地址 串必须以$结束,光标跟随串移动 |
显示存储器
MDA显存的起始地址为B000:0000,CGA、EGA、VGA的是B800:0000。 1屏幕的字符数据称为1页数据。据显存大小,可存储若干页的字符象素。
例: 16KB 显存能存储:(1000B=1KB)
25×80方式,4页( 0 ~ 3 ), 80×25×2×4 =16000
25×40方式,8页( 0 ~ 7 ), 40×25×2×8 =16000
对CGA、EGA、VGA的80列显示方式,0页显存中的起始地址是B800:0000, 1页B800:1000, 2页B800:2000,3页B800:3000。屏幕上某一字符位置在显存中的偏移地址计算公式:
Char_offset=Page_offset+((row*width)+column)*byte
字符偏移地址=页偏移地址+((行号 * 行宽)+列号)* 2
BIOS显示中断(INT 10H)
功能号 AH=0,1, 2, 3, 5, 6, 7, 8, 9, 0AH, 0EH 13H
设置显示方式
- 1.入口参数
AL = 显示方式值
AL显示方式值 | 显示点阵大小 | 显示方式 |
---|---|---|
00 | 40×25 | 黑白文本方式 |
01 | 40×25 | 彩色文本方式 |
02 | 80×25 | 黑白文本方式 |
03 | 80×25 | 彩色文本方式 |
04 | 320×320 | 彩色图形方式 |
- 2.功能号
AH = 00H
- 3.类型号
10H
- 4.出口参数 无
-
5.实现功能 将显示方式设置为指定的形式
例: 将显示方式设置为 25×80彩色文本方式
MOV AL, 03H
MOV AH, 00
INT 10H
控制光标
隐藏光标
;隐藏光标
mov ch,20h
mov cl,00h
mov ah,1
int 10h
光标定位
Int 10h
的功能02
:。
DH和DL寄存器中为光标位置的行列号,BH中为页号(单色显示器页号为0 )。
例:
; -------- 设置光标的位置,光标在第5行第6列(4,5)。
mov dh,4 ; DH是行号
mov dl,5 ; DL是列号
mov bh,0 ; BH是页号
mov ah,2 ; 2号功能
int 10h
读光标位置
功能03 :BH
中指定页号。把光标位置的行号回送给DH
,列号回送给DL
,光标大小的参数填入CH
和CL
。
mov ah,3
mov bh,0
int 10h ;返回参数dh:dl=行:列
清屏和卷屏
功能06(07):使屏幕内容上卷(或下卷)指定的行。需要7个参数。
例:清除屏幕
mov ah, 6 ; ah=功能号
mov al, 0 ; al=指定行数(为0全屏幕空白)
mov bh, 70h ; bh=卷入行属性(白底黑字 )
; -------- 定义窗口属性
mov ch, 0 ; ch=左上角行号
mov cl, 0 ;cl= 左上角列号
mov dh, 24 ; dh=右下角行号
mov dl, 79 ;dl= 右下角列号
; -------- 定义窗口属性
int 10h ; BIOS调用类型10h
显示字符及字符串
功能9(0a
): 把一个字符送到显示屏幕,可直接在cx中设定显示次数,调用结束后光标返回它的初始位置。(0a以正常属性显示)
功能8:读取当前光标位置的字符和属性,入口参数bh=显示页号
,返回ah/al=字符/属性
。
例: 在品红背景下,显示5个浅绿色闪烁的星号。(闪烁很快几乎觉察不到)
MOV AH,09 ; 光标位置下显示
MOV AL,'*' ; 显示字符
MOV BH,0 ; 显示页0
MOV BL,0DAH ; 1 101 1010
MOV CX,05 ; 显示次数
INT 10H ; BIOS调用
显示字符串
- 功能
13H
:显示字符串。 - 参数:
- ES:BP=字符串地址
- AL=写方式(0~3)
- CX=字符串长度
- DH/DL=起始行/列
- BH/BL=页号/属性
- 子功能号
- AL=0,1,要指定整个显示字符串的属性。
- AL=2,3,必须指定每个字符的属性。
显示系统时间
DATAS SEGMENT
CLOCK DB 0,0,':',0,0,':',0,0,'$'
DATAS ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS
START:
MOV AX,DATAS
MOV DS,AX
RESTART:
LEA BX,CLOCK
MOV AL,4
CALL GETTIME
MOV AL,2
CALL GETTIME
MOV AL,0
CALL GETTIME
;------ 设置光标位置
MOV DH,10 ;行
MOV DL,30 ;列
MOV BH,0
MOV AH,2
INT 10H
;------ 隐藏光标
MOV CH,20H
MOV CL,00H
MOV AH,1
INT 10H
;------ 输出时间
LEA DX,CLOCK
MOV AH,9
INT 21H
; ------ 检测键盘输入。
IN AL,60H
CMP AL,1
JNZ RESTART ;按下ESC退出,不断更新时间
MOV AH,4CH
INT 21H
; ------ 获取时间 入口参数:AL
GETTIME PROC
; ------ 读取数据
OUT 70H,AL ; 设定读数地址
IN AL,71H ; 取数
; ------ 左移四位得到十位数值
MOV AH,0 ;清零
MOV CL,4
SHL AX,CL
SHR AL,CL ; 得到个位数值
ADD AH,30H ; 转换成ASCII
ADD AL,30H ; 转换成ASCII
MOV CLOCK[BX],AH
MOV CLOCK[BX+1],AL
ADD BX,3 ; 跳过三个字符->':'
RET
GETTIME ENDP
CODES ENDS
END START