汇编语言8086
汇编语言
汇编语言(ASM)是用助记符代替操作码、用符号或标号代替地址码或操作数等的面向机器的程序设计语言。使用汇编语言编写的程序,机器不能直接识别,要由一种程序将其翻译成机器语言,这种起翻译作用的程序叫汇编程序。汇编程序是系统软件,用其把汇编语言翻译成机器语言的过程称为汇编。
汇编语言与高级语言相比有许多优越性,像操作灵活、可以直接作用到硬件的最下层,如寄存器、标志位、存储单元等,因而能充分发挥机器硬件性能,提高程序运行效率。此外,与高级语言相比,汇编语言程序经汇编后产生的目标代码较短、执行速度快、所占内存少。当然也存在一些问题,如程序设计者必须熟悉机器内部硬件结构等。汇编语言虽然较机器语言在阅读、记忆及编写方面都前进了一大步,但对描述任务、编程设计仍然不方便,于是产生了既有机器语言优点,又能较好地面向问题的语言,即宏汇编语言(MASM)。宏汇编语言不仅包含一般汇编语言的功能,而且还使用了高级语言使用的数据结构,是一种接近高级语言的汇编语言。
汇编语言的语句
语句格式
汇编语言的源程序是由若干条语句构成的,每条语句可以由4项构成,格式如下。
[标识符] 操作码 操作数 [;注释]
其中,标识符用来对程序中的变量、常量、段、过程等进行命名,它是组成语句的一个常用成分,它的命名应符合下列规定。
- 标识符是一个字符串,第一个字符必须是字母或“?”“@”“_”这4种字符中的一个。
- 从第二个开始,可以是字母、数字及“?”“@”“_”。
- 一个标识符可以由1~31个字符组成,但不能用寄存器名和指令助记符作为标识符。
指令性语句
指令性语句包括4段,格式如下:
[标号:] 操作码 [操作数1] [,操作数2] [;注释]
- 标号段:以“:”分界,该段不是每条指令必需的,为提供其他指令引用而设。一个标号与一条指令的地址符号名(即在当前程序段内的偏移量)相联系。在同一程序段中,同样的标号名只允许定义一次。
- 操作码段:操作码助记符是指令系统规定的。任何指令性语句必须有该段,因为它表明程序中的一个环节,有着一定的操作性质并完成一个操作。操作码助记符通常称为关键字或保留字,用户不能用这些字或词作为变量名、标号、标识符等。
- 操作数段:表明操作的对象,操作数可以是常数、寄存器、标号、变量和表达式。在8086指令系统中,有些操作中可能有不止一个操作对象,有的操作对象隐含在操作码中,若指令中有两个操作数,则需用逗号分界。
- 注释段:语句中以分号开始的部分为注释,这部分不被汇编程序翻译,仅作为对该语句的一种说明,以便程序的阅读、备忘和交流。
注:语句中用方括号括起来的段是可选段。
指示性语句
指示性语句也包括4段,格式如下:
[标识符(名字)] 指示符(伪指令) 表达式 [;注释]
- 标识符段:标识符是一个用字母、数字或加上下划线表示的一个符号,标识符定义的性质由伪指令指定。
- 指示符段:指示符又称为伪指令,是汇编程序规定并执行的命令,能将标识符定义为变量、程序段、常数、过程等,且能给出其属性。
- 表达式段:表达式是常数、寄存器、标号、变量与一些操作符相结合的序列,可以有数字表达式和地址表达式两种。在汇编期间,汇编程序按照一定的优先规则,对表达式进行计算后得到一个数值或一个地址值。
有关属性
存储器操作数的属性有3种,即段值、段内偏移量和类型。
- 段值属性:存储器操作数的段起始地址,此值必须在一个段寄存器中,而标号的段则总是在CS寄存器中。
- 段内偏移量属性:16位无符号数,代表从段起始地址到该操作数所在位置之间的字节数。在当前段内给出变量的偏移量值等于当前地址计数器的值,当前地址计数器的值可以用$来表示。
- 类型属性:标号的属性用来指出该标号在本段内引用还是在其他段中引用。在段内引用,称为NEAR,指针长度为2 B;在段间引用,则称为FAR,指针长度为4 B。变量的类型属性用来指出该变量所保留的字节数,主要是指BYTE(1 B的字节型)、WORD(2 B的字型)或DWORD(4 B的双字型)。
汇编语言中的伪指令
伪指令又称为伪操作,在汇编程序的指示性语句中作为指示符,在对汇编语言源程序进行编译期间,是由汇编程序处理的操作。伪指令可以对数据进行定义、为变量分配存储区、定义程序段或一个过程及指示程序结束等。
本节仅介绍8086中几个常用的伪指令,并给出用伪指令定义的语句格式。
符号定义语句
等值语句
等值语句的格式如下:
符号名 EQU 表达式
在程序中有时会多次出现同一个表达式。为了方便起见,可以用赋值伪操作给表达式赋予一个名字,程序中此后凡需要用到该表达式之处,就可以用这个名字来代替。表达式可以是任何有效的操作数格式,或有效的助记符,或能求出常数值的表达式,甚至是一条可执行的指令。例如:
PORT EQU 1234 (1)
BUFF EQU PORT+58 (2)
MEM EQU DS:[BP+20H] (3)
COUNT EQU CX (4)
ABC EQU AAA (5)
当程序中出现下面的语句时:
MOV AX,PORT
MOV BX,BUFF
根据EQU的功能,上面语句实际上就是:
MOV AX,1234
MOV BX,1292
- 语句(3)中,若要引用加段前缀(段超越)的BP基址寻址,直接用符号名MEM即可。
- 语句(4)中,符号名COUNT定义为CX寄存器,程序中使用CX作为计数器时,就可以直接用COUNT表示。
- 语句(5)中,把符号名ABC定义为一条ASCII码加法调整指令AAA。
在同一源程序中,一个符号名用EQU语句只允许定义一次,若再次定义同一符号名,程序在汇编时会给出语法错误。
等号语句
等号语句的格式如下:
NUM=34
……
NUM=34+1
用“=”为符号名赋值与EQU类似,但等号语句允许对同一符号名多次赋不同的值。即对已赋值的符号名引用过后,可再次赋予新的值,以便下次引用。
变量定义语句
变量定义语句的格式如下:
符号名 DB/DW/DD 表达式
当一个符号名用伪指令DB、DW或DD等定义后就称为一个变量。
- 用DB定义,表明变量为字节型数据(8位)。
- 用DW定义,表明变量为字型数据(16位)。
- 用DD定义,表明变量为双字型数据(32位)。
8086中还可以用DQ、DT等定义变量,在此不一一介绍。
用变量定义语句对变量定义后,变量就有了属性,这些属性可用8086汇编程序中的某些运算分析出来。变量定义后的属性如下:
- 字节型、字型和双字型等数据类型。
- 分配内存单元。
- 按低位字节数据存放在低地址单元、高位字节数据存放在高地址单元的原则(或称反向存储)给内存赋值。
- 变量定义一般在数据段中,故一个变量被定义后,就有了段地址和在该段的偏移地址。
变量定义语句中的表达式形式有多种,定义的功能也有所不同。下面给出变量定义语句的具体形式。
定义一组数据
例如:
BUFF1 DW 1234H,0ABCDH,8EH,-79DH
BUFF2 DB 12H,34H,CDH,8EH
定义BUFF1为字型变量,共有4个参数;定义BUFF2为字节型变量,有4个参数。这8个参数放在内存中,地址默认为从某段的偏移地址为0000处开始放数据。
对于BUFF1,按反向存储的原则,每个数据占2B。第1个参数为十六进制数;第2个参数为字母开头的十六进制数,为与符号名区分开,在以字母开头的十六进制数前加0;第3个参数看上去为8位数,但定义时用的是DW,故为其分配2 B,汇编程序会将高8位补0;第4个参数是带符号的,从前面的章节可知,带符号数在计算机中以补码形式存放,故负数-79DH以F863H存放在存储单元中。
定义一串字符
例如:
STR DB 'Welcome!'
定义了STR为一个字节型变量,单引号内表示是字符串,字符以ASCII码的形式存放,每个字符占1 B。由于存储单元以B为单位组织,超过8 bit的数据要按反向存储的原则存放,故对于字符串只用DB定义。
定义保留存储单元
在程序设计中,如果希望将运算结果保存到内存中,则在设计中就要预留一部分存储单元。也就是说,这些内存单元不需要预先赋值。无论是存储器还是寄存器,它们都是一种双稳态门电路,故每一位(bit)不是“1”就是“0”,这些器件中总是有值,当没有特定赋值时,其中有随机值。例如:
SUM DW ?,?
从SUM偏移地址开始,为两个字型数据保留了4 B的内存单元。
复制操作
复制操作符DUP(Duplication)可以预置重复的数值,DUP之前的数字表示重复的次数,DUP后面由括号将重复的内容括起来。例如:
ALL_ZERO DB 0,0,0,0,0
将连续的5个字节赋0。若用复制操作,可改为:
ALL_ZERO DB 5 DUP(0)
可达到同样的效果。
将已定义的地址存入内存单元
定义过的标号或用PROC过程定义过的过程名(子程序名或中断服务程序名)都有段地址和偏移地址属性。若希望将变量、标号或过程名的段地址和偏移地址保存到存储单元,可以用下面的方式完成。例如:
LIT DD CYC
……
CYC:MOV AX,BX
将标号CYC的段地址和偏移地址存放在以变量LIT开始的4 B单元中。有时可以将程序中所有子程序的首地址列在一起,采用查表的方式动态地转入所需的子程序入口。
段定义语句
段定义语句可按段来组织程序和使用存储器。这些语句包括SEGMENT、ENDS、ASSUME、ORG等。
段定义语句格式
段定义语句的格式如下:
段名 SEGMENT [定位类型][组合类型][类别]
……
段名 ENDS
存储器的物理地址是由逻辑段基地址和逻辑偏移地址组合而成的,语句SEGMENT和ENDS把汇编语言源程序分成段,这些段就相应于存储器区段。
对于段内指令的转移和调用,在指令中只需包含目标地址单元的16位偏移量,在段间的转移和调用指令才需要给出段地址和偏移地址。使用当前数据段和当前堆栈段的数据访问指令,也只需在指令中给出数据所在内存单元的16位偏移地址。8086按照规定的组合方式生成物理地址。
当指令中访问的是当前段之外的数据单元时,必须在指令中加入段超越前缀或修改段寄存器的内容。汇编程序在将源程序转换成目标程序时,必须确定标号和变量的偏移地址,并且需要把有关信息通过目标模块传递给连接程序,这样才能把解决同一个问题的不同段、不同模块的程序缝接起来,形成一个可执行程序。
段定义语句中的SEGMENT和ENDS必须成对出现,其中省略号部分可以是汇编语言中的指令性语句和指示性语句。
段名可以是包括下划线在内的字母、数字串,由程序设计者自定。定位类型、组合类型、类别是赋给段名的属性,加上方括号表示这些属性可以省略,省略表示该程序段与其他段没有联系,是独立的;若不能省略,各属性项的书写顺序不能错,并以空格分界。
- 定位类型 定位类型表示此段在内存中的起始边界要求,可以是PAGE(页)、PARA(节)、WORD(字)、BYTE(字节)。
PAGE要求该段从页的边界开始,段地址能被256整除,即十六进制的段地址最后两位为0。
PARA要求该段从节的边界开始,段地址能被16整除,即十六进制的段地址最后一位为0。当定位类型省略时,隐含为PARA。
WORD要求该段从字的边界开始,段地址为偶数值。
BYTE可以从该段边界任何地址开始。
- 组合类型 组合类型用来告诉链接程序本段与其他段的关系,包括NONE、 PUBLIC、COMMON、STACK、MEMORY和AT。
NONE:表示本段与其他段逻辑上没有关系,每段都有自己的基地址。组合类型省略时属于该类型。
PUBLIC:链接程序先把本段与其他模块中同名、同类别的段相邻地链接在一起,然后为所有段指定一个共同的段基地址,将它们链接成一个物理段。各段的链接顺序由链接命令指定。
COMMON:链接程序为本段与其他模块中同名、同类别的段指定一个相同的段基地址。这些段可以相互覆盖,段的长度取决于最长的COMMON段。
STACK:规定被链接的程序中必须有至少一个STACK属性的段,即堆栈段。如果多于一个,则在初始化时会将第一个STACK段的地址送入SS寄存器。而段与段之间的链接按PUBLIC方式处理。
MEMORY:链接程序把本段定位为几个互连段中地址最高的段。若有多个MEMORY段,链接程序认为所遇到的第一个为MEMORY,其余段则具有COMMON属性。
AT表达式:链接程序将表达式计算出来的16位地址作为段地址,但不能用来指定代码段,这个类型使得在某一固定的存储区内的某一固定偏移地址处定义标号或变量,以便程序以标号或变量形式访问这些存储单元。
- 类别
类别可以是任何合法的名称,用单引号括起来,如’STACK’、’CODE’。在定位时,链接程序把同类别的段集中在一起。
段假设语句
ASSUME伪指令在汇编时能提供正确的段码,使汇编程序知道程序的段结构以及在各种指令执行时该访问哪一段。其格式如下:
ASSUME 段寄存器名:段名[,…]
段寄存器可以是CS、DS、SS或ES,而段名则是用SEGMENT定义过的标识符。因一个程序中可能不止一个程序段,方括号中表示可按实际情况添加。
在程序中,ASSUME伪指令只是指定某段分配给哪个段寄存器,并不能把段地址装入寄存器中(因为伪指令不由CPU执行)。
ORG伪指令与地址计数器$
ORG伪指令的格式如下:
ORG <表达式>
此语句指定在它之后的代码或数据存放的起始地址的偏移量,以表达式的值作为起始地址,连续存放程序或数据,除非遇到一个新的ORG语句。
任何时候在使用存储器时,先要给出存储单元地址。汇编程序在汇编时给出一个隐含的地址计数器,“$”是地址计数器的值,也就是当前所使用的存储单元的偏移地址。
PUBLIC和EXTRN伪指令
当一个程序由多个模块组成时,必须通过命令将各模块连接成一个完整的、可执行的程序。程序模块是指单独编辑和汇编的、能够完成某个功能的程序,如主程序模块、各种功能的子程序模块等。正因为它们是独立汇编的,故在程序编写时要使用EXTRN伪指令,表示本模块引用了在其他模块中定义的信息;使用PUBLIC伪指令,表示本模块提供被其他模块使用的信息。
PUBLIC伪指令
该伪指令表示在链接时,本模块中能够提供其他模块使用的名字。其格式如下:
PUBLIC 名字[,…]
其中,“名字”可以是模块中定义的一个变量或标号(包括过程名)。PUBLIC伪指令可在一个汇编模块的任何一行出现,未经定义的符号名字不能被说明为PUBLIC。
EXTRN伪指令
EXTRN伪指令把某些名字的段和类型的属性告诉汇编程序,这些名字是本模块所要用的,但是它们在其他模块中定义。其格式如下:
EXTRN 名字:类型[,…]
其中,“名字”是其他模块中定义过的,类型必须与说明它为PUBLIC的模块中的类型一致。
过程定义语句
在汇编语言中,用过程的定义来实现子程序功能。过程是程序的一部分,它们可被程序调用,每次可调用一个过程。当过程中指令执行完后,控制返回调用点。段间的调用指令把过程返回的段地址和偏移地址同时推入堆栈,而段内的调用指令只将偏移地址入栈。
过程定义语句的格式如下:
过程名 PROC NEAR/FAR
……
RET
过程名 ENDP
其中,“过程名”为标识符,又是子程序入口的符号地址;NEAR或FAR是类型属性,NEAR属性是指该过程是一个段内的调用,而FAR则指段间的调用,当属性省略时,自动设为NEAR。伪指令PROC和ENDP必须成对出现。为了保证过程正确返回,CALL指令的类型必须与过程的类型相匹配。
过程定义语句可把程序分段,以便理解、调试和修改。若整个程序由主程序和若干个子程序组成,则主程序和这些子程序都应包含在代码中。
一般在过程的最后是一条RET语句,表示从栈顶弹出返回地址,以便返回调用点。ENDP则告诉汇编程序,该过程在哪里结束。
过程是可以嵌套的。一个过程中可以包括多个过程定义,堆栈的大小决定嵌套的深度,但过程不允许交叉。
结束语句
编辑结束语句——END
一个汇编语言源程序,而该程序不能单独执行,只是一个程序段时,可能还要与其他程序段链接,这时该程序的结束处应写上一条END语句。它告知汇编程序到此汇编结束,可以形成一个独立的文件。
可执行程序结束语句——END标号
一个可执行的汇编语言源程序,在程序结束处都应写一条带标号的END语句。一个可执行程序只能有一条这样的语句,END后的标号是该程序要执行的第一条语句所在的存储器地址,这样汇编程序在汇编时,将该存储器的段地址送代码段寄存器CS,将存储器偏移地址送指令指针寄存器IP,也就是开始执行的指令位置由CS:IP指定了。
汇编语言中的运算符
常用运算符和操作符
在8086汇编语言程序的指示性语句中可以有表达式。表达式中可以使用3种运算符和两种操作符。
算术运算符
算术运算符包括+(加)、-(减)、*(乘)、/(除)、MOD(求余),可以用于数字操作数或存储器地址操作数的运算中。用于地址表达式时,只有当结果有明确的物理意义时,算术运算符才有效,如对存储器地址操作数,有意义的运算符是“+”“-”。如果把两个不同段的偏移地址做加(减)运算是没有物理意义的。
MOV AX,15*4/7 ;AX=0008H
ADD AX,60 MOD 7 ;AX=8+4=12
MOV CX,-2*30-10 ;CX=-70
逻辑运算符
8086汇编的4种逻辑运算符分别为AND、OR、XOR和NOT,它们只适用于数字操作数,其运算规则与逻辑运算规则相同。逻辑运算符与8086逻辑运算指令的助记符一样,但作为汇编的运算符时,由汇编程序在汇编时计算出结果,该结果作为指令性语句的操作数使用。
MOV AL,NOT 10101010B ;等效于MOV AL,01010101B
OR AL,10100000B OR 00000101B ;等效于OR AL,10100101B
XOR AX,0FA0H XOR 0F00AH ;等效于XOR AX,0FFAAH
关系运算符
关系运算符连接的两个运算对象,必须都是数字或是同一段内的存储器地址。关系运算符有6种,即EQ(相等)、NE(不等)、LT(小于)、GT(大于)、LE(小于等于)和GE(大于等于)。
关系运算符的运算规则是:两个运算对象的关系是否满足某种关系,若满足,结果为全“1”;否则,结果为“0”。例如:
MOV DL,10H LT 16
其中的源操作数在汇编时由汇编程序进行关系运算。根据运算规则可知,10H不小于16,其关系不成立(假),结果为0。故上述指令实际上就是“MOV DL,0”,指令执行后,DL寄存器的内容为0。又如:
AND AX,555 GT 222
其中的源操作数关系满足。指令执行后,AX的内容和FFFFH做“与”操作,则AX原内容不变。
分析操作符
分析运算能将定义过的变量、标号的存储器地址分解成它们的组成部分,如逻辑段基址、逻辑偏移地址、类型等。比如,有变量定义语句:
BUFF DW 1234H
变量名BUFF所携带的信息非常丰富,如地址信息,包括偏移地址、段基地址,变量的类型是字型,变量的内容等。例如,下面的指令:
MOV AX,BUFF
就是把名字为BUFF的内存单元的内容送给寄存器AX,对于目的操作数来讲是寄存器寻址,而对于源操作数来讲是直接寻址。该指令执行完毕后,寄存器AX的内容为1234H。
上述指令是读取内存单元的操作,那么如果指令的功能是取名字BUFF所代表的地址或类型,那将如何操作呢?这就要使用分析操作符完成此要求了。
SEG操作符。
例如:
MOV AX,SEG BUFF
设变量BUFF已在数据段中定义过,上面指令执行后,AX寄存器有变量BUFF所在段的段地址。
OFFSET操作符。
例如:
MOV BX,OFFSET BUFF
设变量BUFF已在数据段中定义过,上面指令执行后,BX寄存器有变量BUFF所在段的偏移地址。
又如:
LEA BX,BUFF
上面两语句执行后有相同的结果,区别在于:第一个语句是由汇编程序在汇编阶段,通过分析运算OFFSET求得变量BUFF的偏移地址,然后由CPU运行MOV指令,将它作为源操作数传送到BX寄存器;而第二个语句是直接由CPU执行有效地址传送指令完成的。
TYPE操作符。
TYPE运算可求出变量或标号的类型,类型用数字表示,对于变量有3种:1——字节型、2——字型、4——双字型;对于标号有两种:-1——NEAR(段内)、-2——FAR(段间)。例如:
DATA SEGMENT
VAL1 DB 12H,8EH
VAL2 DW 0A234H,5B78H
VAL3 DD 9457B68DH
DATA ENDS
CODE SEGMENT
ASSUME DS:DATA,CS:CODE
START:MOV AX,DATA
MOV DS,AX
MOV DL,TYPE VAL1
MOV BL,TYPE VAL2
MOV CL,TYPE VAL3
MOV AX,4C00H
INT 21H
CODE ENDS
END START
上面是一个完整的汇编语言源程序,对于它的程序结构和格式在稍后进行解释,这里要了解的是关于TYPE运算符,程序可以通过上机调试得到结果。程序运行的结果是DL的内容为1,BL的内容为2,CL的内容为4。若将其中的TYPE运算符换成SEG、OFFSET、LENGTH和SIZE运算符,并对程序相应处稍加修改,就可在计算机上得到另一种结果,请读者对此自行分析。
LENGTH操作符。
LENGTH运算对使用DUP定义过的变量求元素个数,对其他方式定义的变量总是给出1作为结果。例如:
BUFF DW 10 DUP(?)
当执行以下命令:
MOV CL,LENGTH BUF
则CL的内容为10。注意:操作符LENGTH只对DUP有效。如果DW定义的是一系列数字,如上例中的VAL2,则LENGTH对其作用后返回的值为1。
SIZE操作符。
该操作符可以分析出一个使用DUP定义过的变量所有元素所分配的内存字节数。它与变量的类型和元素个数有关:
SIZE=TYPE×LENGTH
综合运算符(合成操作符)
PTR运算符。
PTR运算符的格式如下:
类型 PTR 表达式
对一个存储器操作数,不管原来是何种类型,现在以PTR前的类型为准。也就是说,PTR能建立一个存储器操作数,它与其后的存储器操作数有相同的段地址和偏移量,但有不同的类型。PTR仅仅为已分配存储器单元的操作数赋予另外的意思(PTR不为存储器操作数分配内存单元)。
例如:
INC WORD PTR[BX]
ADD BYTE PTR[SI],4BH
汇编程序在汇编上面两个语句时,PTR操作符指明其中由BX和SI寻址的存储器操作数的类型,以便能正确汇编出二进制目标码。
THIS操作符。
THIS操作符的格式如下:
THIS 类型(或属性)
THIS可以像PTR一样建立一个指定类型(BYTE、WORD或DWORD)或指定距离(NEAR、FAR)的存储器地址操作数,但并不为其分配存储单元。所建立的存储器操作数的段地址和偏移地址与下一个存储单元地址相同。例如:
FIRST EQU THIS BYTE
SECOND DW 100 DUP(?)
此时,FIRST的偏移地址值和SECOND完全相同,但FIRST是字节型,SECOND是字型。
运算符的优先级
在使用以上5种类型的常用运算符或操作符计算表达式的值时,应按规定的优先级进行,程序设计者在写表达式时应注意。优先级别从高到低排序如下:
• 圆括号,LENGTH,SIZE。
• PTR,OFFSET,SEG,TYPE,THIS。
• *,/,MOD。
• +,-。
• EQ,NE,LT,LE,GT,GE。
• NOT。
• AND。
• OR,XOR。
汇编语言程序设计
综合前面的知识,可以使用两种语句来设计一个汇编语言源程序。而程序设计首先要将问题分解成一个一个的步骤,每步都可以用汇编语言中的指令性语句,按照先后顺序表达。
设计一个好的程序,不仅要满足设计要求,能正常运行,实现预定功能,还应满足以下条件。
①结构化、简明、易读、易调试、易维护(修改和扩充)。
②执行速度快。
③占用存储空间尽量少。
执行速度和占用存储空间两者有时是矛盾的,这两个指标往往不能同时满足,在许多情况下要加以权衡,看哪个指标对于程序设计更重要。对于较大的程序,如何使程序结构化、模块化,便于阅读、调试,以及与其他程序的方便链接,则显得更加重要。
汇编语言程序设计的步骤如下。
(1)分析问题,抽象出问题的数学模型,确定解决问题的合理算法。
(2)绘制流程图或写出程序步骤,可以从粗到细地把算法逐步具体化。
(3)分配存储空间及工作单元,根据流程图编写程序。
(4)静态检查,设计者仔细阅读所设计的程序,尽量找出如语法、逻辑等错误。
(5)在计算机上调试程序。
在调试程序的过程中,读者应该善于利用计算机提供的软件调试工具,编程技巧是通过大量的阅读程序和自己动手编制程序的实践中获得的。我们提供一个汇编语言程序调试的集成环境,在此环境中调试汇编语言程序更方便,它具备编辑、汇编、链接、运行调试等一系列功能。这个集成环境还可以用不同颜色表示汇编语言语句中的不同部分,如操作码的助记符用红色、伪指令用绿色、寄存器名用蓝色等,如果操作码输入错误便不会变成红色。这样可以提示设计者,在程序编辑过程中不易造成语法错误。
3.4.1 顺序程序设计 顺序结构程序一般是简单程序,它是顺序执行的,无分支,无循环,也无转移,因此也称为直线程序。顺序结构程序设计如下。
【例3.3】 阅读下面程序,指出程序的运行结果。
DATA SEGMENT
BLOCK DW 0ABCDH
BUFF DD ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:MOV AX,DATA
MOV DS,AX
MOV DX,BLOCK
MOV AX,DX
AND AX,0F0FH
AND DX,0F0F0H
MOV CL,4
SHR DX,CL
LEA BX,BUFF
MOV [BX+0],AL
MOV [BX+1],DL
MOV [BX+2],AH
MOV [BX+3],DH
MOV AX,4C00H
INT 21H
CODE ENDS
END START 说明:
(1)该程序有两个程序段——以DATA为段名的数据段和以CODE为段名的代码段。
(2)数据段定义了一个字型的变量BLOCK,并赋值为十六进制数(因该数以字母开头,故在数据前加上0);定义另一变量BUFF为双字型,实质上是保留4 B单元,用于存放结果。
(3)程序的主要功能是将字型数据转换成4 B型数据,存储在BUFF缓冲区中。
(4)程序最后采用DOS功能(后面将介绍)结束。
(5)运行结果为:从存储器缓冲区BUFF开始,顺序存入0DH、0CH、0BH和0AH。
3.4.2 分支程序设计 分支结构程序是指程序在按指令先后的顺序执行过程中,遇到不同的计算结果值,需要计算机自动进行判断、选择,以决定转向下一步要执行的程序段。计算机的智能化、分析判断能力就是这样实现的。分支程序一般是利用比较、转移指令来实现的:用于比较、判断的指令有两数比较指令CMP、串比较指令CMPS、串搜索指令SCAS,用于实现转移的指令有无条件转移指令JMP和各种类型的条件转移指令,它们可以互相配合实现不同情况的分支。多路分支情况可以采用多次判断转移的方法实现,每次判断转移形成两路分支,n 次判断转移形成n +1路分支,也可以利用跳转表来实现程序分支。
分支程序一般根据条件判别,满足条件则转移到标号所指示的程序部分执行,不满足条件则执行下一条指令。
【例3.4】 判断MEMS单元数据,将结果存入MEMD单元。若数据大于0,结果为1;若数据小于0,结果为-1;若数据等于0,结果为0。代码如下:
MY_D SEGMENT
MEMS DB 08H
MEMD DB ?
MY_D ENDS
MY_C SEGMENT
ASSUME DS:MY_D,CS:MY_C
START:MOV AX,MY_D
MOV DS,AX
MOV AL,MEMS ;取数据进行判别
CMP AL,0
JGE NEXT ;≥0,转移
MOV AL,-1 ;<0,结果为-1
JMP DONE
NEXT: JE DONE ;为0,则结果为0
MOV AL,1 ;否则,结果为1
DONE: MOV MEMD,AL
MOV AX,4C00H
INT 21H
MY_C ENDS
END START 说明:
(1)该程序是一个典型的分支结构程序,根据一个数据的3种情况,执行3个分支程序段。
(2)程序转移的根据是与0比较,这是一个条件判断转移。由于参加比较的数据是带符号数,故使用“大于”“不大于”等条件判别,此处用“大于等于”即JGE为条件。
(3)由于一次分支只有两路,故从JGE判别分支后,有第二次判别分支即JE。
(4)由于本例中被测数据为正数,所以结果为1。
(5)若改变MEMS单元的数,可以得到不同的结果。
(6)对于多分支的程序,在上机调试时各分支程序段都应进行检验。
3.4.3 循环程序设计 程序中的某些部分需要重复执行,设计者不可能将重复部分反复地书写,那样程序会显得很冗长。只要选好参数,将程序中重复执行部分构成循环结构,这样设计的程序既美观又便于修改。
循环结构每次测试循环条件,当满足条件时,重复执行这一段程序;否则结束循环,顺序往下执行。由于循环程序需要循环准备、修改变量、结束控制等指令,执行的速度会稍慢些。
在循环程序设计中,循环控制有以下3种。
①采用计数法(一般用减1计数)。当循环控制次数已知时,常用方法是将循环控制次数送到CX寄存器,每做完一次循环,利用LOOP指令减1计数,并判断循环是否结束。
②比较条件结束。当循环控制次数未知时,采用此方法。满足比较条件,则结束循环;否则继续做循环操作。
③设定标志结束。当循环内又套循环,而循环次数又未知时采用此方法,如设0FFH为结束标志。
【例3.5】 编程统计BUFF缓冲区数据中负数的个数。
DATA SEGMENT
BUFF DB 67H,9EH,-6AH,0ABH,6DH
MEM DB ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:MOV AX,DATA
MOV DS,AX
MOV CX,5 ;循环控制次数
LEA BX,BUFF ;设置缓冲区指针
XOR DL,DL ;统计计数器清零
NEXT: MOV AL,[BX] ;取数据
ADD AL,0 ;做运算,影响标志
JNS AA1 ;是正数,转移
JNC DL ;是负数,统计加1
AA1: INC BX ;移动指针
LOOP NEXT ;循环控制
MOV MEM,DL ;保存统计结果
MOV AX,4C00H
INT 21H
CODE ENDS
END START 说明:
(1)程序的初始化包括设置缓冲区指针、设定循环控制次数、统计计数器先清零。
(2)执行部分有从BUFF缓冲区取数,进行算术运算;判断符号标志SF,是负数,则统计值加1。
(3)修改部分含移动缓冲区指针,循环次数减1。
(4)循环控制部分为CX内容不为0时,继续循环操作;否则脱离循环。
(5)结束处理是将统计结果(DL寄存器中)存入MEM单元,且将控制权交操作系统。
【例3.6】 编程统计AX寄存器中“1”的个数。
CODE SEGMENT
ASSUME CS:CODE
START:MOV CX,16 ;循环控制次数
XOR DL,DL ;统计计数器清零
CMP AX,0 ;AX的内容为0吗?
JZ DONE ;是0,结束循环
BB1: SHL AX,1 ;否则移动AX
ADC DL,0 ;统计“1”的个数
LOOP BB1
DONE: MOV AX,4C00H
INT 21H
CODE ENDS
END START 说明:
(1)程序的初始化包括设定循环控制次数、统计计数器先清零。
(2)先判断循环是否继续,若AX内容为0,则没有必要循环。
(3)执行部分采用移位操作,并用ADC指令统计CF的值,避免了程序分支。
【例3.7】 在BLOCK内存区中有一串字符,试编程统计“%”之前的字符个数。
DATA SEGMENT
BLOCK DB 'ANDEP0139%WR'
COUNT EQU $-BLOCK
MEM DB 0
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:MOV AX,DATA
MOV DS,AX
MOV SI,OFFSET BLOCK
MOV CX,COUNT
LOOP1:MOV AL,[SI] ;取字符
CMP AL,'%' ;是%号吗
JZ DONE ;是,结束循环
INC BYTE PTR MEM ;否,统计值加1
INC SI ;移动指针
LOOP LOOP1 ;继续循环
DONE: MOV AX,4C00H
INT 21H
CODE ENDS
END START 说明:
(1)对于字符变量的定义,必须用DB伪指令,因为一个字符的ASCII码是7位二进制数值。
(2)本例中采用内存地址关系($-BLOCK)自动计算循环控制次数。
(3)本例中的统计计数器直接在内存单元MEM中。
3.4.4 子程序设计 将一个具有特定功能的代码块定义为一个过程(或子程序)。该过程可以与主程序在同一段,这时过程的属性为NEAR,即主程序调用时只将IP寄存器的值入栈保存;当过程与主程序在不同段时,过程的属性为FAR,主程序调用时,CS和IP寄存器的值都要入栈保存。子程序的最后一条语句是RET,执行该语句,返回地址从堆栈中弹出,控制返回到被调用处。
【例3.8】 用子程序结构编写寄存器AX内容乘10,结果仍在AX中。
XX EQU 1000
CODE SEGMENT
ASSUME CS:CODE
START:MOV AX,XX ;把AX赋值为,000=03E8H
CALL MUL10 ;调用把AX内容乘10子程序
MOV AX,4C00H
INT 21H
MUL10 PROC ;一个将AX乘10的子程序,入口参数是AX,出口参数是AX
PUSHF ;保护现场,保护标志寄存器和BX
PUSH BX ;下面是功能程序段,实现10*AX→AX
ADD AX,AX ;2XX→AX
MOV BX,AX ;2XX→BX
ADD AX,AX ;4XX→AX
ADD AX,AX ;8XX→AX
ADD AX,BX ;8XX+2XX→AX
POP BX ;恢复现场
POPF
RET
MUL10 ENDP
CODE ENDS
END START 说明:
(1)该程序只用了代码段,未用数据段,所以程序只对代码段进行了定义。
(2)主程序对AX进行赋值,然后调用子程序MUL10对AX内容进行乘10的操作。
(3)子程序MUL10包括以下5部分,即子程序功能说明、入口和出口参数说明、保护现场、实现具体操作的功能段程序及恢复现场。一个标准的子程序都应该具备这5部分。
(4)由于子程序使用了加法指令,它将影响标志寄存器;程序中还使用了BX作为中间变量,所以在子程序保护现场部分,用PUSH指令把标志寄存器和BX推入堆栈,完成对这两个寄存器的现场保护。
(5)在功能程序段中,利用加法指令先后完成了2倍XX的操作和8倍XX的操作,然后把2XX和8XX相加实现了10XX。
(6)在恢复现场部分,用POP指令从堆栈中推出两个数,分别送给BX和标志寄存器,完成了恢复现场的操作。要注意的是,现场恢复过程是按照先进后出的操作顺序。
3.5 DOS系统功能调用和BIOS中断调用 3.5.1 DOS系统功能调用 PC-DOS(也称为IBM-DOS或MS-DOS)是美国微软公司为IBM-PC微机研制的磁盘操作系统。它不仅提供了许多命令,还给用户提供了80多个常用子程序。DOS功能调用就是指对这些子程序的调用,也称为系统功能调用。子程序的顺序编号称为功能调用号。
DOS功能调用采用软中断指令“INT n”实现,调用范围为INT 20H~INT 3FH。其中,“INT 21H”是一个大型中断处理程序(也称为DOS系统功能调用),它又细分为很多子功能处理程序,可供分别调用。
DOS系统功能调用的一般过程是:将调用号放入AH中,设置入口参数,然后执行软中断语句“INT 21H”。
1.基本的输入与输出
(1)AH=01H,输入一个字符。
程序:
MOV AH,01H
INT 21H 上述指令执行后,系统等待从键盘输入一个字符,输入后将该字符显示在屏幕上,并且将该字符放入AL寄存器。按“Ctrl”+“Break”组合键,程序自动返回到DOS控制下。
(2)AH=02H,输出一个字符。
功能:将DL中的字符输出到屏幕。
程序:
MOV DL,'A'
MOV AH,02H
INT 21H 执行结果,在屏幕上显示字符A。
(3)AH=05H,输出一个字符到打印机。
功能:将DL寄存器的字符输出到打印机。
(4)AH=09H,输出字符串。
功能:把DS:DX所指单元内容作为字符串首字符,将该字符串逐个显示在屏幕上,直至遇到串尾标志“$”为止。
(5)AH=0AH,输入字符串。
功能:从键盘接收字符串到DS:DX所指内存缓冲区。要求内存缓冲区的格式为:首字节指出计划接收字符个数,第二个字节留作机器自动填写实际接收字符个数,从第三个字节开始存放接收的字符。若实际输入字符数少于指定数,剩余内存缓冲区填零;若实际输入字符数多于指定数,则多出的字符会自动丢失。若输入RETURN,表示输入结束,DOS系统自动在输入字符串的末尾加上的回车字符不被计入实际接收的字符数中。
2.文件管理
文件:文件是具有名字的一维连续信息的集合。DOS以文件的形式管理数字设备和磁盘数据。
文件名:在DOS文件系统中,文件名是一个以零结尾的字符串,该字符串可包含驱动器名、路径、文件名和扩展名,如C:\SAMPLE\IVIY.ASM。
文件管理:将工作文件名和一个16位的数值相关联,对文件的操作不必使用文件名,而直接使用关联数值,这个数值称为文件称号。文件管理从PC-DOS 2.0版本开始引入。
DOS文件管理功能:包括建立、打开、读写、关闭、删除、查找文件以及有关的其他文件操作。这些操作是相互联系的,如读写文件之前,必须先打开或建立文件,要设置好磁盘传输区或数据缓冲区,然后才能读写,读写之后还要关闭文件等。文件管理中的最基本的几个功能调用如下。
(1)AH=3CH,创建一个文件。
功能:建立并打开一个新文件,文件名是DS:DX所指的以00H结尾的字符串,若系统中已有相同的文件名称,则此文件会变成空白。
入口参数:DS:DX←文件名字符串的起始地址,CX←文件属性(0表示读写,1表示只读)。
出口参数:若建立文件成功,则CF=0,AX=文件称号;否则CF=1,AX=错误码(3、4或5,其中3表示找不到路径名称,4表示文件称号已用完,5表示存取不允许)。
(2)AH=3DH,打开一个文件。
功能:打开名为DS:DX所指字符串的文件。
入口参数:DS:DX←文件名字符串的起始地址,AL=访问码(0表示读,1表示写,2表示读写)。
出口参数:若文件打开成功,则CF=0,AX=文件称号;若失败,则CF=1,AX=错误码(3、 4、5或12,其中12表示无效访问码,其他同上)。
(3)AH=3EH,关闭一个文件。
功能:关闭由BX寄存器所指文件称号的文件。
入口参数:BX←指定欲关闭文件的文件称号。
出口参数:若文件关闭成功,则CF=0;若文件关闭失败,则CF=1,AX=6表示无效的文件称号。
(4)AH=3FH,读取一个文件。
功能:从BX寄存器所指文件称号文件内,读取CX个字节,且将所读取的字节存储在DS:DX所指定的缓冲区内。
入口参数:BX←文件称号,CX←预计读取的字节数,DS:DX←接收数据的缓冲区首地址。
出口参数:若文件读取成功,则CF=0,AX=实际读取的字符数;若文件读取失败,则CF=1,AX=出错码(5或6)。
(5)AH=40H,写文件。
功能:将DS:DX所指缓冲区中的CX个字节数据写到BX指定文件称号的文件中。
入口参数:BX←文件称号,CX←预计写入的字节数,DS:DX←源数据缓冲区地址。
出口参数:若文件写成功,则CF=0,AX=实际写入的字节数;若文件写失败,则CF=1,AX=出错码(5或6)。
3.其他
(1)AH=00H,程序终止。
功能:退出用户程序并返回操作系统。其功能与“INT 20H”指令相同。
执行该中断调用时,CS必须指向PSP的起始地址。PSP是DOS装入可执行程序时,为该程序生成的段前缀数据块,当被装入程序取得控制权时,DS、ES便指向PSP首地址。
通常,结束用户程序并返回操作系统需要以下指令完成:
PUSH DS
MOV AX,0
PUSH AX ;保存PSP入口地址DS:00进栈
⋮
RET ;弹出PSP入口地址DS:00至CS:IP 之所以能返回DOS,是因为RET指令使程序转移到PSP入口,执行该入口处的“INT 20H”指令所致。
(2)AH=4CH,进程终止。
功能:结束当前执行的程序,并返回父进程DOS或DEBUG(加载并启动它运行的程序)。返回时,AL中保留返回的退出码。
例如:
MOV AX,4C00H 或者 MOV AH,4CH
INT 21H 4.应用举例
【例3.9】 利用DOS功能调用命令从键盘输入字符串,并在显示器上显示出该字符串。程序如下:
STACK SEGMENT STACK
DW 256 DUP(?)
TOP LABEL WORD
STACK ENDS
DATA SEGMENT
STRING1 DB 'DO YOU WANT TO工NPUT STR工NGS?(Y/N)'
DB ODH,0AH,'$'
STRING2 DB 'PLEASE INPUT STRING.',0DH,0AH,$'
BUFIN DB 20H
DB ?
BUFIN1 DB 20H DUP(?)
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START:MOV AX,DATA
MOV DS,AX
LEA DX,STRING1
MOV AH,09H
INT 21H ;显示STRING1
MOV AH,01H
INT 21H ;等待键盘输入
CMP AL,'Y'
JNZ DONE
LEA DX,STRING2
MOV AH,09H
INT 21H ;显示STRING2
LEA DX,BUFIN
MOV AH,0AH
INT 21H ;把键盘输入的字符串送
;入由DS:DX指向的缓冲区
MOV AL,BUFIN+1 ;取输入字符串的实际长度
CBW
LEA SI,BUFIN1
ADD SI,AX
MOV BYTE PTR [SI],'$';缓冲区以$结尾
LEA DX,BUFIN1
MOV AH,09H
INT 21H
DONE: MOV AH,4CH
INT 21H
CODE ENDS
END START 3.5.2 BIOS中断调用 PC微机系统的BIOS(基本输入/输出系统)存放在内存较高地址区域的ROM中,它处理系统中的全部内部中断,还提供对主要I/O接口的控制功能,如键盘、显示器、磁盘、打印机、日期与时间等。这些中断调用为用户使用计算机的硬件和软件资源提供了极大方便。
与DOS功能调用相比,采用BIOS中断调用的优点在于:运行速度快,功能更强;不受任何操作系统的约束(而DOS功能调用只可在DOS环境下适用);某些功能仅BIOS具有。其缺点是:可移植性较差,调用也复杂些。
BIOS采用模块化结构形式,每个功能模块的入口地址都存在于中断向量表中。对这些中断的调用是通过软中断“INT n”指令来实现的,其操作数n 就是BIOS提供的中断类型码。BIOS中断调用的范围为INT 05H~INT 1FH。
BIOS中断调用的方法是:首先按照要求将入口参数置入相应寄存器,然后写明软件中断指令“INT n”。
【例3.10】 键盘输入调用(类型码为16H的中断调用:INT 16H)。
这种中断类型调用有3个功能,功能号为0、1、2,功能号放在AH寄存器中。当AH=02H时,其功能是检查键盘上各特殊功能键的状态。执行后,各种特殊功能键的状态放入AL寄存器中,其对应关系如图3.1所示。
图3.1 键盘特殊功能键状态字
这个状态字被记录在内存0040H:0017H单元中。若某键对应位为1,表示该键状态为ON,处于按下状态;若对应位为0,表示该键状态为OFF,处于断开状态。
比如,检查Ctrl键是否按下,若按下则转移到相应程序段执行,可用以下程序:
MOV AH,02H ;置功能号
INT 16H ;取键盘状态存到AL中
AND AL,00000010B ;检查Ctrl键是否按下
JNZ Ctrl-ON ;若按下,转到相应处理程序
…
Ctrl-ON:… 3.6 宏指令、条件汇编及上机过程 3.6.1 宏指令 宏指令是一组汇编语言语句序列的缩写,是程序员事先自定义的“指令”,在宏指令出现的地方,汇编程序自动把它们替换成相应的语句序列。
1.宏指令的使用
宏指令的使用包括宏定义、宏调用和宏扩展。
(1)宏定义。
格式:<宏指令名>MACRO [形参][,形参]…
⋮
ENDM
说明:宏指令名是为该宏定义起的名字,可以像指令助记符一样出现在源程序中;它允许和指令性语句助记符相同,以便重新定义该指令的功能;形参间用逗号或空格隔开,在宏指令调用时,形参被实参依次取代,形参为可选项;MACRO表示宏定义开始,ENDM表示宏定义结束,二者之间的程序段称为宏体。
(2)宏调用。
格式: <宏指令名>[实参][,实参]…
功能:宏指令名的调用就是宏调用,它要求汇编程序把定义的宏体目标代码复制到调用点;调用时实参依次替代形参,实参数目与形参数目可以不相同,当实参数多于形参数时,忽略多余实参,当实参数少于形参数时,剩余的形参处理为空白。
(3)宏扩展。
当汇编程序扫描到源程序中的宏调用时,就把对应宏定义的宏体指令序列插入到宏调用所在处,用实参替代形参,并在插入的每条指令前面加上一个“+”号,这一过程就称为宏扩展。
【例3.11】 若给定宏定义如下:
FOP MACRO P1,P2,P3
MOV AX,P1
P2 P3
ENDM 且宏调用如下:
FOP WORD_VAR,INC,AX 则宏展开后的结果如下:
MOV AX,WORD_VAR
INC AX 又如:字型查表宏指令的定义及调用如下:
FTab MACRO V,N ;宏定义
LEA SI,V
MOV AX,N
ADD SI,AX
MOV AX,[SI]
ENDM ;结束
FTab Y,6 ;宏调用:[Y+6]→AX 2.用于宏定义的其他伪指令
1)LOCAL
格式:LOCAL <符号表>
功能:只要将宏体中的变量和标号列在LOCAL指令的符号表中,汇编程序在宏扩展时用从小到大的特殊序列符号替换它们。
说明:该指令只能在宏定义中使用并放在宏体起始行。
2)PURGE
格式:PURGE <宏指令名表>
功能:宏指令名表所列的宏定义被废弃,不再有效。
3)特殊的宏操作符
(1)%:取表达式操作符。
功能:用在宏体中,则在宏扩展时用表达式的值取代表达式,若在宏体中表达式前没有加%,则在宏扩展时用表达式本身取代。
(2)&:标识字符串或符号中的形参操作符。
功能:加在标识字符串或符号中的形参前,以在宏扩展时使用实参代替这个形参。
(3)!:标识普通字符操作符。
功能:出现在宏指令中时,不管其后是什么字符,都作为一般字符处理,而不再具有前述操作符功能。
【例3.12】 带操作符“&”的宏指令的定义及调用。
ShfX MACRO OP,S,N ;宏定义
MOV CL,N
S&OP S,CL
ENDM ;结束
ShfX HL,AX,4 ;宏调用,AX逻辑左移4次
ShfX AR,BX,7 ;宏调用,BX算术右移7次 3.重复块宏指令
格式:REPT <整数表达式)
⋮ ;重复体
ENDM
功能:重复执行重复体,重复次数必须有确定值且由整数表达式给出。
【例3.13】 在当前内存区定义10个字节数:3,6,9,…,30。
X=0
REPT 10 ;宏定义
X=X+3
DB X
ENDM ;结束 3.6.2 条件汇编 汇编程序能根据条件把一段源程序包括在汇编语言程序内或者把它排除在外,这里就用到条件汇编这样的伪指令。其格式如下:
IF XX argument
… ;自变量满足给定条件汇编此块
[ELSE]
… ;自变量不满足给定条件汇编此块
ENDIF 自变量必须在汇编程序第一遍扫视后就成为确定的数值。其中的XX条件如下:
IF expression ;汇编程序求出表达式的值,如此值不为0则满足条件
IFE expression ;如求出表达式的值为0,则满足条件
IFDEF symbol ;如符号已在程序中定义,或者已用EXTRN伪指令说明
;该符号是在外部定义的,则满足条件
IFNDEF symbol ;如符号未定义或未通过EXTRN说明为外部符号则满足条件
IFB <argument> ;如自变量为空则满足条件
IFNB <argument> ;如自变量不为空则满足条件
IFIDN <argue-1>,<argue-2>;如果字符串<argue-1>和字符串<argue-2>相
;同,则满足条件
IFDIF <argue-1>,<argue-2>;如果字符串<argue-1>和字符串<argue-2>不
;相同,则满足条件 条件伪指令可以用在宏定义体内,也可以用在宏定义体外,也允许嵌套任意次。
【例3.14】 可用DOS或BIOS功能调用输入字符的宏定义。
Input MACRO
IFDEF DOS
MOV AH,1
INT 21H
ELSE
MOV AH,0
INT 16H
END
RNDIM 在引用宏指令Input时,汇编程序会根据符号DOS是否已定义来生成调用不同输入功能的程序段。
3.6.3 汇编语言程序上机过程 汇编语言源程序编写完后,需要经过几个步骤生成可执行文件,才可以在机器上运行。
(1)建立汇编源程序,打开文本编辑器(如EDIT.COM、TURBO.EXE、TC.EXE、C.EXE等),输入源程序,保存为ASM文件。
(2)用汇编程序(如MASM.EXE、ASM.EXE等)产生二进制目标文件(OBJ文件)及列表文件(LST文件)。
(3)用链接程序(如LINK.EXE等)将OBJ文件链接成可执行文件(EXE文件或COM文件)。
(4)在当前盘下输入可执行文件名即可运行程序。
(5)如果在汇编、链接和运行中出现问题,可以用调试程序(如DEBUG.EXE等)跟踪检查,发现问题进行修改后再运行。
1.建立或编辑源文件
这个过程也称为源代码录入。可通过MD-DOS自带的EDIT.EXE文本编辑器进行输入,在DOS提示符下输入“EDIT”并按Enter键,这时如果系统内可调用,EDIT的操作画面便会出现在屏幕上,就可在提示下进行录入了,当录入完毕后,选择存盘并给输入的文件起一个文件名,形式为filename.asm。其中filename为文件名,由1~8个字符组成,asm是为汇编程序识别而必须加上去的,不可更改。也可通过“记事本”等软件建立或编辑源文件。
2.汇编过程
汇编过程就是把编写正确的源代码编译为机器语言、程序清单及交叉引用表的目标文件。如果此时程序有语句错误,系统将报错,并指出在第几行、什么类型的错误,可根据提示逐一修改。
在DOS提示符下输入“MASM filename”并按Enter键(注:假定系统内的汇编程序为MASM.EXE,如果系统的汇编程序为ASM.EXE时,则输入“ASM filename”。其中filename为刚才建立的文件名),这时汇编程序的输出文件可以有3个(扩展名分别为.obj、.lst、.crf),会出现3次提问,在此直接按Enter键即可。下面显示的信息是源程序中的错误个数,如果为0则表示顺利通过。如果不为0就说明有错误,并指出错误出现的行,可依据这个提示进行修改。但如果错误太多还未等看清就显示过去了,可用“MASM<filename>file_ame”(filename为一个没用过的文件名,用以存放出错信息)命令将错误信息存于一个指定的文件,再使用文本编辑器查看。
若编译不通过,就需要重新修改。首先要清楚,在编译中检测出的错误均为每一条语句的语法或用法错误,它并不能检测出程序的逻辑设计(如语句位置安排等)错误,所以要记好出错的行号以便修改。在记录行号后,应再次执行“EDIT filename.asm”命令,依据行号进行修改并存盘,再次进行汇编,直至编译通过为止。
3.链接为可执行文件
即链接为EXE或COM文件。在DOS提示符下输入“LINK filename”并按Enter键,链接后会产生一个可执行文件,运行编译好的可执行文件就可以得到结果了。