汇编语言程序格式
地址计数器
汇编器在将源程序转换为目标程序的过程中,每汇编一个段,都需要跟踪其中代码或数据的偏移地址,这就是地址计数器。
地址计数器的值表示当前偏移地址。在缺省情况下,段的偏移地址从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位程序。