汇编语言程序设计(第3版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第4章 汇编语言与汇编语言程序

本章主要介绍汇编语言程序的格式和组成元素,对于表达式和常用伪指令进行了较为详细的讲述,还介绍了汇编语言程序的上机过程。

4.1 汇编语言程序与汇编程序

用汇编语言编写的程序称为汇编语言源程序,简称汇编语言程序。

汇编语言程序虽然比机器语言程序直观、易懂、便于交流和维护,但不能像机器语言那样为计算机直接识别和执行,这一点与许多高级语言源程序相似。汇编语言源程序只有被编译成机器语言程序后,才能被计算机执行。这种机器语言程序称为目标程序(Object Program)。虽然目标程序已经是二进制文件了,但是它还不能直接上机执行,必须经过连接程序把目标文件与库文件或其他目标文件连接在一起形成可执行文件,才可以由DOS装入存储器,并在机器上执行。

将汇编语言源程序编译成目标程序的加工程序称为汇编程序。这一加工过程称为汇编,三者的关系如图4.1所示。

图4.1 汇编语言源程序、目标程序及汇编程序之间的关系

说明:汇编程序与汇编语言程序是两个不同的概念。汇编程序是用来将汇编语言程序编译成机器代码的工具;汇编语言程序是用户根据实际需要编写的程序。

8086/8088、80286、80386、80486及Pentium等系统广泛使用的汇编程序为80X86宏汇编程序,简称为MASM(Macro-Assembler)。

4.2 汇编语言程序的格式和组成元素

为了说明汇编程序格式,这里给出了一个汇编语言示例程序。

【例4.1】示例程序。

          NAME      EXAMPLE
          DSEG      SEGMENT
          DATA1     DB  4  DUP(1),10H,11,0AH,0,0BH
          SUM       DB  ?
          COUNT     EQU  9
          DSEG      ENDS
          SSEG      SEGMENT  STACK
                    DB  100H  DUP(?)
          SSEG      ENDS
          CSEG      SEGMENT
                    ASSUME  CS:CSEG,DS:DSEG,SS:SSEG
          START:    MOV  AX,DSEG
                    MOV  DS,AX
                    XOR  AL,AL        ;AL清0;
                    MOV  CX,COUNT     ; 设置相加次数, 也即循环操作次数;
                    LEA   SI,DATA1    ;SI指向数据区起始位置;
          LOOP1:    ADD  AL,[SI]      ; 将SI所指数据加到AL中;
                    INC   SI          ;SI指向下一字节;
                    LOOP  LOOP1       ;CX减1计数, 减1后不为0则转至LOOP1标号处;
                    MOV  SUM,AL       ; 将DATA1中9个字节之和送变量SUM;
                    MOV  AH,4CH
                    INT  21H          ; 返回DOS。
          CSEG      ENDS
                    END  START

这一程序实现了DATA1数据区中9个字节的相加,基本上体现了汇编语言程序的一般格式和组成元素。

(1)汇编语言程序采用以SEGMENT和ENDS定义的段结构,一个汇编语言程序由若干个段组成。

(2)段中包含两种语句:指令语句,即前面介绍的指令,以及伪指令语句,简称伪指令。

(3)语句中包含若干元素:标识符,保留字,表达式等。

以下介绍汇编语言程序的组成元素,指令语句已在前面讲解,此处不再重复。

4.2.1 标识符

标识符是源程序中便于指定和阅读的字符串。例如,示例程序中的数据段名DSEG,变量DATA1,标号LOOP1,符号常量COUNT等都是标识符。

标识符可以由字母A~Z,a~z,数字0~9,专用字符?、.、@、$、_(下画线)等字符组成。除数字外,所有这些字符均可作为标识符的首字符。“.”只能作为标识符的首字符。标识符可由多个字符组成,但仅前31个字符能为汇编程序识别。

4.2.2 保留字

保留字是汇编程序预留的具有固定用途的字符串。例如,示例程序中的SEGMENT,DB,MOV,CX等所有寄存器名,指令及伪指令助记符,运算符等均属于保留字。在编写源程序时,一般要避免将保留字用于非固定用途。

4.2.3 表达式

表达式是由常量、变量、标号及运算符等构成的式子。表达式分为数值表达式和地址表达式。

1.常量

常量分为字面常量、符号常量及串常量。字面常量由0~9,A,B,C,D,E,F以及基数后缀,即尾标B,D,H,Q(或O)构成。如果一个十六进制常量以字母开头,则须在前面添加数字0以区别于标识符。例如,示例程序中的10H,0A1H,9等均属于字面常量,如果将0A1H中的数字0去掉,即写成A1H,则会被当做以字母A开头的标识符。

符号常量是使用EQU、= 伪指令定义其值的标识符。例如,示例程序中的标识符COUNT被定义为数值9。

串常量是用单引号括起的一串字符。串常量以单引号中各字符的ASCII码存储。例如,'Data' 以44H,61H,74H,61H存储。

2.变量

在汇编语言程序中的变量是存储单元的标识符,即数据存放地址的符号表示。例如,示例程序中的DATA1就是变量。对变量的访问是通过变量名来实现的。因此,变量名被认为是变量的符号地址。变量名一般由定义变量的伪指令语句确定。

由于存储器是分段使用的,所以源程序定义的变量具有3个方面的属性。

(1)变量的段属性

变量的段属性是指变量所在段的段地址。当需要访问该变量时,该段地址一定要在某一段寄存器中。例如,在示例程序中,通过指令:

          MOV  AX,DSEG
          MOV  DS,AX

将变量DATA1,SUM所在段的段地址放到DS中,以便其后对这些变量进行访问。

(2)变量的偏移属性

变量的偏移属性是指变量所在段的首地址到该变量的偏移量。例如:示例程序中变量DATA1和SUM的偏移地址分别为0000H和0009H。

(3)变量的类型属性

变量的类型是指存取该变量中的数据所对应的字节数。有字节(BYTE)、字(WORD)、双字(DWORD)、四字(QWORD)及十字节(TBYTE)等类型。变量类型由变量定义时所使用的伪指令决定。例如,示例程序中通过DB伪指令将变量DATA1、SUM定义为字节类型。

3.标号

汇编语言程序中的标号是机器指令存放位置的标识符,即机器指令存放地址的符号表示。它可作为转移指令或重复控制指令转向目的操作数。例如,示例程序中的LOOP1就是标号,它作为LOOP指令的目的操作数。标号一般只在代码段中定义和引用。由于标号代表了指令的符号地址,所以标号也有3个属性:

(1)标号的段属性

标号的段属性是指标号定义所在段的段地址。

(2)标号的偏移属性

标号的偏移属性是指标号所在段的首地址到该标号的定义语句的偏移量。

3)标号的类型属性

标号的类型有两种:NEAR类型和FAR类型。NEAR类型的标号只能在定义该标号的段内使用,而FAR类型的标号则无此限制。

4.数值表达式

数值表达式是由常量与算术运算符、逻辑运算符或关系运算符构成的式子。下面分别介绍这3类运算符,运算符见表4.1所示。

表4.1 数值表达式中常用的运算符

(1)算术运算符

算术运算符包括+、-、*、/、MOD(模除)。其中,/运算的结果为两常量之商的整数部分。如:122/6的结果为20。MOD运算的结果为两常量相除所得余数。如:122 MOD 6的结果为2。

(2)逻辑运算符

逻辑运算包括NOT,AND,OR,XOR,SHL及SHR。这些运算符实现相关常量的二进制逻辑运算。例如:

          NOT  12H的结果为0EDH;
          1234H  AND  0FH的结果为04H;
          1234H   OR  0FH的结果为123FH;
          1234H  XOR  1FH的结果为2BH。

SHL或SHR运算是指将运算符左边的二进制常量左移或右移运算符右边所指定的位数(正整数),空出位补0。如:0010B SHL 2的结果为1000B。

(3)关系运算符

关系运算符包括EQ(相等)、NE(不等)、LT(小于)、GT(大于)、LE(小于等于)、GE(大于等于)。此类运算的结果是两个特殊的常量,若关系成立则结果为-1(用补码表示),否则为0。例如

设已定义符号常量COUNT的值为9,则COUNT NE 2的值为-1,COUNT GE 10的值为0。

数值表达式的运算在汇编期间进行,且运算结果为数值常量。

5.地址表达式

地址表达式是由常量、变量、标号、寄存器BP、BX、SI、DI内容(在此用方括号括起)和运算符组成的式子。例如,本书第三章中所述的直接寻址、寄存器间接寻址、基址寻址、变址寻址、基址变址寻址的表示均属地址表达式。又如,示例程序中的[SI]也属于地址表达式。地址表达式的值一般为段内偏移地址,具有段属性、偏移属性及类型属性。

地址表达式除使用前述运算符外,还可以使用表4.2所示的运算符。

(1)属性定义算符

① 段超越前缀“:”。该运算符用于给变量、标号或地址表达式临时指定一个段属性,其格式为

段寄存器名:地址表达式

或段名:地址表达式

例如:

          MOV  AL,ES:[1000H]    ; 以(ES)作为段地址, 以1000H作为偏址的存储单元的内容送AL。
          MOV  AL,[1000H]       ; 以(DS)作为段地址, 以1000H作为偏移地址的存储单元的内容送
                                  AL。如前所述, [1000H]的段属性默认为(DS)。另外, 并不因前
                                  一指令中使用过段超越前缀符而改变本语句中[1000H]的段属性。

表4.2 地址表达式中专用运算符

② 类型运算符PTR。该运算符用于给变量、标号或地址表达式临时指定一个类型。其格式为

类型PTR地址表达式

根据地址表达式的不同值,类型可以是BYTE,WORD,DWORD,NEAR及FAR等。例如:

          MOV  WORD  PTR[2000H],0   ; 将偏移地址为2000H的字单元, 即2000H和2001H两字节清0。
          MOV  BYTE   PTR[2000],0   ; 将偏移地址为2000H的字节清0。

③ 定义类型运算符THIS。该运算符具有与PTR类似的功能,可用于指定某个变量、标号或地址表达式的类型,但在具体用法上有区别。其格式为

标识符EQU THIS类型

将THIS右边的类型赋给左边的标识符。

(2)分析运算符

① 取段地址运算符SEG。该运算符产生其后的变量或标号所在段的段地址。例如

          MOV  AX,SEG DATA1        ; 将变量DATA1所在段的段地址送AX。

② 取偏移地址运算符OFFSET。该运算符产生其后的变量或标号的偏移地址。例如

          MOV  BX,OFFSET  DATA1    ; 将变量DATA1的偏移地址送BX。

③ 取类型运算符TYPE。该运算符产生其后的变量或标号的类型值。变量或标号的类型与该运算符产生的类型值的对应关系如表4.3所示。

④ 取变量单元数运算LENGTH。该运算符产生其后变量所包含的单元个数。该运算的结果根据该变量定义伪指令中第一个表达式的形式而定。若第一个表达式为重复子句“n DUP(数值表达式)”,则LENGTH运算的结果为重复因子n;否则结果为1。伪指令中“n DUP(数值表达式)”等同于将括号中的表达式重复n次。例如,示例程序中定义变量DATA1的伪指令

          DATA1  DB  4  DUP(1),10H,11H,0AH,0,0BH

中第一个表达式为“4 DUP(1)”,所以LENGTH DATA1的值为4。

表4.3 存储器操作数的类型值

⑤ 取变量总字节数运算符SIZE该运算符产生其后变量所包含的总字节数。例如:对于示例程序中的变量DATA1而言,SIZE DATA1的值为9。

又如,设有伪指令

          DATA2  DW  4  DUP(1),10H,11H,0A1H,0,0BH

也即将DATA2定义为字变量,则4个1以及10H,11H,0A1H,0及0BH各占两个字节,那么SIZE DATA2的值为18。

【例4.2】分析运算符应用举例。

下面定义的数据段DATA,假设段地址为2000H,则利用分析运算符可进行如下运算。

          DATA   SEGMENT
          D1     DB  41H,6DH
          D2     DW  803AH,104FH
          D3     DD  12345678H,0ABCDEF9H
          D4     DW  40  DUP(1)
          DATA   ENDS

运算一:

          MOV  AX,SEG  D1          ;AX=2000H
          MOV  BX,SEG  D2          ;BX=2000H
          MOV  DX,SEG  D3          ;DX=2000H

变量D1,D2和D3同属一个段,故它们的段地址相同。

运算二:

          MOV  AX,OFFSET  D1       ;AX=0
          MOV  BX,OFFSET  D2   ;BX=2
          MOV  DX,OFFSET  D3       ;DX=6

变量D1,D2和D3的偏移地址分别是0、2和6。

运算三:

          MOV  AX,TYPE  D1         ;AX=1
          MOV  BX,TYPE  D2         ;BX=2
          MOV  DX,TYPE  D3         ;DX=4

变量D1,D2和D3的类型值分别是1、2和4。

运算四:

(3)分离运算符

          MOV  AX,LENGTH  D4       ;AX=40
          MOV  BX,SIZE  D4         ;BX=80
        而:
          MOV  AH,LENGTH  D1       ;AH=1
          MOV  AL,SIZE  D1         ;AL=2
          MOV  BH,LENGTH  D2       ;BH=1
          MOV  BL,SIZE  D2         ;BL=4
          MOV  DH,LENGTH  D3       ;DH=1
          MOV  DL,SIZE  D3         ;DL=8

① 分离高字节运算符HIGH。该运算符产生其后的运算对象的高字节。例如

          MOV  AL,HIGH  1234H      ; 将12H送AL。

② 分离低字节运算符LOW。该运算符产生其后的运算对象的低字节。例如

          MOV  AL,LOW  1234H       ; 将34H送AL。

(4)其他运算符

SHORT运算符用于说明其后的标号在短矩离(-128~127之间)内;

( )运算符用于改变运算的优先级别;

[ ] 运算符用于表示间接寻址等。

各种常用运算符的优先级别见表4.4所示。

表4.4 运算符的优先级

4.3 伪指令

汇编语言程序由多条语句构成。而语句分为指令语句和伪指令语句,分别简称为指令和伪指令。在第三章和第四章中已分别介绍了8086/8088、80286、80386、80486、及Pentium中的指令。指令在汇编过程中被翻译成相应的目标代码,并经过连接后生成计算机可执行的机器指令代码。伪指令属于汇编控制命令,在汇编过程中实现数据定义、分配存储区、指示程序结果等功能。伪指令本身一般不产生任何目标代码。

80x86宏汇编语言提供了多种伪指令,本节只介绍部分常用伪指令。

4.3.1 符号定义伪指令

符号定义伪指令用于为程序中多次出现的同一个常量或表达式定义一个标识符,以便在源程序中以标识符来代替对应的常量或表达式。

1.EQU伪指令

格式:标识符EQU表达式

功能:用表达式来定义标识符,即使得标识符等同于表达式。

        例如:COUNT  EQU  9              ; 定义一个符号常量COUNT, 使之等同于9;
              ADDR   EQU  ES:[BX][SI]   ; 使ADDR等同于地址表达式ES:[BX][SI]。

2.= 伪指令

格式:标识符 = 表达式

功能:与EQU伪指令功能基本相同,但两者中只有 = 伪指令可对同一标识符作重新定义。

        例如:COUNT=9            ; 定义COUNT等于9;
              COUNT=COUNT+1      ; 重新定义COUNT等于10。
        再如:DT1   EQU   30
              DT1   EQU   20     ; 此定义错误, 因为前面已经用EQU伪指令对DT1作了定义。

注意:EQU、 = 伪指令仅仅是对程序中某些符号进行等价说明,并不实际分配存储单元,因此,用EQU、 = 伪指令定义的符号不占存储单元。

4.3.2 变量定义伪指令

变量定义就是为数据分配存储单元,有时还为这个存储单元取一个变量名。

1.DB伪指令

格式:[变量] DB一个或多个表达式。

功能:告知汇编程序,留出一块内存单元作为字节数据区,并在其中存放各表达式的值,先出现者对应低地址,后出现者对应高地址。若此伪指令中设有DB左边的“变量”,则用此变量来标识新定义的内存单元。表达式可以是以下4种形式。

(1)字节常量以及不确定常量“?”。

(2)重复子句:数值表达式DUP(一个或多个表达式)。

(3)串常量。

(4)以上3种形式的任意组合。

        例如:DATA1   DB  0,45H,0FFH,?
              DATA2   DB  'HELLO!'
              DATA3   DB  10,2 DUP(2 DUP(1,2),3)

这里定义的字节变量在存储器中的存储格式如图4.2所示。

图4.2中“--”表示不确定常量,各个字符实际上以其ASCII码形式存储。

图4.2 字节变量存储格式

2.DW伪指令

格式:[变量] DW 一个或多个表达式。

功能:与DB伪指令功能类似,但定义的是字变量。表达式可以是以下4种形式。

(1)除了是字数据外,与DB伪指令的(1)、(2)同;

(2)地址表达式。在此情况下,实际取其偏移地址;

(3)一个或两个字符组成的串常量;

(4)以上3种形式的任意组合。

        例如:ARRAYW    DW   -1,1234H,2 DUP(-32768)
              ADDR      DW   ARRAYW+2
              STRING    DW  'EH','LL','!O'

这里定义的字变量在存储器中的存储格式如图4.3所示(此处设第一个字变量的存储地址为01A0:3000,即段地址为01A0H,偏移地址为3000H),当DW后有两个字符组成的串常量时,这两个字符中的后一个字符对应低地址,前一个字符对应高地址。

3.DD伪指令

格式:[变量] DD 一个或多个表达式。

功能:与DB伪指令功能类似,但定义的是双字变量。表达式可以是以下4种形式。

(1)除了是双字数据外,与DB伪指令的(1)、(2)同。

(2)地址表达式。在此情况下,分别将偏移地址和段地址存放到存储器中,偏移地址对应较低地址。

(3)1~4个字符组成的串常量。

(4)以上3种形式的任意组合。

除上述3种变量定义伪指令外,还有3字变量定义伪指令(DF伪指令)、4字变量定义伪指令(DQ伪指令)和10字节变量定义伪指令(DT伪指令)。

4.3.3 段定义伪指令

段定义伪指令指示汇编程序如何按段组织程序和使用存储器。

图4.3 字变量存储格式

1.SEGMENT和ENDS伪指令

格式:段名SEGMENT [定位方式][组合方式][ '类别']

                    ┇    ;段体

              段名ENDS

功能:SEGMENT和ENDS伪指令用于把程序分成若干逻辑段。这些逻辑段根据其用途的不同分为代码段、数据段、堆栈段和附加段,它们被分别装入由CS、DS、SS和ES所指定的物理段中。

段名由程序员指定,且起始处和结束处的段名一致。段体为段内的指令和伪指令序列。其后的3个参数一般可任选,它们的含义将在第十一章中介绍。

2.ASSUME伪指令

格式:ASSUME段寄存器:段名 [,段寄存器:段名…]

功能:该伪指令用于通知汇编程序,CS、DS、SS或ES被设定为哪些段的段地址寄存器,从而在汇编时能知道语句中引用的变量、标号或表达式所对应的段。例如:示例程序中的ASSUME伪指令将DS设定为DSEG段的段地址寄存器,所以在对指令

          LOOP1:ADD  AL,[SI]

进行汇编时,知道源操作数对应的段为DSEG。

值得注意的是:ASSUME伪指令只是告知汇编程序有关段寄存器将被设定为哪个段的段地址,而段寄存器必须通过指令来设定具体值。例如:示例程序中在使用上述ASSUME伪指令后,通过指令

          MOV AX, DSEG
          MOV DS, AX

给DS设定具体值。

3.ORG伪指令

格式:ORG表达式

功能:告知汇编程序,使其后的指令或数据从表达式的值所指定的偏移地址开始存放。表达式的值应为0~65535(即0000H~FFFFH)例如:ORG $+10表示其后的指令或数据跳过10个字节存放。其中$表示当前偏移地址。

4.3.4 过程定义伪指令

在程序设计中,常把具有某种功能的程序段设计成一个过程。80x86宏汇编语言用于过程定义的伪指令的格式为

          过程名  PROC  [NEAR或FAR]
                ┇      ; 过程体
          过程名  ENDP

其中过程名由程序员指定,且起始处和结束处的过程名一致。过程体为过程内的指令和伪指令序列。定义一个近过程时,参数NEAR可省略。

4.3.5 80x86指令集选择伪指令

这类指令一般放在程序的起始位置,用来通知汇编程序以下程序中使用哪一种指令集,如表4.5所示。

表4.5 汇编程序指示符

注意:*用于6.11版本的MASM。

说明:(1)8086可默认。当一个程序未使用80x86指令集选择伪指令时,被默认为使用8086/8088指令集。(2)80x86指令集向上兼容。例如,在起始位置使用了·386伪指令的程序中可使用8086/8088,80286及80386指令集中的指令,但不能使用80486指令集中的指令。

4.4 汇编语言程序的上机过程

汇编语言程序的处理过程包括编辑、汇编、连接及执行。一般可以分为以下4个步骤:

(1)用编辑程序,建立扩展名为.ASM的汇编语言源程序文件。

(2)用汇编程序将ASM文件汇编成二进制的目标文件,即OBJ文件。

(3)用连接程序,可将OBJ文件连接为可执行文件,即EXE文件。

(4)可在DOS环境下直接执行EXE文件,亦可通过DEBUG调试和执行。

该处理过程为图4.4所示。

图4.4 汇编语言程序的处理过程

本节将介绍上机实现上述过程的具体操作方法。

上机操作首先使用EDIT或其他文本编辑工具编辑源程序,建立扩展名为.ASM的源程序文件。源程序一般一行安排一条语句。一般而言各语句的标号、助记符、操作数及注释首字符对齐,如【例4.1】中示例程序所示。在【例4.1】示例程序被编辑后,建立EXAMPLE.ASM源程序文件。

4.4.1 MSAM汇编环境

1.生成OBJ文件

可使用常用Microsoft公司的MASM汇编程序根据已建立的ASM文件生成对应的OBJ文件。汇编语言源程序一定要用.ASM作为扩展名,否则MASM.EXE文件不进行汇编。现以EXAMPLE.ASM文件汇编为EXAMPLE.OBJ文件为例说明生成OBJ文件的过程。具体操作步骤如下。

(1)通过鼠标导航到MASM.EXE文件所在的文件夹下;

(2)在MASM.EXE文件的位置上双击左键,启动汇编程序;

(3)在汇编时,用户根据提示输入源程序文件名EXAMPLE.ASM;

(4)根据提示输入其他有关信息。

其操作过程如图4.5所示。该过程中需要用户回答3项内容以便生成3个文件,如窗口中间的3行。

图4.5 汇编过程的信息显示及输入

① 第1行提示用户汇编将生成的“目标文件”,即EXAMPLE.OBJ文件。这是汇编的重要文件。按Enter键表示同意生成该文件及系统给该文件的命名,也可输入其他文件名。

② 第2行提示汇编是否生成“列表文件”(*.LST)。列表文件包含源程序清单和机器语言程序清单,以及有关的符号表,为方便调试而建立的文件。直接回车表示不需要生成列表文件;若需要该文件,则需要输入一个列表文件名。

③ 第3行提示汇编是否生成“交叉引用文件”(*.CRF)。该文件包含了源程序中定义的所有符号,及其在源程序中的行号。直接回车表示不需要生成“交叉引用文件”。回答上述3项提问后,系统开始汇编。当汇编没有发现源程序错误时将提示:

          0  Warning  Errors(警告错误)
          0  Severe   Errors(严重错误)

警告错误不会影响连接和执行的,严重错误必须要修改源程序,直到汇编没有错误为止,否则汇编将不生成目标文件。

2.生成EXE文件

可使用常用的Microsoft公司的LINK连接程序根据已生成的OBJ文件产生对应的EXE文件。现以EXAMPLE.OBJ文件连接EXAMPLE.EXE文件为例说明产生EXE文件的过程。具体操作步骤如下。

(1)通过鼠标导航到LINK.EXE文件所在的文件夹下;

(2)在LINK.EXE文件的位置上双击左键,启动连接程序;

(3)在连接时,用户根据提示输入目标程序文件名EXAMPLE.OBJ;

(4)根据提示输入其他有关信息。

其操作过程如图4.6所示,该过程中同样需要用户回答3项内容以便生成3个文件,如窗口中间的3行。

图4.6 连接过程的信息显示及输入

① 第1行提示用户连接将生成的“可执行文件”,即EXAMPLE.EXE文件。这是最终的执行文件。按Enter键表示同意生成该文件及系统给该文件的命名,也可输入其他文件名。

② 第2行提示连接是否生成“映像文件”(*.MAP)。映像文件包含每个段在内存中的分配情况。直接回车表示不需要生成映像文件;若需要该文件,则需要输入一个映像文件名。

③ 第3行提示连接是否生成“库文件”(*.LIB)。该文件包含了源程序中需要用到的库函数。直接回车表示不需要生成“库文件”。

回答上述3项提问后,系统开始连接生成可执行文件。当连接没有发现目标程序时将提示有严重错误,无法生成可执行文件。当程序中未定义堆栈段时,将提示有警告性错误:

          Warning:  No   STACK  segment
             (警告错误:没有堆栈段)

警告性错误不影响可执行文件的生成,也不影响程序的执行。

3.快速生成可执行文件的方法

若用户只需生成源文件(ASM文件)、目标文件(OBJ文件)和可执行文件(EXE文件),而不要LST文件、CRF文件、MAP文件和LIB文件时,可用下列命令方式来汇编和连接。

(1)单击“开始”菜单 → “运行”。

(2)在对话框中输入E:\MASM\MASM EXAMPLE;然后单击“确定”按钮,完成汇编工作。运行过程中屏幕显示如图4.7所示的“运行”对话框。

(3)连接文件过程与汇编相似,不同的是在对话框中输入E:\MASM\LINK EXAMPLE;然后单击“确定”按钮。

图4.7 “运行”对话框

其中“E:\MASM\”指定了MASM.EXE和LINK.EXE文件所在的磁盘和文件夹。

命令行末的分号告诉系统省略屏幕提示信息,直接使用默认值,从而提高汇编和连接的速度。

4.程序的执行和调试

在产生可执行文件,即EXE文件后就可以直接执行程序。程序的执行可以使用类似于快速生成可执行文件的方法。区别在于,在“运行”对话框中输入的是要执行的程序名。有的程序不包含输入输出指令,CPU在执行完用户程序后就自动返回到操作系统,用户可能看不到程序的任何执行结果,上述的EXAMPLE.EXE就属于此类程序。若要观察程序的执行结果,最常用的方法是运行DEBUG程序来检查。

在汇编、连接成功后,程序的执行未必就能成功,这是因为在汇编、连接时,源程序中可能存在的一些逻辑错误往往不能被发现。可使用调试器DEBUG对目标程序进行动态调试,在执行过程中观察各寄存器、相关存储单元及标志寄存器的值,跟踪执行情况,判断结果的正确性。EXAMPLE.ASM经过汇编、连接生成可执行文件EXAMPLE.EXE后,可用DEBUG对其进行动态调试。首先将待调试的EXE文件调入内存,调入“运行”对话框,如图4.7所示。

文件调入后,将显示DEBUG命令状态提示符“-”,此时可用R命令查看或修改寄存器内容,用U命令对程序代码进行反汇编,用D命令查看数据单元,用G命令执行程序,用T命令单步逐条执行程序等。DEBUG中使用的地址和数据默认为十六进制形式。在输入数据时不要加尾标“H”。下面分别介绍上述命令的使用方法,其他的命令使用见附录。

(1)显示存储单元命令D(Dump)

      格式:-d [地址]
            -d [地址范围]

功能:显示指定地址或地址范围内存储单元的内容

① 显示代码段存储单元的内容

-d cs:0000显示代码段中从0000H开始的128个字节单元的内容,命令输入及显示结果如图4.8所示。从B8到21这部分内容即为程序EXAMPLE.ASM编译链接后产生的机器代码在代码段存储的情况,均以十六进制表示。显示的首行中:

      1402:0000 B8 F1 13 8E D8 32 C0 B9-09 00 8D 36 00 00 02 04  …..2…..6…..
      1402:0000表示代码段CS的值为1405H,偏移地址为0000H。

B8 F1 13 8E D8 32 C0 B9-09 00 8D 36 00 00 02 04表示代码段中偏移地址0000H到000fH的16个存储单元中存放的数据值,对应关系是0000H单元存储的值为B8H,0001H单元存储的值为F1H……,000fH单元存储的值为04H。

…..2…..6….. 表示上述存储单元中的数据为ASCII码时,在屏幕的显示,如果该数值为控制字符或者不可显示的字符,则以 . 表示。

其他各行显示的内容表示含义与首行相似。

② 按指定地址显示存储单元的内容。

-d ds:0000显示数据段中从0000H开始的128个字节单元的内容,命令输入及显示结果如图4.8所示。

③ 按指定地址范围显示存储单元的内容。

-d 0000003f显示数据段中0000H至003fH单元的内容,此处可省略“ds:”。命令输入及显示结果如图4.8所示。

图4.8 D命令执行显示结果

说明:如果第一次使用D命令时,没有指定地址或地址范围,则显示代码段的内容,例如图4.8中的第一个d命令;其后没有指定地址或地址范围的D命令,将在上一次显示内容的基础上,显示其后128个单元的内容。

当D命令显示代码时,采用的是二进制数的十六进制表示,可读性比较差,无法直接判断出每一个十六进制代码所代表的含义,导致初学者无法直接看出程序的正确与否。这时,可以通过U命令将十六进制表示的机器代码转换成为汇编语言助记符。

(2)反汇编命令U(Unassemble)

      格式:-u [地址]
            -u [地址范围]

功能:对指定地址或地址范围的目标代码进行反汇编,例如

            -u 0000

从代码段中偏移地址为0000H开始的单元开始反汇编,反汇编显示的结果如图4.10所示。

            -u 00000018

将地址0000H至0018H之间的目标代码进行反汇编。

图4.9中可以看出,代码段中从偏移地址0000H单元开始到0019H单元均为有效地数据,从001AH单元开始往后都是“00”,也即程序EXAMPLE.EXE在由操作系统装入内存后占用的存储空间偏移地址是0000H~0019H。使用反汇编U命令时,将会对目标地址单元的数据反汇编为汇编语言助记符,如图4.9显示的。

      1402:0000  B8F113   MOV   AX,13F1
      1402:0003   8ED8    MOV   DS,AX
      1402:0005   32C0    XOR    AL,AL
      ┇

图4.9 U命令执行显示结果

从偏移地址0000H到0002H单元,3个单元存放的数据为B8H、F1H、13H,该数据反汇编成汇编语言助记符为MOV AX, 13F1,这样就很容易知道该指令的作用了;偏移地址0003和0004单元存放的数据为8ED8,该数据反汇编成汇编语言助记符为MOV DS, AX。以此类推,其他反汇编得到的指令在图4.9中的显示就比较容易理解了。

注意,在显示中1402: 001A 0000 ADD [BX+SI], AL,该指令以及其后的指令均无意义,从D命令显示的代码段数据中,已经可以看出从偏移地址001AH单元开始数据值已经全部为“0”了,所以反汇编得到的汇编语言助记符是没有意义的。

通过以上的D命令和U命令,已经能够检查DEBUG调试器载入的EXAMPLE.EXE程序与EXAMPLE.ASM之间的对应关系和两者之间的差异了,在汇编语言源程序中的伪指令和其他说明性的语句在EXAMPLE.EXE中已经全部转换完成。

在EXAMPLE.EXE程序未执行时使用D命令查看数据段内容,得到的数据段DS寄存器的值为13E1H(如图4.8所示),使用U命令反汇编后查看指令时,系统为程序分配的数据段DS寄存器的值为13F1H,同样都是系统分配的数据段,为什么两者相差了256个单元呢?

原因是磁盘上的EXE文件包括两部分:一部分为装入模块,另一部分为“重定位信息”。DEBUG调试器在载入EXAMPLE.EXE程序到内存时,这两部分均调入内存,在完成对装入模块的重定位后,重定位信息即被丢弃,然后再在同一内存块的用户程序上方(低地址处)偏移地址为00H~FFH的单元自动生成一个有256个字节的数据块,该数据块被称为“程序段前缀”(Program Segment Prifix,简称PSP)。载入程序时,系统自动给DS,ES,FS和GS赋值,令DS=ES=存放PSP的段基址,FS=GS=0,CS:IP=用户程序的启动地址,然后系统将控制权交给用户程序。

这时,用户必须注意系统给DS,ES段寄存器所赋的初值并不等于用户程序数据段、附加数据段的段基址,由于PSP的存在,两者相差了256个字节,因此用户程序一开始就必须对DS进行初始化(程序中如使用ES段,也必须初始化)。有关EXE文件的内存映像读者可参考其他相关资料。

(3)执行命令G(Go)

格式:-g [=地址1][地址2]

功能:从地址1开始执行程序,一直执行到地址2。

若程序能够正确地执行结束,屏幕将显示当前寄存器的执行结果及后一条将要执行的指令。这时用户可以查看寄存器中的数据变化情况,也可以使用D命令查看各个段的信息。用户可以使用两种方法来确定地址1和地址2的具体值:其一,在汇编时,产生LST文件,用文本编辑器查看*.LST文件;其二,在DEBUG中,使用U命令查看程序。例如:在EXAMPLE.ASM程序中,最后一条指令是INT 21H,该指令的地址为0018H,故可以使用命令:

          g=00000018

命令的执行情况如图4.10所示。图中显示了处理器中的通用寄存器AX, BX, CX, DX, SP, BP, SI, DI的当前值,该值是执行完指令MOV AH, 4CH后寄存器的状况,除此之外,还显示段寄存器DS, ES, SS, CS的当前值以及IP的值。在本书前面章节中,曾经介绍IP寄存器中存放的是将要执行指令的偏移地址,这里IP=0018H,说明了存储在偏移地址为0018单元的指令并未执行,也即INT 21H指令并未执行。

图中4.10显示的NV UP EI PL NZ NA PE NC表示了指令执行后状态标志寄存器的值,各符号所代表的含义如表4.6所示。

表4.6 FLAGS中的状态标志的状态表示符号

注意,状态标志寄存器的值是指令执行到MOV AH, 4CH后寄存器的状况,如需要查看每条指令执行后寄存器的值,则需要使用DEBUG中的T命令。

图4.10 G命令执行的显示结果

INT 21H指令执行后将返回操作系统,无法观察指令执行后寄存器及状态标志值的变化。若确需执行该指令,则命令的结束地址应改为001A,即使用命令:

          g=0000001a

(4)单步执行命令T(Trace)

格式:-t [指令数]

功能:执行一条或多条指令。每执行一次,则显示所有寄存器内容和状态标志值。

如果不指定指令数,则执行当前CS:IP指定地址的一条指令。显示这条指令执行后所有寄存器内容、状态标志值,并显示后一条将要执行的指令。如果指定指令数,则从当前CS:IP指定地址开始执行指定数目的指令,并显示每一条指令执行后的结果。T命令的执行情况如图4.11所示。

图4.11 T命令执行显示结果

首先使用R命令使第一条指令MOV AX,13F1H显示出来(R命令将在后面详细介绍),同时能够观察到各寄存器的初始状态。因为T命令不带参数将执行一条指令,图中第一个T命令执行的就是MOV AX,13F1H后各寄存器的状态。通过对比读者可以发现,该指令的执行改变了AX寄存器的值,由0000H变为13F1H,其他寄存器的值均无变化,状态标志寄存器同样未发生改变,在本书3.4.1节中已经介绍过。

T命令执行指令时,如果指令中涉及到数据段,则DEBUG调试器自动显示该数据段中相应单元的数值。

(5)检查和修改寄存器内容命令R(Register)

格式: -r [寄存器]

功能:显示和修改CPU中寄存器内容和状态标志值。

该命令可以显示所有寄存器的内容,例如

          -r

系统显示结果如图4.12所示。该命令也可以显示某个寄存器的内容,例如

          -r ax

系统显示结果如图4.12所示。即AX寄存器的当前内容为0000H,如不修改则直接按Enter键,否则可输入欲修改的内容,例如

          -r bx
          BX  0000
        :0123   把BX寄存器的内容修改为0123H。系统显示结果如图4.12所示。

该命令还可以显示和修改状态标志值,例如

          -r f

图4.12 R命令显示结果

系统显示状态标志值,此时如不修改可按Enter键;否则,可输入欲修改的内容,各符号代表的含义如表4.6所示。例如,欲修改IF、AF、CF及PF标志,可在显示的各状态标志值之后输入diaccype,即

          NV  UP  EI  PL  NZ  NA  PO  NC-diaccype

(6)修改存储单元内容命令E(Enter)

格式1:-E地址 [数据表]

功能:用给定的数据表来替代指定范围的存储单元内容。

例如,-E DS:0010 11 'XYZ' 22

其中11H,'X','Y','Z' 和22H各占一个字节,该命令可以用这5个字节来替代存储单元DS:0010到0014的原先的内容。指令执行结果如图4.13所示。图中DS:0010到0014的原先的内容为F2H,0DH,17H,03H,F2H,指令执行后DS:0010到0014的内容为11H,

格式2:-E地址

功能:逐个单元相继修改存储单元内容。

例如,-E DS:0015则可能显示为:如果需要把该单元的内容修改为88,则用户可以直接输入88,再按“空格”键可接着显示下一个单元的内容,如下:

      13E1:0015  0D.
      13E1:0015  0D.88   E1.

这样,用户可以不断修改相继单元的内容,直到用Enter键结束该命令为止。指令执行结果如图4.13所示。

图4.13 E命令显示结果

(7)帮助命令 ?

格式1: DEBUG/?

功能:显示DEBUG调试器装载文件的命令格式。

在系统的命令提示符C:\>下DEBUG/?输入Enter,如图4.14所示。

DEBUG [[drive:][path]filename [testfile-parameters]]

DEBUG调试器可选参数中

[drive:]:表示被装载文件所在的驱动器

[path]:表示被装载文件所在的文件夹

filename:表示被装载文件名称

[testfile-parameters]:表示调试参数

注:[]表示可选项

该提示信息指出,具体DEBUG调试器命令可在运行DEBUG调试器后输入?得到显示。格式2: -?功能:显示DEBUG调试器命令。该命令可以显示命令的具体内容和参数,例如

图4.14 debug帮助显示结果

      -?

系统显示结果如图4.15所示。

图4.15 debug命令帮助显示结果

在DEBUG调试器命令中,其他的命令含义为:

    compare      C range address ——比较
    fill         F range list ——填充
    hex          H value1 value2 ——十六进制运算
    input        I port ——指定端口输入
    load         L[address][drive][firstsector][number] ——加载
    move         M range address ——移动
    name         N[pathname][arglist] ——命名
    output       O port byte ——输出
    proceed      P[=address][number] ——执行
    search       S range list ——搜索
    write        W[address][drive][firstsector][number] ——写入
    allocate expanded memory        XA[#pages] ——分配扩展内存
    deallocate expanded memory      XD[handle] ——释放扩展内存
    map expanded memory pages       XM[Lpage][Ppage][handle] ——映射扩展内存页
    display expanded memory status  XS——显示扩展内存状态
    DEBUG的上述命令,在这里不一一详细讲解,想详细了解这些命令请参考附录D。

(8)退出命令Q(Quit)

格式:-q

功能:退出DEBUG调试程序,返回操作系统。

DEBUG调试程序结束后,需要返回操作系统时,在提示符后输入Q命令按Enter键即可。

4.4.2 TASM汇编环境

如上所述,使用MASM、LINK及DEBUG可以实现对汇编语言源程序进行处理,事实上,这些工具可以分别以TASM、TLINK及Turbo Debugger来替代,TASM是Borland公司的汇编工具,使用方法类似于MASM,但是调试软件TD(Turbo Debugger)更加简单直观,适合于初学者。其常见版本还可以对使用80X86、Pentium指令的源程序进行处理。

1.Turbo Assembler的使用

TASM是Borland公司开发的3个汇编工具版本之一,适合于比较小的模块编译工作,编译速度比其他两种要快一些。除此之外,Borland公司还开发了TASMX和TASM32两个版本的汇编工具。下面的举例默认TASM汇编工具存放在E盘的TASM文件下。

(1)TASM指令格式

命令格式为:

          TASM  [option]  source  [,object]  [,listing]  [,xref]

该命令格式与上述MASM的格式类似。如果简单输入命令TASM,就可以得到TASM的可选项和参数的说明信息,但是不能进行汇编工作,这一点与MASM的人机会话方式有区别。

Source指定源程序文件,默认的扩展名是ASM。

Object指定生成目标文件的标识,可默认。默认时文件名同源程序文件名,扩展名为OBJ。

Listing指定生成列表文件的标识,可默认。默认时一般表示不生成列表文件。

xref指定生成交叉参考文件的标识,可默认。默认时一般表示不生成交叉参考文件。

TASM允许一次汇编多个文件,此时,在命令中的Source选项中将多个文件使用“+”连接每个文件,当然也可以使用DOS系统的通配符“*”和“?”来表示多个源文件。例如:

   E:\TASM>TASM EXAMPLE1+ EXAMPLE2
   E:\TASM>TASM EXAMPLE*ASM

注意:命令行中有下画线的部分为用户输入的信息。

(2)TASM汇编工具参数Option

在TASM汇编工具中,提供了较多的汇编参数,这些汇编参数为汇编工具TASM提供相关的汇编信息。在汇编参数使用时应注意,每个参数都以“/”起始,参数可以连用,也可以单独使用,参数可以紧接在TASM之后输入,然后空一格输入源文件名,也可以在命令行的末尾输入。需要了解更全面的汇编参数,可以在“运行”→“CMD”下或者MS-DOS方式下输入

    E:\TASM>TASM ¦MORE

可以分页显示所有参数的功能说明,下面介绍其中几个常用的参数,其他的读者可自行参考参数说明。

      /a,/s ——按字母顺序或者源代码段排序。
      /c   ——在列表文件中产生“交叉引用信息”。
      /d   ——为原文件中的变量赋值。
               例4.1中,可为SUM赋初值0:TASM  EXAMPLE.ASM  /dSUM=0。该参数
            可同时为多个变量赋值后再进行汇编。
      /l   ——产生列表文件。与源文件同名,扩展名为 .LST。
      /zi  ——产生用于调试的含有完整的信息的目标文件。

用户在汇编源程序时可以根据需要可以选择不同的参数项,例如假设TASM.EXE文件和用户的源程序EXAMPLE.ASM文件都在E:\TASM下,则汇编如下:

      E:\TASM>TASM  EXAMPLE.ASM     仅生成EXAMPLE.OBJ文件,如果源程序扩
                                       展名为.ASM,则可以省略。
      E:\TASM>TASM  EXAMPL E        功能与上同。
      E:\TASM>TASM  EXAMPL E  /L/ZI 生成EXAMPLE.OBJ文件和EXAMPLE.LST
                                       文件,并包含完整调试信息。

汇编的过程及结果如图4.16所示。

图4.16 汇编EXAMPLE源程序结果

提示信息中,包含了如下信息:

      Error  messages:None
      Warning  messages:None

表示源程序已通过汇编,没有发现错误。反之,则会显示错误和警告的个数及错误所在的行号和错误类型。

2.Turbo Link的使用

下面介绍的链接器TLINK也是Borland C++的一部分,它的使用方法与LINK类似,但是命令选项较多,功能比LINK要强,而且它还支持386以上的指令,这一点是LINK所不具有的功能。命令格式为:

        TLINK  objfiles  [,exefile]  [,mapfile]  [,libfiles]

如果简单地输入命令TLINK,那么就可以得到TLINK的可选项和参数的说明信息。

Objfiles指定欲链接的目标代码文件,默认的扩展名为OBJ。如果要链接多个目标代码文件,那么文件标识之间用加号或者空格间隔。

Exefile指定输出的可执行文件名,默认的文件名同第一个目标代码文件名。默认的扩展名一般是EXE。

Mapfile指定输出的定位图文件,默认的扩展名是MAP。默认情况下生成的定位图文件名与可执行文件名相同。

Libfiles指定链接时使用的库文件,默认的扩展名是.LIB。可以有多个库,间隔同目标代码文件。默认情况下不使用库。

TLINK链接器中同样提供了很多参数,与TASM类似,可使用:

    E:\TASM>TLINK ¦MORE

查看各参数功能。

如果要链接32位目标代码(包含386以上指令的目标代码),必须使用32位方式链接,选用选项 /3。

例如链接32位目标代码EXAMPLE.OBJ则使用如下命令:

    E:\TASM>TLINK /3 EXAMPLE

链接的过程及结果如图4.17所示

图4.17 链接EXAMPLE目标程序结果

以后各章中带有.386的源程序均要使用该连接方式。

3.Turbo Debugger的使用

Turbo Debugger是一个比较先进的源代码级调试器,它可以调试多种语言编写成的程序,而且它还支持32位的源代码。为描述方便,将Turbo Debugger简称为TD。

(1)启动TD及退出TD

启动TD及装载被调试程序example.exe的命令如下:

   E:\TASM>TD EXAMPLE

TD直接给出如图4.18所示的机器指令级调试界面。可能会叠加一个报告无符号表的对话框,按Esc键就能关闭该对话框。

退出TD比较简单,可以先使用Esc键关闭所有对话窗口,然后按下组合键Alt+X即可退出TD,也可以使用主菜单当中File→Quit菜单项退出TD返回操作系统。

(2)TD的多窗口界面操作

TD的机器级调试界面是一个多窗口界面,如图4.18所示,中间为5个显示调试区,光条停留的区域称之为“活动区”,可以使用Tab键在各区之间移动。活动区的快捷键可使用Ctrl或者Alt激活显示。

① 查看和修改代码区内容

代码区显示的是以行为单位的机器指令和反汇编后的指令助记符。如窗口的第一行:

   CS: 0000▶B8330B mov ax, 0B33

表示位于代码段偏移地址0000H处的3个字节的内容:B8330B,它是符号指令MOV AX, 0B33对应的机器指令。其中B8是该指令的操作码字段,330B是操作系统分配给数据段DSEG的段基址,所以这条指令就是由源程序中的指令MOV AX, DSEG汇编成的机器指令,然后由TD反汇编得到的。窗口的中CS:0000后面的“▶”符号表示本条指令由CS:IP指向,即将执行。

当代码显示区成为活动区时,可进行如下操作:

该区指令的光条可用光标控制键、翻页键调整所显示的代码区。

可使用Ctrl+G组合键弹出定位对话框,输入“段寄存器:偏移地址”定位显示。

可使用Ctrl+S组合键弹出查找对话框,输入要查找的指令,直接寻找该指令。

组合键Alt+F10能够弹出适合本区的操作菜单。

图4.18 TD的机器指令级调试界面

② 查看和修改寄存器区内容

该区位于代码区的右侧,按下Tab键,光条从代码区移至寄存器区,表示该区为当前活动区。寄存器显示区显示了CPU内部各通用寄存器、段寄存器和IP的当前值。

该区中可进行16位通用寄存器和32位通用寄存器的切换,切换有两种方法:

组合键Alt+F10弹出菜单,选择其中的“Registers 32-bit”选项。

组合键Ctrl+R

32位寄存器显示如图4.19所示。

图4.19 32位寄存器

修改被光条覆盖的寄存器的内容:

组合键Ctrl+C,弹出对话框,用户输入有效的数据将替换被光条覆盖的寄存器原有数据。

组合键Ctrl+Z,被光条覆盖的寄存器清0。

组合键Ctrl+I,被光条覆盖的寄存器加1。

组合键Ctrl+D,被光条覆盖的寄存器减1。

③ 查看和修改状态标志位

按下Tab键,光条从寄存器区移至状态标志值显示区,当该区成为“活动区”时,使用光标控制键移动光条,组合键Ctrl+T可翻转光条所在标志位的状态,即0改为1,1改为0。

④ 查看和修改堆栈内容

按下Tab键,光条从状态标志值显示移至堆栈显示区,该区显示堆栈栈顶附近个单元的地址和当前值。在图4.18中,窗口中SS:0100后面的“▶”符号表示该位置为当前栈顶,单元的数值为B833H。

当该区为“活动区”时,按组合键Ctrl+G弹出一个用于地址定位的对话框,用户可输入新的堆栈地址定位显示。按组合键Ctrl+C弹出输入数值的对话框,输入新的数值可以修改光标覆盖的堆栈单元的内容。

⑤ 查看和修改内存数据区的内容

该区显示存储器的内容,各列分别显示“段、偏移地址、内容、……、ASCII码”,最右侧的ASCII是由存储单元中十六进制数据转换得到的,所以某些数值对应的ASCII码可能是符号或者不显示的控制符。

该区为“活动区”时,可进行下列操作

光标控制键和翻页键可调整窗口显示的内容。

组合键Ctrl+D可改变窗口显示内容的格式。

组合键Ctrl+G可定位显示存储单元内容(类似于代码区中的组合键功能)。

组合键Ctrl+S搜索字节表。

组合键Ctrl+C修改存储单元内容。

(3)简单的程序调试过程。

以本章例4.1程序EXAMPLE.ASM为例,介绍调试的过程。

首先使用TASM和TLINK完成程序的编译和链接工作,生成EXE文件,并启动TD,加载EXAMPLE文件,屏幕出现EXAMPLE.ASM源程序,如图4.18所示,三角形符号停留在第一条指令上。

① 连续执行程序

按F9键(菜单中的RUN),即可从三角形符号停留的指令上开始执行。

② 查看执行结果

程序中如果有输出到屏幕的指令,则按下组合键Alt+F5(菜单中的Windows→User Screen)临时切换到DOS屏幕,查看结果,需要返回TD的窗口可按任意键。

③ 使光标重新指向启动位置

程序运行结束后,如果用户想重新运行程序,可按下组合键Ctrl+F2(菜单中的RUN→Program rest)重新装入程序,并使光条指向第一条指令。

④ 单步执行程序

单步执行程序仅仅执行一条指令,方便用户仔细观察程序运行时各寄存器、存储单元和状态标志位的变化,排除程序的错误。单步执行根据对程序“跟踪”状态的不同,有3种单步执行的操作命令。

F8(菜单中的RUN→Step over)单步操作。执行CALL和INT n指令时,“不跟踪”相关的子程序。“不跟踪”的含义是指在进入相关子程序后,自动连续执行子程序直至返回。

F7(菜单中的RUN→Trace into)单步操作。执行CALL能够跟踪子程序,随即暂停,等待用户下一步操作。但是遇到INT n指令时与F8相同操作。

组合键Alt+ F7(菜单中的RUN→Instruction trace)单步操作。执行CALL和INT n指令时进入相关子程序之后立即停止,等待用户操作。

实践证明:用组合键Alt+ F7进入服务程序后,如果执行单步操作,很容易造成系统瘫痪,因此请读者慎用。

在上例中,按一次F7,从寄存器窗口可以看出AX寄存器的值改为0B33H,IP的值改为0003H。在TD中,每次单步执行后,发生变化的寄存器,标志位都会以高亮显示,便于用户观察。再按一次F7,从寄存器窗口可以看出DS寄存器的值由0B23H改为0B33H,IP的值改为0005H,表示程序已经执行完成5字节指令。连续按F7,逐个观察各寄存器、存储单元的值的变化过程,可以使编程者验证程序的正确性。

以上仅仅是一个简单程序的调试过程,汇编语言程序设计是一项综合性的工作,需要有一定的硬件基础和经验,并结合程序调试,才能更好地理解和学习汇编语言。

习题

4.1 什么是汇编语言源程序和汇编程序,二者有何区别?

4.2 解释下列名词:

            标识符   保留字   伪指令   标号

4.3 下面是一些数据段的定义,画出它们在内存中的分配情况。

     (1)数据段DATA定义为:     (2)数据段DATA定义为:
          DATA  SEGMENT          DATA    SEGMENT
            V1   DB  70H                   V1  DB  92H,63H
            V2    DB   43H                 V2  DW  8000H
            V3    DW   1200H              V3  DB 12H,2 DUP(01H,2 DUP(35H))
            V4    DW   6600H            DATA  ENDS
          DATA    ENDS

(3)数据段DATA定义为

          DATA  SEGMENT
            V1   DD  12345678H
            V2  DB 'GOOD!'
          DATA  ENDS

4.4 下面定义的数据段DATA,假设段地址为1000H。

          DATA  SEGMENT
            D1   DB  61H,52H
            D2   DW  3456H,0123H
            D3   DB  20 DUP(0)
          DATA  ENDS

执行下列运算后,填写各寄存器的值。

          MOV  AX,SEG  D2             AX=
          MOV  BX,OFFSET D3            BX=
          MOV  CX,TYPE  D1            CX=
          MOV  DH SIZE  D2         DH=

4.5 已经定义了数据段如下:

            DATA  SEGMENT
                NUM = 48
                X  DB  NUM
                Y  DB  56
                Z  DW  300
            DATA  ENDS

指出下列指令中的错误

     (1)MOV  DS,DATA                  (2)MOV  AX,X
     (3)MOV  NUM,BX                  (4)MOV  AL,Z
     (4)MOV  Y,BX                     (6)MOV  X,Y

4.6 EQU与=伪指令的区别是什么?

4.7 下列语句各为变量分配了多少个字节存储单元?

     (1)D1  DB  3                       (2)D2  DB  100
     (3)D3  DB  '100'                     (4)D4  DW  20,?,0,10 DUP(1)
     (5)D5  DW  3,9,6                   (6)D6  DD  10,20
     (7)D7  DD  D1                      (8)D8  DW  D5+4

4.8 有以下程序段,试问汇编后符号N1和N2的值各是多少。

      STR1  DB  1,1,3,4
      STR2  DW  3 DUP(0),5
      N1  EQU  $-STR2
      N2  EQU  STR2-STR1

4.9 汇编语言源程序中有哪4个段?

4.10 指出汇编、连接过程中能够生成哪6种文件,各自的用途是什么。