汇编语言程序的格式
汇编语言程序的上机过程
伪指令语句
汇编语言程序设计的基本方法本章内容
了解汇编语言程序的基本格式,及其上机过程 。
熟练掌握数据定义伪指令,段定义伪指令,符号定义伪指令,过程定义伪指令 。
熟练掌握汇编语言程序设计的一般步骤以及顺序程序,分支程序,循环程序,子程序设计的方法 。
学习目的
6.1 汇编语言与汇编程序
6.1.1 汇编语言的基本概念
1 机器语言与汇编语言 (Machine Language
and Assembly Language)
由二进制代码 0,1表示的指令称为 机器指令,相应的程序称为机器语言程序 。
用指令助记符表示的 指令 称为 汇编语言指令,对应的程序称为汇编语言程序 。
例 6-1 将 4位二进制数转换为 ASCII码字符。当数在 0000B-1001B时,对应的 ASCII码为
‘ 0’~‘9’;当数在 1010B~1111B时,对应的
ASCII码为‘ A’~‘F’。设待转换的数据已在累加器 AL中(低 4位)。
编制的机器语言程序与汇编语言程序如下表所示。
地 址 机器代码 对应的汇编指令
E380:0000 24 0F AND AL,0FH
E380:0002 3C 0A CMPAL,0AH
E380:0004 72 02 JB NUM
E380:0006 04 07 ADD AL,07H
E380:0008 04 30 NUM:ADDAL,30H
E380:000A C3 RET
2.汇编语言与高级语言 (Assembly Language
and Computer-independent Language)
汇编语言 是一种依赖于计算机微处理器的语言
汇编语言 一般不具有通用性和可移植性
进行 汇编语言 程序设计必须熟悉机器的硬件资源和软件资源
高级语言 是面向过程的语言
高级语言 具有很好的通用性和可移植性学习汇编语言的必要性
1,利用汇编语言可以设计出效率极高的核心底层程序。
2,用汇编语言编写的程序一般比用高级语言编写的程序执行得快,且所占内存较少。
3,汇编语言程序能够直接有效地利用机器硬件资源。
4,学习汇编语言对于理解和掌握计算机硬件组成及工作原理是十分重要的。
6.1.2 汇编语言源程序的组成
1.汇编语言源程序的结构 (Program Structure)
例 6-2 完整的汇编语言源程序示例 。
NAME HEXTOASC;*****************************************************
DATA SEGMENT ; 数据段定义开始
HEX DB 5AH
ASC DB 2 DUP(?)
DATA ENDS ; 数据段定义结束;*****************************************************;*************************************************
STACK SEGMENT ; 堆栈段定义开始
DB 256 DUP('S')
TOP EQU $-STACK
STACK ENDS ; 堆栈段定义结束;*************************************************;************************************************
CODE SEGMENT ; 代码段定义开始
ASSUME CS:CODE,DS:DATA,SS:STACK
START,MOV AX,DATA ; 主程序开始
MOV DS,AX
MOV AX,STACK
MOV SS,AX
MOV SP,TOP
MOV BX,OFFSET ASC
MOV AL,HEX
MOV AH,AL
MOV CL,4
SHR AL,CL
CALL NEAR PTR CONVERT
…
HLT ; 主程序结束
CONVERT PROC ; 过程 (子程序 )定义开始
AND AL,0FH
CMPAL,10
JB NUM
ADD AL,7
NUM,ADD AL,'0'
RET
CONVERT ENDP ; 过程 (子程序 )定义结束
CODE ENDS ; 代码段定义结束;*************************************************
END START ; 程序结束要点总结,
汇编语言源程序由 段结构 组成。
一个段 由,SEGMENT”( 段定义开始 ) 和
,ENDS”( 段定义结束 ) 语句来定义 。
每个段都有 唯一的 段名,前后一致。
不同 段 的段名不能相同。
数据段、堆栈段和代码段的作用各不相同 。
各个段都由一系列 语句 组成 。
语句包括 指令 语句和 伪指令 语句 。
2.汇编语言的语句格式 (Statement Formats)
汇编语言程序的每行语句由 1~4个部分组成。
指令语句和伪指令语句在格式上稍有区别,指令语句的标号后有冒号,,,,而 伪指令 语句的标号后则没有冒号。
指令语句的格式为
[LABEL:] OPERATION [OPERAND] [;COMMENT]
标号域 指令助记符域 操作数域 注释域伪指令语句的格式为
[LABEL] OPERATION [OPERAND] [;COMMENT]
标号域 伪指令助记符域 操作数域 注释域
一行最多可有 132个字符注,
指令助记符前面还可以有 [ 前缀 ]。
汇编程序语句中的四项,均可以用 大 写,
小写 或 混合编写 。
语句的各部分之间至少用一个 空格 分开
3.标号 (Label) (含数据变量标号和程序位置标号 )
标号 是一个自行设计的标识符或名称,
最多可由 31个字母,数字和特别字符 (?,@、
-,$)等组成 。 但
不能用数字开头 。
不能为汇编语言的保留字 。
注:保留字指有专门用途的字符或字符串,
如 CPU的寄存器名,指令助记符,伪指令助记符等 。
(1) 数据变量标号的三种属性
① 段值,即所在段的段地址。
② 偏移量,即数据变量所在位置的地址与其段地址之差值。
③ 类型,指该数据变量是字节、字还是双字。
(2) 程序位置标号的两种属性
① NEAR——段内调用或转移标号。
② FAR——段间调用或转移标号。
4,操作数 域 (Operand Fields)
操作数可以是 常数,变量,标号,寄存器名 或 表达式
(1) 常数,二进制,八进制,十进制,十六进制或 ASC Ⅱ 码字符串 。
(2) 寄存器名,标号,变量指令,MOV AL,HEX 中,AL为寄存器名,HEX为变量名。
(3) 表达式,由 运算符 连接起来的式子叫表达式,
按一定的规则对表达式进行运算后得到一个数值或一个地址 。
表达式可分 算术表达式,逻辑表达式,关系运算表达式,分析运算表达式 和 合成运算表达式 。
用算术运算符 +,-,*,/和 MOD连接的表达式 。
① 算术表达式用逻辑运算符 AND,OR,XOR和 NOT
连接的表达式 。
② 逻辑表达式例如,5*8+30,128/100,206 MOD 128 等算术表达式。
逻辑表达式的结果可能为 8位或 16位二进制数,视参加运算的数的字长而定。
逻辑运算指令中可以包含有逻辑表达式,如指令
AND AL,10101010B AND 0FH
左边第一个 AND为逻辑运算指令,第二个 AND
为逻辑运算符,10101010B AND 0FH 为逻辑表达式,其结果在汇编时即已确定,为
00001010B,故上述指令等价于
AND AL,00001010B
用 EQ,NE,LT,GT,GE和 LE连接的表达式 。 若条件成立,其值为 1,否则为 0。
③ 关系运算表达式分析运算表达式把存储器操作数分解为几个组成部分;而合成运算式是把这些组成部分综合为存储器操作数 。
④ 分析运算表达式和合成运算表达式例如:指令 MOV BX,5 GT 3
等价于 MOV BX,0FFFFH
分析运算表达式和合成运算表达式的操作对象都为 存储器操作数 。
存储器操作数表示变量、符号地址、存储单元等与存储器 地址 有关的量。
分析运算表达式把存储器操作数 分解 为几个部分,如分解出段值、偏移量、类型等;
合成运算表达式则把这些组成部分 综合 为存储器操作数。
6.2 伪指令指令语句,汇编程序把它们翻译成机器代码,命令 8086执行对应操作 。
伪指令语句,汇编程序并不把它们翻译成机器代码,只是用来指示,引导汇编程序在汇编时做一些操作,它本身不占用存储单元 。
* 程序分段及存储器分配
* 变量定义及存储器申请
* 过程定义
* 符号定义
* 程序模块定义与通信
* 宏定义及宏调用
* 条件汇编
* 格式控制,列表及其他功能伪指令主要分为下列几类:
6.2.1 段定义伪指令 SEGMENT/ENDS
1,SEGMENT和 ENDS
定义方式:
[段名 ] SEGMENT[定位方式 ][组合方式 ][类别名 ]
语句 1
语句 2
…
[段名 ] ENDS
段名 是赋予该段的一个名称,SEGMENT
与 ENDS成对出现,且前面的段名要相同,段名的取法与文件名,标号及变量名等相同 。
(1) 段名
LINK程序除完成段与段的联合操作以外,还把联合后得到的各个段互相衔接起来,
段与段的 衔接方式 叫做定位方式,共 4种 。
(2) 定位方式
③ WORD (字 ):表示段从地址是一个最低位为 0的二进制数开始 。
④ PAGE(页 ):表示段从一个页的边界,即十六进制数表示地址的最低两位均为 0处开始 。
① PARA (未定义的 ):表示段从一个节的边界,
即用十六进制数表示地址的最低位为 0处开始 。
② BYTE (字节 ):表示段可从任一地址开始 。
BYTE XXXX XXXX XXXX XXXX XXXX B
WORD XXXX XXXX XXXX XXXX XXX0 B
PARA XXXX XXXX XXXX XXXX 0000 B
PAGE XXXX XXXX XXXX 0000 0000 B
X表示可取 0或 1
定位类型 起 始 地 址
(3) 组合方式程序不同模块中具有相同性质的段若使用同样的段名,则连接时就会把同名的段按照指定的方式组合起来,组合方式共有 6种 。
表示该段与其他模块的段没有任何关系,
每段都有自己的基址 。 这是 缺省 方式 。
① NONE( 缺省 )方式:
③ STACK方式:
连接时把所有 STACK方式的同名段连接成一个段,运行时就是 SS的装入值,且栈指针 SP指向该段的起始地址 。
② PUBLIC方式:
表示该段与其他模块中说明为 PUBLIC方式的同名段互相组合成 一个逻辑段,公用一个段址,运行时装入同一个物理段中 。
⑤ MEMORY方式:
表示该段应定位在所有其他段的上面,
若有多个段选用 MEMORY,则除第一个之外,
其余段均作为 COMMON处理 。
④ COMMON方式:
表示该段与其它模块中所有已说明为
COMMON的同名段共享相同的存储区域,
即具有相同的段起始地址,共享的长度为模块同名段中最大长度 。
例,有 A,B; C,A,B; A,C共 7个段是 3个模块中的段,用 P,C,S分别代表 PUBLIC,
COMMON或 STACK。 组合成 4个新段 (见下图 ),新段的长度为被组合在一起的各段长度之和 (用 PUBLIC和 STACK方式时 ),
或是各段中最长段的长度 (COMMON方式 )。
⑥ AT方式:
表示该段按 绝对地址 定位,其段地址即为其后表达式的值,位移量为 0。
A (P)
B (C)
C (S)
A (P)
B (C)
A
C (S)
模块 1
模块 2
模块 3
LINK
P
C
S
A
B
C
D
连接程序的组合处理示意图
(4) 类别名是一个用 单引号 括起来的 字符串,LINK
程序把类别名相同的所有段放在连续的存储区域内,先出现的在前,后出现的在后 。
例,A SEGMENT ‘FAT’ A
B SEGMENT ‘BAZ’ E
C SEGMENT ‘BAZ’ B
D SEGMENT ‘ZOU’ C
E SEGMENT ‘FAT’ D
LINK 处理前 LINK 处理后
2,GROUP伪指令格式,[组名 ] GROUP [段名 ] [,…]
功能,用来把模块中若干不同名的段集合成一个组,使其装入 同一个物理段中,组内各段之间的跳转可视为 段内跳转 。
注,组名与段名是一样的取名规则,是代表该组的段地址,格式中的段名也可用表达式 SEG[变量 ]或 SEG[标号 ]。
6.2.2 位置计数器 $和定位伪指令 ORG
1,位置 计数器 $
汇编程序专门设置了一个表示 当前位置 的计数器,称 位置计数器 $。正常情况下,汇编程序每扫描一个字节,位置计数器的值便加 1。
语句,TOP EQU $-STACK
含义,当前位置计数器的值 $减去 STACK代表的起始位置的值,然后把两者的差值赋予符号常量 TOP。
语句,JMP $
含义,程序跳转到本条指令执行。
格式,ORG [数据表达式 ]
功能,把位置计数器的值设置为表达式的值 。 ORG后面的一条指令性语句或数据区定义命令即从指定的位置处进行汇编 。
2,ORG伪指令格式,ASSUME [段寄存器 ],[段名 ],
[段寄存器 ],[段名 ],…
功能,用于告知汇编程序,段寄存器 CS、
DS,ES和 SS的内容将被设定为那些段或组的 段址 。
6.2.3 段寻址伪指令 ASSUME
注,(1) 段名 可以是已定义过的任何 段名 或组名,也可以是表达式 SEG [变量 ]或
SEG [标号 ],还可以是 NOTHING。
(2) 除 CS外,DS,ES,SS的设置必须通过指令性语句来完成 。
例 CODE SEGMENT
ASSUME CS:CODE,DS,DATA,
SS,STACK,ES,NOTHING
MOV AX,DATA
MOV DS,AX ;设置 DS
MOV AX,STACK
MOV SS,AX ;设置 SS
CODE ENDS
6.2.4 过程定义伪指令 PROC/ENDP
过程 即是 子程序 。汇编语言规定必须对过程进行定义,以确定过程的三种属性。过程的属性确定之后,就可对调用指令 CALL进行正确汇编,
决定是产生近调用指令还是远调用指令。
1,过程的三种属性
① 段 属性:过程所在段的段地址。
② 偏移量 属性:过程所处位置的段内偏移地址。
③ 类型 属性 (NEAR或 FAR)。过程为 NEAR或 FAR类型。
使用格式:过程名 PROC NEAR/FAR
RET
过程名 ENDP
…
NEAR---近过程。该过程与调用指令 CALL处在同一个代码段中(段名相同)。
FAR ---远过程。该过程与调用指令 CALL处在不同的代码段中(段名不同)。
2,过程的定义功能,把具有一定功能的程序段设计成为一个过程 (子程序 ),便于实现模块化的程序设计 。
注,(1) CALL指令中过程名起着标号的作用 。
有段属性,偏移量属性和类型属性
(NEAR和 FAR)。
(2) 子程序中至少有一个 RET。
过程名是为该过程指定的一个名称,与变量,
标号的定义法相同 。
任何变量均有下列三属性 。
(1) 段属性即变量所使用的段;
(2) 段内偏移属性;
(3) 变量的类型:字节、字、双字等。
6.2.5 数据定义伪指令与存储器分配
1,变量的三种属性
DB—定义字节型变量,每个变量分配 1个存储单元
DW--定义字型变量,每个变量分配 2个存储单元
DD--定义双字型变量,每个变量分配 4个存储单元
2,变量定义及存储器申请伪指令 DB/DW/DD
(1) 格式 1,[变量名 ] { DB/DW/DD}表达式功能,定义一变量,并为其分配一定数量的存储单元,变量的初值由表达式的值指定 。 若初值可任意,则用问号 (?) 表示 。 变量名可省 。
HEX DB 5AH ; 定义 字节 变量
VWORD DW 1234H;定义 字 变量注,在存放字变量时,低字节在前,高字节在后。
BUFFER DW 1,0,-1 ; 定义了三个 字 单元
STR DB ‘Program’ ; 定义了一个 字符串注,在存放字符串时,存储的是字符的 ASCII码。
HEX_OFF DW HEX ; 定义了一个 字 单元,
其初值为已定义变量 HEX的偏移量。
DVAR DD 12345678H ; 定义了一个 双字 单元
ASC DB 2 DUP(?)
功能:分配 2个字节单元,初值任意
BUF DW 100 DUP(0)
功能:分配 100个字单元,初值为 0
ZIP DB 3 DUP (0,2 DUP (1))
功能:存储单元依次初始化为,0,1,1,0,1,1,
0,1,1。
(2) 格式 2,[变量名 ] { DB/DW/DD} DUP 表达式变量经过定义及存储器申请之后,在程序中即可像高级语言一样使用。
MOV AL,HEX ; 取变量 HEX的内容送 AL
寄存器 AL。
MOV ASC[BX],AL ; 取变量 ASC的偏移地址,再与 BX寄存器的内容相加,得到 存储器操作数的有效地址 EA,再将 AL寄存器的内容送入该单元。
3,变量的使用记录名 RECORD 字段名,宽度 [=初值表达式 ],…
6.2.6 记录与结构定义伪指令( RECORD、
STRUC/ENDS)
1.记录定义伪指令 RECORD
记录,能进行 位处理 的 8位 或 16位 二进制数。
字段,记录中相邻的若干位构成一个字段。
记录类型,说明记录中有哪些字段,各字段分别有多少位。
(1)记录类型的定义记录名 和 字段名 遵循标号的取名法则 。 宽度表示该字段占有的二进制位数,最多不能超过 16
位,初值表达式可省,表示该字段的初值 。
如,COLOR RECORD X,3=5,Y:4=12,Z:9=255
1 0 1 1 1 0 0 0 1 1 1 1 1 1 1 1
15 13 12 9 8 0
COLOR
X Y Z
注,当定义中的字段总位数不能占满整个字节或字时,汇编程序就把所定义的字段与记录的 低位端 对齐 。
如,COOL RECORD A:5=17,B:7=127
X X X X 1 0 0 0 1 1 1 1 1 1 1 1
15 12 11 7 6 0
COOL
未定义 A B
(2) 记录类型变量的定义及存储器分配记录可以用记录名称作为操作符。
[名称 ]记录名称 <表达式,…>
表达式 DUP(<表达式,…>)
一般格式:
定义了一个记录型变量,其字段 X,Y和 Z的初值分别为 5( 缺省值 ),15和 9,即
COLOR1=1011,1110,0000,1001B
如,COLOR1 COLOR <,15,9 >
(3) 记录的使用定义了记录型变量之后,在程序中可像一个字节或字型变量来使用。
如,MOV DX,COLOR1
指令执行后,
DX=1011,1110,0000,1001B=BE09H
(4) 记录专用操作符
① MASK操作符:使 字段名所在位置的位全为
1,其余为 0 。
如,AND DX,MASK Z
② WIDTH操作符,返回纪录或纪录中字段的位数 。
2,结构定义伪指令 STRUC/END伪指令使用格式:
结构是一种复杂的数据类型 。 结构体中包含若干个字段,其数据类型一般为基本的数据类型 。
结构名称 STRUC
[字段名称 ] { DB/DW/DD}
结构名称 ENDS
表达式,…
表达式 DUP (表达式,… )
…
…
⑴ 结构类型的定义
COURSE STRUC
NO DD?
CNAME DB 'Assembler’
SCORE DW 0
COURSE ENDS
定义了结构名为 COURSE的结构,该结构包含三个成员变量,结构 体长度为 15(=4+9+2)
个字节。
(2) 结构类型变量的定义及存储器分配如,COURSE1 COURSE < >
定义一个结构变量 COURSE1,其 初值为缺省。
格式,[变量名 ] 结构名 <[字段值表 ]>
(3) 结构的使用定义了结构型变量之后,在程序中即可使用。
格式,结构变量名,字段名如,MOV AX,COURSE1.SCORE
1,EQU伪指令格式:符号名 EQU 数值表达式功能,为常量,变量,表达式或其他符号定义一个名字,但不申请分配内存 。
如,THREE EQU 3
TOP EQU $-STACK
6.2.7 符号定义伪指令( EQU,LABEL)
,=”伪操作与 EQU相似,其区别是前者可重复定义而后者不能 。
使用 EQU可使程序简单明了和便于修改 。
注:
EQU可用 PURGE解除 。
2,LABEL伪指令格式:变量名 /标号 LABEL [类型 ]
功能,为当前存储单元定义一个指定类型的变量名或标号,其 类型 为 BYTE、
WORD,DWORD,结构名,记录名,NEAR和 FAR。
BYTE_ARRAY LABEL BYTE;定义 字节型 数 组标号
WORD_ARRAY DW 50 DUP(?) ;定义 字型 数组如定义不同类型的数组:
程序中的用法:
MOV AL,BYTE_ARRAY[2] ;将 2号 字节 单元内容送 AL
MOV AX,WORD_ARRAY[0] ;将 0号 字 单元内容送 AX
又如,TRANS_N LABEL NEAR
TRANS PROC FAR
这样 TRANS不仅适合 远程调用和转移,
也可用新名 TRANS_N适合 近程调用和转移 。
汇编语言程序可划分为许多模块,对每个模块独立地进行汇编及调试 (见下图 ),
一般从 低层 到 高层 逐步进行 。
6.2.8 程序模块定义伪指令( NAME/END、
PUBLIC/EXTRN)
PUBLIC
二级子模块 1
PUBLIC
二级子模块 2
PUBLIC PUBLIC
二级子模块 n- 1 二级子模块 n
…
PUBLIC PUBLIC PUBLIC
一级子模块 1
EXTRN
一级子模块 2
EXTRN
一级子模块 m
EXTRN
…
主模块
EXTRN
主模块
PUBLIC
二级子模块 1
PUBLIC
二级子模块 2
PUBLIC PUBLIC
二级子模块 n- 1 二级子模块 n
…
一级子模块 一级子模块 一级子模块…
汇编语言程序的模块结构
1,NAME和 END伪指令功能,定义一个模块,作为一个独立的汇编单位,NAME缺省时模块若使用了 TITLE语句,则 TITLE语句中前 6个字符为模块名,
否则源文件名将作为模块名 。
格式,NAME 模块名
END 标号
…
注,一个模块是一个独立的汇编单位,汇编处理只进行到模块结束语句 END为止 。
注,符号必须用,,,分开,且均在本模块中定义过 。
注:
2,PUBLIC伪指令格式,PUBLIC [符号表 ]
功能,用来说明该模块中被定义的那些常量,变量 和 标号 (含过程名 )可以被其他模块所引用 。
如,PUBLIC ABC,BCD,CDE
3,EXTRN伪指令格式,EXTRN [符号:类型,…]
功能,指出本模块中需要引用但却在其他模块中定义并说明为 PUBLIC属性的符号 (含常量,变量,标号和过程 )。
被引用符号的 类型 说明,BYTE,WORD、
DWORD,NEAR,FAR,ABS(符号常量 )。
如,EXTRN ABC,WORD,BCD,ABS,
CDE,NEAR
6.3 汇编语言属性操作符
1,分析操作符(数值返回操作符)
6.3.1 分析操作符( SEG,OFFSET,TYPE、
LENGTH,SIZE)
返回的是变量或标号的 段地址,偏移地址 及 类型 的属性值 。
格式,分析运算符 变量或标号其运算的结果为一常数
(1) SEG—— 取段址算符
(2) OFFSET—— 取偏移地址算符功能,分离出该变量或标号的段址如,MOV AX,SEG BUF
功能,分离出该变量或标号的偏移地址如,MOV SI,OFFSET BUF
(3) TYPE—— 取类型算符
(4) LENGTH—— 取变量所含存储单元的个数功能,分离出该变量所含存储单元的个数 。
功能,分离出该变量或标号的类型的字节数 。
变量 类型为 BYTE,WORD和 DWORD时,
返回的值分别为 1,2和 4;
标号类型为 NEAR或 FAR时,则返回 -1或 -2。
注意,在定义该变量时,数据定义伪指令后面的第一个表达式的形式为,n
DUP(表达式 )”时,取值为 n,否则为 1。
如,BUF1 DB 100 DUP( 0)
BUF2 DW 10,5 DUP( 2)
BUF3 DD 5 DUP( 1,2 DUP( 0))
则 LENGTH BUF1=100,LENGTH BUF2=1
LENGTH BUF3=5。
(5) SIZE—— 取变量所含存储区的总字节数。
功能,SIZE=LNGTH*TYPE
SIZE BUF1=100(因为 TYPE BUF1=1)
SIZE BUF2=2(因为 TYPE BUF2=2)
SIZE BUF3=20(因为 TYPE BUF3=4)
格式,类型 PTR 表达式类型,
表达式,是变量、标号或数值
变量的类型有,BYTE,WORD和 DWORD;
标号的类型有,NEAR和 FAR;
结构名称
6.3.2 属性修改操作符( PTR,THIS,SHORT)
1,PTR操作符
PTR操作符用于 暂时 修改变量或标号的原有属性。
功能,PTR把它左边的属性指派给它右边的变量,
标号或数值,使之产生一个新的存储器地址操作数 。
如,(1) INC BYTE PTR [BX]
(2) ADD DX,WORD PTR FOOB[20]
新存储器操作数的 段地址和段内偏移量与
PTR运算符右边的操作数的对应量相同 。
PTR给已分配的存储器一个另外的定义但并不重新分配存储器 。
2,SHORT操作符
SHORT用于条件转移,转移和调用指令中,指出其转移的相对位移量不超过一个字节所能表达的范围 。
3,THIS操作符
THIS操作符与 PTR操作符有类似的功能,
但新的属性放在 THIS的 右边 。
格式,THIS 类型 /距离其中类型是 BYTE,WORD和 DWORD,
距离是 NEAR和 FAR属性 。
功能,与 PTR类似建立一个新的存储器地址操作数,但不分配存储器,其类型由
THIS指定 。
(1) FOOB EQU THIS BYTE
(2) FAR-OUT EQU THIS FAR
如:
6.4 汇编语言程序的上机过程
6.4.1 汇编语言程序上机运行的软件环境
DOS操作系统下,编辑、修改和运行汇编语言程序,需要用文本编辑软件、宏汇编程序、连接程序和调试程序。
① 文本编辑软件,EDIT.EXE等
② 宏汇编程序,MASM.EXE,TASM.EXE等
③ 连接程序,LINK.EXE,TLINK.EXE等
④ 调试程序,CV.EXE,TD.EXE等
6.4.2 源程序的编辑与汇编
1,编辑源程序
C:\ASM>EDIT HEXTOASC.ASM( 回车 )
2,汇编源程序
C:\ASM>TASM HEXTOASC ( 回车 )
在汇编中,如果有语法错误,会给出提示信息,指出错误的类型、行号。当汇编无错之后,会自动生成目标文件(文件 扩展名 为 OBJ)。
注:待编辑的文件扩展名必须为 ASM。
汇编程序通过 ASSUME语句了解到运行时各段寄存器的设定值后,就可以对被汇编的指令语句中的变量和标号作如下处理 。
3,汇编程序对变量和标号的处理
1,检查指令中所引用的变量和标号是否合理,即它们的段属性是否和某个段寄存器的段假设值相符 。
2,检查是否需要为所引用的变量和标号是否产生跨段前缀字节,即检查变量和标号的段属性是否与硬件为该指令所规定的段寄存器的假设值相符 。
6.4.3 目标程序的连接
C:\ASM>TLINK HEXTOASC ( 回车 )
连接成功后,将生成可执行程序 HEXTOASC.EXE。
6.4.4 程序的调试与运行
1.调试可执行程序利用 Turbo Debuger( TD) 对可执行程序进行调试,以检查程序可能存在的各种错误:
C:\ASM>TD HEXTOASC.EXE ( 回车 )
程序在存储区中存放的逻辑地址源程序部分 程序执行后各寄存器,标志位的结果数据段中存放的 40个
,A”及对应的 ASCII码
DEBUG调试环境如下:
2.运行可执行程序
C:\ASM>HEXTOASC ( 回车 )
6.5 汇编语言程序与 DOS的接口
6.5.1 DOS和 BIOS的功能调用
1,DOS系统 功能调用的一般步骤
DOS为磁盘操作系统( Disk Operating System)的简称。 DOS提供了极为丰富的子程序,能够实现控制键盘、显示器、读写文件、串行通信等一系列功能 。
使用 DOS系统功能调用的一般过程:见下图所示。
功能调用号?AH
置入口参数执行,INT 21H,
分析出口参数
…
…
系统功能调用的方法
( 1)键盘输入 (1号调用 )
格式,MOV AH,1
INT 21H
功能,等待从键盘输入一个字符并将输入字符的 ASCII码 送入寄存器
AL中,碰到 CTRL+Break则退出 。
2,DOS系统功能调用的一般步骤
( 2)显示单个字符 (2号调用 )
格式,MOV AH,2
MOV DL,待显字符的 ASCII码
INT 21H
功能,将 DL中的字符送显示器显示,若为 CTRL+Break的 ASCII码 则退出 。
( 3)控制台输入 (8号调用 )
格式,MOV AH,8
INT 21H
功能,与 1号 相似,但只从键盘上输入而 不显示 。
(4) 显示字符串 (9号调用 )
格式,LEA DX,字符串首偏移地址
MOV AH,9
INT 21H
功能,将当前数据区中以 ‘ $’结尾的字符串送显示器显示 。
例如,要显示下列 DIS0数组变量中定义的字符串
DIS0 DB ‘PRESS ANY KEY TO
QUIT’,0DH,0AH,‘$’
程序如下,
LEA DX,DIS0
MOV AH,09H
INT 21H
(5) 键盘输入字符串 (10号调用 )
格式,LEA DX,缓冲区首偏移地址
MOV AH,10
INT 21H
功能,从键盘上往指定缓冲区中输入字符串并送显示器显示 。
如,BUF DB 81
DB?
DB 80 DUP (0)
注,缓冲区应按规定的格式定义。
BUF第一字节规定了缓冲区的大小,从键盘输入的字符串从第三个字节存放,第二个字节存放实际输入的字符个数 。
3.常用 BIOS调用
BIOS为基本输入输出系统( Basic Input and Output
System),它提供了最底层的控制程序。
( 1) 设置显示器显示模式功能,设置显示器显示模式 。 显示模式代码见附录 E。
格式,MOV AH,00H
MOV AL,显示模式代码
INT 10H
( 2) 设置光标位置功能,设置光标位置,使字符从该位置处开始显示 。
MOV AH,02H
MOV BH,页号
MOV DH,行号
MOV DL,列号
INT 10H
6.5.2 用户程序与 DOS的接口
1.程序段前缀 PSP
程序段前缀 PSP是一个 256字节的区域,从页的边界开始存放有关信息。用户程序可从 PSP中获得有关键盘输入参数等信息。
当 DOS加载一个可执行文件的程序代码到内存中去时,它首先为该程序建立一个 程序段前缀 PSP,
然后把可执行的程序代码加载到 PSP后续的地址上。
2,用户程序与 DOS的接口在 DOS操作系统下,用户程序的主程序,对于操作系统而言,也是一个过程,且必须说明为 FAR属性。
3.结束用户程序返回 DOS的方法
( 1) INT 20H
说明,INT 20H为系统,结束任务返回 DOS”
的子程序 。
( 2)用户程序用 RET指令返回说明,用户程序必须定义为一个 FAR过程 。
( 3)用 DOS系统功能调用 4CH返回格式,MOV AH,4CH
INT 21H
6.6 汇编语言程序设计的基本技术
6.6.1 简单程序设计例 6-3 编写程序段,完成下面公式的计算 (其中:变量 X
和 Y是 32位无符号数,变量 A,B和 Z是 16位无符号数 ),
(X-Y-29)/Z的商 → A,(X-Y-29)/Z的余数 → B
DATA SEGMENT
X DD 453921F0H
Y DD 123A6825H
Z DW 0A86CH
A DW?
B DW?
DATA ENDS
定义数据段,
CODE SEGMENT
…
MOV AX,WORD PTR X ; 取 X的低位字
MOV DX,WORD PTR X+2 ; 取 X的高位字
SUB AX,WORD PTR Y ; 与 Y的低位字相减
SBB DX,WORD PTR Y+2 ; 与 Y的高位字相减,并; 考虑低位的借位
SBB AX,29D ; 结果的低位与 29D相减
SBB DX,0 ; 可能产生借位,再减去借位
DIV Z ; 32位无符号数 ( DX:AX) 除以 16位无符号数 Z
MOV A,AX ; 商在 AX中,保存商
MOV B,DX ; 余数在 DX中,保存余数
CODE ENDS
定义代码段,
例 6-4 编写程序段,完成下面公式的计算,A=(X+Y)-
(W+Z),其中 X,Y,Z,W均为用压缩 BCD码表示的数。
DATA SEGMENT
X DB 39H
Y DB 25H
W DB 86H
Z DB 46H
A DB?
DATA ENDS
定义数据段,
MOV AL,W
ADD AL,Z ; AL=(W+Z)
DAA ; 加法的十进制调整
MOV A,AL ; 调整后的结果存到单元 A
MOV AL,X
ADD AL,Y ; AL=(X+Y)
DAA ; 加法的十进制调整
SUB AL,A ; AL=( X+Y) -( Z+W)
DAS ; 减法的十进制调整
MOV A,AL ; 结果送 A
程序如下,
例 6-5 编写完整的汇编语言程序,用 8086的 16位无符号数乘法指令实现两个 32数的乘法运算。
算法分析,8086没有 32位无符号数乘法指令,需借助于
16位无符号数乘法指令做 4次乘法,然后把部分积相加,
如下图所示 。
A B
B × D
C D
A × D
B × C
A × C+
×
部分积 1
部分积 2
部分积 3
部分积 4
完整的汇编语言程序如下:
NAME MULTIPLY_32BIT
DATA SEGMENT
MULNUM DW 8000H,0001H,0FFFH,0001H ;定义被乘数 B、;A与乘数 D,C
PRODUCT DW 4 DUP(?) ;定义乘积,低字在前
DATA ENDS
STACK SEGMENT PARA STACK ‘STACK’
DB 100 DUP(?)
STACK ENDS
例如,求,0001 8000H*0001 0FFFH=0000000197FE8000H
A B C D
CODE SEGMENT
ASSUME CS,CODE,DS:DATA,SS:STACK
START PROC FAR
BEGIN,PUSH DS ; DS中包含的是程序段前缀的起始地址
MOV AX,0
PUSH AX ; 设置返回至 DOS的段值和偏移量
MOV AX,DATA
MOV DS,AX ; 置段寄存器初值
MOV BX,0
MULU32,MOV AX,MULNUM[BX] ; B→AX
MOV SI,MULNUM[BX+4] ; D→SI
MOV DI,MULNUM[BX+6] ; C→DI
MUL SI ; B× D
MOV PRODUCT[BX],AX ; 保存部分积 1
MOV PRODUCT[BX+2],DX
MOV AX,MULNUM[BX+2] ;A→AX
MUL SI ; A× D
ADD AX,PRODUCT[BX+2]
ADC DX,0 ; 部分积 2的一部分与部分积 1的相应部分相加
MOV PRODUCT[BX+2],AX
MOV PRODUCT[BX+4],DX ; 保存
MOV AX,MULNUM[BX] ; B→AX
MUL DI ; B× C
ADD AX,PRODUCT[BX+2] ; 与部分积 3的相应部分相加
ADC DX,PRODUCT[BX+4]
MOV PRODUCT[BX+2],AX
MOV PRODUCT[BX+4],DX
PUSHF ; 保存后一次相加的进位标志
MOV AX,MULNUM[BX+2] ;A→AX
MUL DI ; A× C
POPF
ADC AX,PRODUCT[BX+4] ; 与部分积 4的相应部分相加
ADC DX,0
MOV PRODUCT[BX+4],AX
MOV PRODUCT[BX+6],DX
RET
START ENDP
CODE ENDS
END BEGIN
程序中根据各种可能出现的情况及相应的处理方法分成若干支路,运行时,根据不同情况有选择地执行相应处理程序 。
例 6-6 试编写程序段,实现符号函数。
1
0
1
( x )s i g n y
当 x > 0时当 x = 0时当 x < 0时
6.6.2 分支程序设计相应的程序段为;
SIGN,MOV AX,BUFFER ;( BUFFER) =X
AND AX,AX ; 做一次,与,运算,以把 X
的特征反映到标志位
MOV BX,0 ; 预置 X=0的标志 0到 BX
JE NEXT ; 若 X确实为 0,则转 NEXT
JNS PLUS ; X为正数,则转 PLUS
MOV BX,-1 ; X为负数,则- 1送 BX
JMP NEXT
PLUS,MOV BX,1
NEXT,…
…
…
例 6-7 在数据段中定义了 256个子程序的入口地址
(段地址:偏移地址),试根据 AL中的值,决定调用
256个子程序中的哪一个。
分析:每个子程序的入口地址占用 4个字节,需将
AL的值乘以 4,再加上入口地址表首的偏移地址,即可得到某子程序入口地址在表中的偏移地址,将该地址送入 BX,利用段间间接调用指令实现子程序调用 。 这种处理方法,类似于第 9章中将要介绍的中断向量表的处理 。
BX=入口地址表首偏移地址 +AL*4
DATA SEGMENT
TABADD DD SUB0 ; 0#子程序入口地址
DD SUB1 ; 1#子程序入口地址
……
DD SUB255 ; 255#子程序入口地址
DATA ENDS
STACK SEGMENT PARA STACK ‘STACK’
DB 100 DUP(?)
STACK ENDS
CODE1 SEGMENT;代码段 CODE1,实现查表转移的程序段
ASSUME CS,CODE1,DS:DATA,SS:STACK
……
XOR AH,AH ; AH清 0
MOV CL,2
SHL AX,CL ; AX左移 2次相当于乘以 4
MOV BX,OFFSET TABADD ; 取表首的偏移地址
ADD BX,AX ; 加上 AL*4
CALL DWORD PTR [BX] ; 段间间接调用子程序
……
CODE1 ENDS;代码段 CODE2定义 256个子程序 ( 过程 ),
CODE2 SEGMENT
ASSUME CS,CODE2
SUB0 PROC FAR
……
RET
SUB0 ENDP
SUB1 PROC FAR
……
RET
SUB1 ENDP
……
SUB255 PROC FAR
……
RET
SUB255 ENDP
CODE2 ENDS
一段程序有时重复执行多次,就牵涉到循环程序结构,其组成部分为:
1,循环体即要求重复执行的程序段部分,包括循环工作部分和循环控制部分 。
2,循环初态循环开始时往往要置初态,赋初值,
包括循环工作部分初态和结束条件初态 。
6.6.3 循环程序设计
3,循环结束条件在循环程序中必须给出循环结束条件,
否则就会进入死循环,结束条件有好多种,
最常见的有:
(1) 用计数器控制循环
(2) 按问题的条件控制循环
(3) 用开关量控制循环
(4) 多重循环例 6-8 分类统计字数组 ARRAY中正数,负数和零的个数,并分别存入内存字变量
POST,NEGA和 ZERO中,数组元素个数保存在数组的第一个字中 。
分析:将字变量与 0比较,然后判断是大于 0( JG),等于 0( JE) 还是小于 0(JL),
以分别对相应的计数器加 1。
DATA SEGMENT
ARRAY DW 8 ; 元素个数
DW 230,-1437,26,-31,0,3458,0,10
POST DW 0
NEGA DW 0
ZERO DW 0
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START,MOV AX,DATA
MOV DS,AX
XOR AX,AX ; 用 AX作为正数的计数器
XOR BX,BX ; 用 BX作为负数的计数器
XOR DX,DX ; 用 DX作为零的计数器
MOV CX,ARRAY ; 用 CX来进行循环计数
JCXZ DONE ; 考虑数组的元素个数为 0的情况
LEA DI,ARRAY+2; ; 用指针 DI来访问整个数组
AGAIN,CMP WORD PTR[DI],0 ; 与 0做比较
JG HIGH ; 大于 0,为正数
JE EQUAL ; 等于 0
INC BX ; 小于 0,为负数,负数个数增 1
JMP NEXT
HIGH,INC AX ; 正数个数增 1
JMP NEXT
EQUAL,INC DX ; 0的个数增 1
NEXT,INC DI
INC DI
LOOP AGAIN
DONE,MOV POST,AX ; 把各类的统计数保存到内存单元中
MOV NEGA,BX
MOV ZERO,DX
MOV AX,4C00H ; 结束程序返回 DOS
INT 21H
CODE ENDS
END START
例 6-9 统计字变量 VAL中 0和 1的个数,
并将统计结果分别送字单元 X和 Y中 。
分析:每次将最高位移入 CF中进行测试,若为 1,则 X单元加 1;若为 0,则 Y单元加 1。 一个字有 16位,故有 16次循环 。
程序如下:
MOV CX,16 ; 16次循环
MOV SI,0 ; SI中存放 0的个数
MOV DI,0 ; DI中存放 1的个数
MOV AX,VAL
AGAIN,SHL AX,1
JC NOZERO ; 为 1,转走
INC SI ; 为 0,SI加 1
JMP NEXT
NOZERO,INC DI ; 为 1,DI加 1
NEXT,LOOP AGAIN
MOV X,SI
MOV Y,DI
6.6.4 子程序设计子程序 是程序设计中经常使用的程序结构,通过把一些固定的、经常使用的功能做成子程序的形式,可以使源程序及目标程序大大缩短,提高程序设计的效率和可靠性。
在主程序与子程序中经常要进行参数传递 。 参数传递一般有三种方法:
① 利用 寄存器 。 这是一种最常见方法,把所需传递的参数直接放在主程序的寄存器中传递给子程序 。
② 利用 存储单元 。 主程序把参数放在公共存储单元,
子程序则从公共存储单元取得参数 。
③ 利用 堆栈 。 主程序将参数压入堆栈,子程序运行时则从堆栈中取参数 。
例 6-10 编写一个将单字节的二进制数转换成 BCD码数的程序,再将对应的十进制数位转换成 ASCII码字符串,在显示器上显示出来 。
分析:设单字节二进制数存放在 NUMBIN单元 。 利用除法实现转换 。
第一步,将该数除以 100,商即为 BCD码数的百位,
保留第一步所得余数 。
第二步,将第一步所得余数再除以 10,商即为 BCD码数的十位,余数即为 BCD码数的个位 。
第三步,将 BCD码数的百位,十位和个位分别加上
30H,即为它们的 ASCII码 。
第四步,将 ASCII码字符串存入一个缓冲区,然后调用 DOS功能调用 INT21H 的 09H号功能即可显示该字符串 。
DATA SEGMENT
NUMBIN DB 0E7H ; 待转换的单字节二进制数
STRING DB 10 DUP(20H) ; 定义显示缓冲区
DB 0DH,0AH ; 定义回车换行符
DB ‘$’ ; 定义结束符
DATA ENDS
STACK SEGMENT PARA STACK ‘STACK’
DB 100 DUP(?)
STACK ENDS
CODE SEGMENT
ASSUME CS,CODE,DS:DATA,SS:STACK
START PROC FAR
BEGIN,PUSH DS
MOV AX,0
PUSH AX
MOV AX,DATA
MOV DS,AX ; 置段寄存器初值
LEA DI,STRING
XOR AH,AH ; AH清零
MOV AL,NUMBIN
MOV BL,100D
DIV BL ; AX除以 BL,商在 AL中,余数在 AH中
CALL BCDTOASC ; 调用转换程序,入口参数:
AL=BCD数
MOV AL,AH ; 取余数送 AL
XOR AH,AH
MOV BL,10D
DIV BL
CALL BCDTOASC
MOV AL,AH
CALL BCDTOASC
CALL DISPASC ; 调用显示程序
RET
START ENDP
BCDTOASC PROC ; BCD码到 ASCII码转换程序
ADD AL,30H
MOV [DI],AL ; 保存 ASCII字符
INC DI ; 指向下一个单元
RET
BCDTOASC ENDP
DISPASC PROC ; 显示子程序
LEA DX,STRING
MOV AH,09H
INT 21H
RET
DISPASC ENDP
CODE ENDS
END BEGIN
例 6-11 利用堆栈传递被加数和加数,在子程序中实现两个 32位无符号数相加的过程,结果置于 DX:AX中返回 。
DATA SEGMENT
NUM1 DD 01234567H ; 定义第一个 32位数,存放顺序:
67H,45H,34H,01H
NUM2 DD 89ABCDEFH ; 定义第二个 32位数,存放顺序:
0EFH,0CDH,0ABH,89H
RESULT DD (?) ; 定义结果单元
DATA ENDS
STACK SEGMENT PARA STACK 'STACK'
DB 100 DUP(?)
STACK ENDS
CODE SEGMENT
ASSUME CS,CODE,DS:DATA,SS:STACK
START PROC FAR
BEGIN,PUSH DS
MOV AX,0
PUSH AX
MOV AX,DATA
MOV DS,AX
MOV BX,OFFSET NUM1
MOV AX,[BX] ; 取第一个数的低字 (4567H)
PUSH AX ; 压入堆栈
MOV AX,[BX+2] ; 取第一个数的高字 (0123H)
PUSH AX ; 压入堆栈
MOV AX,[BX+4] ; 取第二个数的低字 (0CDEFH)
PUSH AX ; 压入堆栈
MOV AX,[BX+6] ; 取第二个数的高字 (89ABH)
PUSH AX ; 压入堆栈
CALL ADDPROC ; 调用过程
MOV BX,OFFSET RESULT ; 保存结果
MOV [BX],AX
MOV [BX+2],DX
RET
START ENDP
ADDPROC PROC ;32位无符号数相加的过程
PUSH BP ;保护 BP
MOV BP,SP ;将当前的堆栈指针 SP送 BP
MOV AX,[BP+10] ;取第一个数的低字
MOV DX,[BP+8] ;取第一个数的高字
ADD AX,[BP+6] ;与第二个数的低字相加
ADC DX,[BP+4] ;与第二个数的高字相加,并;考虑低字相加的进位
POP BP ;恢复 BP
RET 8 ;返回,并使 SP再加 8,以丢;弃堆栈中参数
ADDPROC ENDP
设被加数为 A,加数为 B,先压入 A的低字,再压入 A
的高字;然后再压入 B的低字和高字 。 主程序在执行
CALL指令之前的堆栈情况如下图 ( a)所示,
进入子程序之后,SP减 2,将 CALL指令的下一条指令的返回地址压栈 (因是近调用,只存放偏移地址 )。
执行 PUSH BP指令之后,SP再减 2。 当把 SP赋给 BP时,
BP指向了堆栈的栈顶,而 BP+4则指向了参数区,此时堆栈的情况如下图 (b)所示 。 图中 SP的具体值与系统初始化情况有关 。
( a) CALL 指令之前的堆栈
SP指针 堆栈区地址 堆栈区内容
SP→ SS:0058 89ABH
SS:005A CDEFH
SS:005C 0123H
SS:005E 4567H
栈底 SS:0060 /////
( b) 执行 PUSH BP 指令之后的堆栈
SP指针 堆栈区地址 堆栈区内容
BP=SP→ SS:0054 BP内容
SS:0056 返回地址
BP+4 SS:0058 89ABH
BP+6 SS:005A CDEFH
BP+8 SS:005C 0123H
BP+10 SS:005E 4567H
栈底 SS:0060 /////