不同的汇编程序有不同的汇编语言编程规定。
目前支持Intel8086/8088系列微机,常用的汇编程序有ASM、MASM、TASM、OPTASM等。
本章主要介绍汇编语言程序设计中的一些基本书写格式与语法规则。
汇编语言的语句可以分为指令语句和伪指令语句
ADDR1: MOV AL,100
LABEL1:ADDAX,BX;
//功能为AX<=(AX)+(BX);
//后面的程序段将完成一次对存储器的访问
指令语句中的标号和伪指令语句中符号名统称为标识符。标识符是由若干个字符构成的。
标识符构成规则:
数据是指令和伪指令语句中操作数的基本组成部分。一个数据由数值和属性两部分构成。
在说明数据时不仅要指定其数值,还需说明它的属性,比如是字节数据还是字数据。
在汇编语言中常用的数据形式有:常数、变量和标号。
常数在汇编期间其值已完全确定,并且在程序运行过程中,其值不会发生变化。
2.134E+10
//汇编程序在汇编源程序时,可以把实数转换为4字节、8字节或10字节的二进制数形式存放。
'B'在内存中为42H,
'ABC'为41H 42H 43H。
//在内存中的存储如图所示。
MOV AX,0B2FOH
ADD AH,64H
MOV BX,32H[SI]
MOV 0ABH[BX],CX
ADC DX,1234H[BP][D]
DB10H
DW 3210H
DATA1 DB 32,30H
DATA1的内容为32(20H),DATA1+1单元内容为30H
STRING1 DB 'ABCDEF' ?
STRING2 DW 'AB','CD','EF'
STRING3 DD 'AB','CD'
DATA_C DB 10H DUP(4 DUP(2),7)
//重复10H个数字序列“2,2,2,2,7”,共占用10H*5=50H个字节。
在指令语句中直接引用变量名就是对其存储单元的内容进行存取
当变量出现在变址(基址)寻址或基址变址寻址的操作数中时表示取用该变量的偏移量。
标号写在一条指令的前面,它就是该指令在内存的存放地址的符号表示,也就是指令地址的别名。
标号主要用在程序中需要改变程序的执行顺序时,用来标记转移的目的地,即作转移指令的操作数。
SUB1 : MOV AX,30H
SUB1_FAR LABEL FAR
SUB1: MOV AX,30H
SUB1 FAR与SUB1两个标号具有相同的段属性和偏移量属性,即相同的逻辑地址。
被转移指令或调用指令访问时,是指同一个入口地址,但SUB1-FAR可以被其它段的指令调用。
LABEL伪指令还可以用来定义变量的属性,即改变一个变量的属性,如把字变量的高低字节作为字节变量来处理。
示例: DATA BYTE LABEL BYTE
DATA WORD DW 20H DUP(?)
在源程序设计中,使用符号定义语句可以将常数或表达式等内容用某个指定的符号来表示。在8086/8088汇编语言中有两种符号定义语句。
语句格式:符号名EQU表达式
功能:用符号名来表示EQU右边的表达式。后面的程序中一旦出现该符号名,汇编程序将把它替换成该表达式。
表达式可以是任何形式,常见的有以下几种情况。
COUNT EQU 5
NUM EQU COUNT+5
ADR1 EQU DS : [BP+14]
ADR1被定义为在DS数据段中以BP作基址寻址的一个存储单元。
CREG EQU CX;``//在后面的程序使用CREG就是使用CX
CBD EQU DAA;``//DAA为十进制调整指令。
格式:符号名=表达式
等号语句与等值语句具有相同的作用。但等号语句可以对一个符号进行多次定义。
CONT=5
NUM=14H
NUM=NUM+10H
CBD=DAA
......
CBD=ADD
表达式是指令或伪指令语句操作数的常见形式。它由常数、变量、标号等通过操作运算符连接而成。
注意:任何表达式的值在程序被汇编的过程中进行计算确定,而不是到程序运行时才计算。
8086/8088宏汇编语言中的操作运算符非常丰富,可以分为以下五类。
+、一、*、/、MOD、SHL、SHR、[]
NUM=15*8; NUM=120
NUM=NUM/7; NUM=17
NUM=NUM MOD3; NUM=2
NUM=NUM+5; NUM=7
NUM=-NUM-3; NUM=-10
NUM=-NUM-NUM; NUM=20
NUM=11011011B
......
MOV AX,NUM SHL 1 //不能修改成SHL NUM,1
MOV BX,NUM SHR 2
ADD DX,NUM SHR 6
上述指令的等效指令为
MOV AX , 110110110B
MOV BX , 00110110B
ADD DX , 3
一般使用格式:表达式1[表达式2]
作用:将表达式1与表达式2的值相加后形成一个存储器操作数的地址。
MOV AX,DA WORD[20H]
MOV AX,DA WORD+20H
MOV AX,ARRAY[BX][SI];基址变址寻址
MOV AX,ARRAY[BX+SI]
MOV AX,[ARRAY+BX][SI]
MOV AX,[ARRAY+SI][BX]
MOV AX,[ARRAY+BX+SI]
MOV AX,ARRAY+BX+SI
MOV AX,ARRAY+BX[SI]
MOV AX,ARRAY+DA_WORD
逻辑运算符有NOT、AND、OR和XOR等四个,它们执行的都是按位逻辑运算。
MOV AX,NOT 0F0H => MOV AX,0FF0FH
MOV AL,NOT 0F0H => MOV AL,0FH
MOV BL,55H AND 0F0H => MOV BL,50H
MOV BH,55H OR 0F0H => MOV BH,0F5H
MOV CL,55H XOR 0F0H => MOVCL,0A5H
关系运算符包括:EQ(等于)、NE(不等于)、LT(小于)、LE(小于等于)、GT(大于)、GE(大于等于)
MOV AX,OFH EO 1111B => MOV AX,OFFFFH
MOV BX,OFH NE 1111B => MOV BX,0
VAR DW NUM LT OABH
该语句在汇编时,根据符号常量NUM的大小来决定VAR存储单元的值,当NUM<0ABH时,则变量VAR的内容为0FFFFH,否则VAR的内容为0。
该类运算符有5个,它们将变量或标号的某些特征值或存储单元地址的一部分提取出来。
DATA SEGMENT
K1 DW 1,2
K2 DW 3,4
MOV AX,SEG K1
MOV BX,SEG K2
//设DATA逻辑段的段基值为1FFEH,则两条传送指令将被汇编为:
MOV AX , 1FFEH
MOV BX , 1FFEH
该运算符的作用是取变量或标号在段内的偏移量。
DATA SEGMENT
VAR1 DB 20H DUP(0)
VAR2 DW 5A49H
ADDR DW VAR2; //将VAR2的偏移量20H存入ADDR中
......
MOV BX,VAR2;
(BX)=5A49H
MOV SI,OFFSET VAR2;
(SI)=20H
MOV DI,ADDR; //DI的内容与SI相同
MOV BP,OFFSET ADDR;
(BP)=22H
作用:取变量或标号的类型属性,并用数字形式表示。对变量来说就是取它的字节长度。
V1 DB 'ABCDE'
V2 DW 1234H,5678H
V3 DD V2
.......
MOV AL,TYPE V1
MOV CL,TYPE V2
MOV CH,TYPE V3
//经汇编后的等效指令序列如下:
MOV AL,01H
MOV CL,02H
MOV CH,04H
该运算符用于取变量的长度。
如果变量是用重复数据操作符DUP说明的,则LENGTH运算取外层DUP给定的值。
如果没有用DUP说明,则LENGTH运算返回值总是1。
K1 DB 10H DUP(0)
K2 DB 10H,20H,30H,40H
K3 DW 20H DUP(0,1,2 DUP(0))
K4 DB 'ABCDEFGH'
......
MOV AL,LENGTH K1; (AL)=10H
MOV BL,LENGTH K2; (BL)=1
MOV CX,LENGTH K3; (CX)=20H
MOV DX,LENGTH K4; (DX)=1
该运算符只能作用于变量,SIZE取值等于LENGTH和TYPE两个运算符返回值的乘积。
MOV AL,SIZE K1;(AL)=10H
MOV BL,SIZE K2;(BL)=1
MOV CL,SIZE K3;(CL)=20H*2=40H
MOV DL,SIZE K4;(DL)=1
这一类运算符用来对变量、标号或存储器操作数的类型属性进行修改或指定。
DA BYTE DB 20H DUP(0)
DA_WORD DW 30H DUP(0)
......
MOV AX,WORD PTR DA BYTE[10]
ADD BYTE PTR DA WORD[20],BL
INC BYTE PTR[BXI
SUB WORD PTR[SI],100
JMP FAR PTR SUB1;//指明SUB1不是本段中的地址
HIGH表达式
LOW表达式
DATA SEGMENT
CONST EQU 0ABCDH
DA1 DB 10H DUP (0)
DA2 DW 20H DUP (0)
DATA ENDS
......
MOV AH,HIGH CONST
MOV AL,LOW CONST
MOV BH,HIGH (OFFSET DA1)
MOV BL,LOW (OFFSET DA2)
MOV CH,HIGH(SEG DA1)
MOV CL,LOW(SEG DA2)
//设DATA段的段基值是0926H,则上述指令序列汇编后的等效指令为:
MOV AH, 0ABH
MOV AL, OCDH
MOV BH, 00H
MOV BL, 10H
MOV CH, 09H
MOV CL, 26H
DA1 DW 1234H
......
MOV AH, HIGH DA1
MOV BH, LOW AX
MOV CH, HIGH[SI]
DATA BYTE EOU THIS BYTE
DATA_WORD DW 10 DUP(0)
......
MOV AX,DATA WORD
MOV BL,DATA BYTE
......
------------------------
LFAR EOU THIS FAR
LNEAR : MOV AX,B
//标号LFAR与LNEAR具有相同的逻辑地址值,但类型不同。
//LNEAR只能被本段中的指令调用,而LFAR可以被其它段的指令调用。
在一个表达式中如果存在多个运算符时,在计算时就有先后顺序问题。不同的运算符具有不同的运算优先级别。
汇编程序在计算表达式时,按以下规则进行运算。
K1=10 OR 5 AND1;结果为K1=11
K2=(10 0R 5)AND1;结果为K2=1
8086/8088在管理内存时,按照逻辑段进行划分,不同的逻辑段可以用来存放不同目的的数据。在程序中使用四个段寄存器CS,DS,ES和SS来访问它们。
在源程序设计时,使用伪指令来定义和使用这些逻辑段。
伪指令SEGMENT和ENDS用于定义一个逻辑段。使用时必须配对,分别表示定义的开始与结束。
- (1)PAGE:表示该段从一个页面的边界开始
- 由于一个页面为256个字节,并且页面编号从0开始,因此,PAGE定位类型的段起始地址的最后8位二进制数一定为0,即以00H结尾的地址。
- (2)PARA:表示该段从一个小节的边界开始
- 如果用户未选定位类型,则缺省为PARA。
- (3)WORD:表示该段从一个偶数字节地址开始,即段起始单元地址的最后一位二进制数一定是0。
- (4)BYTE:表示该段起始单元地址可以是任一地址值。
- 注意:定位类型为PAGE和PARA时,段起始地址与段基址相同。定位类型为WORD和BYTE时,段起始地址与段基址可能不同。
3、组合类型
组合类型说明符用来指定段与段之间的连接关系和定位。它有六种取值选择。
- (1)若未指定组合类型,表示本段与其它段无连接关系。在装入内存时,本段有自己的物理段,因此有自己的段基址
- (2)PUBLIC:在满足定位类型的前提下,将与该段同名的段邻接在一起,形成一个新的逻辑段,共用一个段基址。段内的所有偏移量调整为相对于新逻辑段的段基址。
- (3)COMMON:产生一个覆盖段。在多个模块连接时,把该段与其它也用COMMON说明的同名段置成相同的段基址,这样可达到共享同一存储区。共享存储区的长度由同名段中最大的段确定。
- (4)STACK:把所有同名段连接成一个连续段,且系统自动对SS段寄存器初始化为该连续段的段基址。并初始化堆栈指针SP。
- 用户程序中应至少有一个段用STACK说明,否则需要用户程序自己初始化SS和SP。
- (5)AT表达式:表示本段可定位在表达式所指示的小节边界上。表达式的值也就是段基值。
- (6)MEMORY:表示本段在存储器中应定位在所有其它段之后的最高地址上。如果有多个用MEMORY说明的段,则只处理第一个用MEMORY说明的段。其余的被视为COMMON
4.类别名
段寻址伪指令ASSUME的作用是告诉汇编程序,在处理源程序时,定义的段与哪个寄存器关联。
ASSUME并不设置各个段寄存器的具体内容,段寄存器的值是在程序运行时设定的。
ASSUME ES:NOTHING;//删除前面对ES与某个定义段的关联
ASSUME NOTHING; //删除全部4个段寄存器的设置
段寄存器的初值(段基值)装入需要用程序的方法来实现。四个段寄存器的装入方法略有不同。
MOV ES:DBYTE2[2],AL
SS的装入有两种方法
STACK1 SEGMENT PARA STACK
DB 40H DUP(?)
STACK1 ENDS
......
CODE SEGMENT
ASSUME CS:CODE,SS:STACK1
......
CPU在执行指令之前根据CS和IP的内容来从内存中提取指令,即必须在程序执行之前装入CS和IP的值。因此,CS和IP的初始值就不能用可执行语句来装入。
装入CS和IP一般有下面两种情况。
在程序设计过程中,常常将具有一定功能的程序段设计成一个子程序。在MASM宏汇编程序中,用过程(PROCEDURE)来构造子程序。
汇编程序在汇编源程序时,每遇到一个逻辑段,就要为其设置一个位置计数器,它用来记录该逻辑段中定义的每一个数据或每一条指令在逻辑段中的相对位置。
为了使程序运行结束后,能够正确地返回到操作系统,需要在程序中加上一些必要的语句。一般有以下两种方法。
DOS系统将一个.EXE文件(可执行文件)装入内存时,在该文件的前面生成一个程序段前缀PSP,其长度为100H字节。
同时让DS和ES都指向PSP的开始,而CS指向该程序的代码段,即第一条可执行指令。
如图所示。PSP中一开始就是一条中断指令INT20H,执行该指令将终止用户程序,返回DOS系统。
为了使程序执行完后,正确返回DOS,需要做以下三个操作:
程序结构如下:
执行DOS功能调用4CH,也可以控制用户程序结束,并返回DOS操作系统。
MOV AH,4CH
INT 21H