汇编语言――程序格式

时间:2021-10-16 01:26:37
汇编语言程序格式 
地址计数器
 汇编器在将源程序转换为目标程序的过程中,每汇编一个段,都需要跟踪其中代码或数据的偏移地址,这就是地址计数器。
 地址计数器的值表示当前偏移地址。在缺省情况下,段的偏移地址从0开始。
例如,下列指令序列:
0: mov bl, al  ; 机器码为2字节
2: and bl, 0fh  ; 机器码为3字节
5: mov bh, al  ; 机器码为2字节
7: shr bh, 4  ; 机器码为3字节
A:
      若该指令序列出现在段的开始,那么,在汇编相应指令时,地址计数器的值如左侧所示。例如,第1条MOV指令始于偏移地址0,由于该MOV指令是2字节,故下一条指令始于偏移地址2,等等。
  汇编语言语句  
1.语句格式
(1)语句的书写形式:
 名字 助记符 操作数 ; 注释
(2)MASM对语句格式的要求:
 大小写无关。
 每条语句必须占1行,但可以使用续行符“\”。
 为了提高可读性,应该使各个域对齐。
2.常数与数值表达式
 整数。包括二进制、十进制、八进制或十六进制表示的整数。
 字符与字符串。必须用单引号或双引号括起来。
 数值表达式。
3.变量、标号与地址表达式
 变量与标号的3种属性:段地址、偏移地址、类型。
 变量的类型包括BYTE(字节)、WORD(字)、DWORD(双字)等。标号的类型包括NEAR和FAR。
 地址表达式。
 地址表达式的基本形式为:
  变量名或标号名 ± 常数
其类型由相应的变量或标号确定。
 两个地址表达式的差表示两个地址之间的距离(字节数),两个地址必须在同一个段内。注意,不能将两个地址表达式相加。
 $是一个特殊的地址表达式,表示当前地址,即地址计数器的当前值。
基本伪指令
1. 处理器选择伪指令
     在缺省方式下,MASM只承认8086指令。
     为了使用更高的CPU指令,必须使用处理器选择伪指令,主要包括:
 .8086 .286  .286P  .386 .386P
 .486  .486P .586 .586P .686 .686P
分别表示其后面的代码使用相应CPU的指令。其中,结尾的'P'表示使用特权指令。
      若使用32位CPU新增指令以及寄存器或内存寻址方式,则至少要用.386伪指令。
2.  段定义伪指令
    段定义由SEGMENT与ENDS伪指令实现,基本形式如下:
  段名 SEGMENT      STACK  USE16
             <语句序列>
  段名     ENDS
    其中,STACK仅用于堆栈段。USE16指出使用16位段。
    在实模式下,只能使用16位段,而32位段只能用于保护模式程序。
    在实模式下,如果要使用32位指令,还必须在段定义时给出USE16。
  段名作为操作数出现在指令中时,MASM将其视为立即数,表示段地址。
 
3.  符号定义伪指令
基本形式:
 符号名 EQU 表达式
 符号名   =        常数表达式
功能:给表达式指定一个等价的符号名。
说明:
(1)= 后的表达式只能是常数,对于字符或字符串,汇编时按整数处理。例如:
 COUNT = 20 
   MOV    CX, COUNT ; 等价于MOV CX, 20
(2)EQU后的表达式可以是数值、字符串、寄存器名、指令助记符等。
(3)EQU不能重复定义,而 = 可重复定义,其作用域从定义点到重新定义之前。
4.  变量定义伪指令
变量定义伪指令用来为数据分配内存空间,并设置相应内存单元的初始值。
形式:
 变量名 变量定义符    操作数,,...,操作数
其中,变量名是一个符号地址,表示其后操作数的首地址,
变量名为可选项,给出变量名只是为了按名存取其对应的内存单元。
变量定义符主要包括下列几种:
 DB(Define Byte):定义字节,后面的每个操作数占1个字节。
 DW(Define Word):定义字,后面的每个操作数占1个字。
 DD(Define Dword):定义双字,后面的每个操作数占2个字。
操作数可以是常数、用EQU或=定义的符号常量、表达式、?和DUP子句等。
其中,?表示只保留内存空间,未定义初始值。DUP子句的格式为:
 重复次数  DUP (操作数,...,操作数)
说明:
(1)变量可以定义在任何段(包括代码段),但一般定义在数据段。
(2)用DW/DD/DQ/DF/DT定义的数据在内存按“低字节在低地址”的方式存放。例如:
  S1 DB 'AB' ; 等价于 S1  DB  41H, 42H
  S2 DW 'AB' ; 等价于 S2  DW  4142H
(3)当DW与DD后的操作数是地址表达式时,分别表示其16位偏移地址和32位分段地址(段地址在高字,偏移地址在低字)。例如:
  X DB 10 DUP (?)
  ADDR1 DW   X    ; ADDR1的内容(字)为变量X的偏移地址
  ADDR2 DD X  ; ADDR2的高字为X的段地址,低字为X的偏移地址
(4)MASM是强类型的。变量在定义后,其类型便被确定,使用时要注意类型匹配。例如:
  OP1 DB ?, ?
  OP2 DW ?, ?
下列两条指令执行不同类型的操作:
  MOV OP1 + 1, 0  ; 字节操作指令,将0作为1个字节送到地址OP1 + 1
  MOV OP2 + 2, 0  ; 字操作指令,将0作为1个字送到地址OP2 + 2
然而,下列两条指令是错误的:
  MOV OP1, AX  ; 类型不匹配
  MOV OP2, AL  ; 类型不匹配
       若希望进行与变量类型不一致的操作,例如,对字变量实施字节操作,如何处理?
       可以采用下面介绍的LABEL伪指令或类型操作符PTR等。
5.  LABEL
 基本形式:
  名字 LABEL 类型
 功能:将名字作为一个符号地址,指定其类型,地址由所在位置确定。然而,并不为名字分配内存空间。
 说明:当类型是BYTE、WORD、DWORD时,名字作为相应类型的变量;当类型是NEAR或FAR时,名字作为相应类型的标号。
【例】 定义地址相同、类型不同的两个变量。
  ba label byte
  wa dw 50 dup (?)
  将100个字节的数组首地址赋予两个不同类型的变量:字节类型变量BA与字类型变量WA。尽管WA + 2与BA ­+ 2指向同一内存地址,但下列两条指令的操作类型不同:
   mov wa + 2, 0 ; 将0作为1个字送到地址WA + 2
   mov ba + 2, 0 ; 将0作为1个字节送到地址BA + 2
6. ASSUME 
基本形式:
 ASSUME 段寄存器名:段名, ..., 段寄存器名:段名
功能:
    明确指出段与段寄存器的缺省对应关系,即告诉MASM某个段的段地址在哪个段寄存器里。这样,汇编器会根据变量所在的段,必要时自动插入段超越前缀。
例如,下列伪指令
     ASSUME CS:CSEG, DS:DSEG, ES:ESEG, SS:SSEG 
指出CSEG、DSEG、ESEG和SSEG分别为代码段、数据段、附加段和堆栈段。
    通常,访问的数据在DS所指的数据段或SS所指的堆栈段,而所有的代码引用(如转移等)相对于当前代码段。那么,汇编器如何知道哪个段是代码段、哪个是数据段呢?
    实际上,数据段之所以成为数据段,是由于DS指向它。由于程序运行时可以改变DS的值,使得任何段都可以成为数据段。因此,当程序定义一个段后,需要告诉汇编器该段的段地址在哪个段寄存器中。ASSUME就提供这种信息。
6.  源程序结束伪指令  
 基本形式:
  END 地址
 功能:
        表示汇编语言源程序到此结束,对END之后的语句不再进行汇编。可选的地址指出程序执行的起始点,通常是标号或过程名。
        若程序包含多个源文件,则每个源文件的最后必须有一条END语句,但只有主模块文件可以指出执行的起始地址。
7.  ORG
        通常,段中的数据或指令是按顺序一个接着一个存放的。ORG可以控制数据或代码的偏移地址,形式为:
  ORG 常数表达式
 功能:
       设置其后数据或代码的起始偏移地址为n,设常数表达式的值为n。也就是将地址计数器的值置为n。
【例】 已知下列数据段,指出变量V1、V2的偏移地址。
 data segment 
  org 200h
 v1 dw 1,2 ; v1的偏移地址为200h
  org $ + 10h ; 跳过10h个字节
 v2 db ? ; v2的偏移地址为214h
 data ends  
  操作符
1.地址操作符
    地址操作符的返回值都是数值,相当于立即数,是汇编时由MASM自动计算的。
(1)SEG
形式:
 SEG  地址表达式
功能:返回地址表达式的段地址,作为立即数使用(汇编时求值)。
(2)OFFSET
形式:
 OFFSET 地址表达式
功能:返回地址表达式的偏移地址,作为立即数使用(汇编时求值)。
(3)TYPE、SIZEOF和LENGTHOF
形式:
 TYPE  变量
 SIZEOF  变量 
 LENGTHOF 变量
功能:
     TYPE返回一个常数,表示变量的类型。对于BYTE、WORD、DWORD、类型,分别返回1、2、4。
    LENGTHOF返回直接分配给指定变量的元素个数。
    SIZEOF返回直接分配给指定变量的字节数。
2.类型操作符
(1)PTR
形式:
 类型 PTR  内存操作数或标号
功能:返回一个指定类型的内存操作数或标号,而地址不变。对于内存操作数,类型包括BYTE、WORD、DWORD等。对于标号,类型包括NEAR和FAR。
说明:PTR只是临时改变操作数的类型,类似于C语言的强制类型转换。
(2)THIS
形式:
 THIS 类型
功能:返回一个指定类型的内存操作数或标号,地址由所在位置确定,即地址计数器的当前值。
【例】设有如下定义:
 WordVar dw 10 dup (?)
欲将WordVar的第0个字节置为1,如何处理?
方法1:用PTR操作符。
 WordVar dw 10 dup (?)
  ...
  mov byte ptr WordVar, 1
方法2:用THIS与EQU。
 ByteVar equ this byte
 WordVar dw 10 dup (?)
  ...
  mov ByteVar, 1
 
汇编语言源程序结构 
1.源程序的一般结构
 源程序由若干个代码段、数据段、附加段和堆栈段组成。
 段之间的顺序可以随意安排。
 通常需要一个代码段、一个数据段和一个堆栈段,有时可包含一个附加段。
2.源程序的基本框架
 .386 
 ... ; 若干符号定义
dseg  segment  use16
 ... ; 数据定义(DB/DW/DD)
dseg    ends
eseg  segment  use16
 ... ; 数据定义(DB/DW/DD)
eseg     ends
sseg     segment stack  use16
        dw 512 dup ( ? ) 
sseg     ends
*****
cseg    segment    use16
           assume      cs:cseg, ds:dseg, es:eseg, ss:sseg
start:   mov          ax, dseg
           mov          ds, ax
           mov          ax, eseg
           mov          es, ax
            ..       ; 指令序列
           mov          ah, 4ch
           int           21h       ; 程序退出,返回DOS
cseg    ends
           end           start
3.说明
(1)为什么要用ASSUME语句?
    每当MASM遇到一个变量名时,首先检查看哪个段定义了该变量。例如,遇到指令
 MOV  TABLE,0时,MASM首先确定TABLE所在的段。然后,看该段是否已在ASSUME语句中声明。若未声明,则显示错误信息“不能访问该变量”;若该段不对应于DS,假设对应于ES,则MASM产生一个段超越前缀ES:,即
 MOV  ES:TABLE, 0
    实际上,ASSUME的功能也仅此而已。此外,段超越前缀总是超越ASSUME的定义。
    注意,ASSUME只是告诉汇编器段寄存器指向哪个段,并不设置段寄存器的值。
(2)设置段寄存器的初值。
       CS与IP的初值不能在程序中显式设置,由系统自动设置为END后指定的起始地址。
       DS、ES的初值必须在程序中设置。
       SS与SP的初值可在程序中显式设置。然而,若堆栈段定义时给出了属性STACK,则由系统自动设置。

汇编语言程序的开发 
1. 开发过程
源程序的编辑。
源程序的汇编。
目标文件的连接。
可执行文件的运行。
可执行文件的调试。
2. 汇编器ML
 ML可以自动调用LINK,依次进行汇编和连接。常用命令如下:
  ML /Zi 源文件名
3. 调试器CodeView
CodeView可以调试32位程序。
Debug只能调试16位程序。