# 汇编语言程序开发

# 汇编语言程序的语句格式

# 硬指令 & 执行性语句

硬指令:使 CPU 产生动作、并在程序执行时才处理的语句

执行性语句 —— 表达处理器指令,由硬指令构成的语句,它通常对应一条机器指令,出现在程序的代码段中,格式:

1
2
标号: 硬指令助记符 操作数,操作数,... ;注释
again: mov dx, offset string ;获得string的偏移地址并保存到dx中

标号:反映硬指令位置(逻辑地址)的标识符,后跟一个冒号分隔

硬指令助记符:可以是任何一条处理器指令

处理器指令的操作数可以是立即数、寄存器和存储单元

# 伪指令 & 说明性语句

伪指令 —— 不产生 CPU 动作、在程序执行前由汇编程序处理的说明性语句。伪指令与具体的处理器类型无关,但与汇编程序的版本有关

说明性语句 —— 由伪指令构成的语句,它通常指示汇编程序如何汇编源程序:

1
2
名字  伪指令助记符  参数,参数,...	;注释
string db ‘Hello,world’ ;使用db伪指令定义一个字符串,使用变量名string表达其在主存中的逻辑地址(包含段基地址和逻辑地址)、

名字:反映伪指令位置(逻辑地址)和属性的标识符,后跟空格或制表符分隔,没有冒号

伪指令助记符:定义字节数据和字符串的 DB 就是伪指令

伪指令的参数可以是常数、变量名、表达式等,可以有多个,参数之间用逗号分隔

# 标识符

标号和名字是符合汇编程序语法的用户自定义的标识符

标识符一般最多由 31 个字母、数字及规定的特殊符号(如 _、$、?、@)组成,不能以数字开头。默认情况下,汇编程序不区别标识符中的字母大小写。一个程序中,每个标识符的定义是唯一的,还不能是汇编语言采用的保留字。

保留字是汇编程序已经利用的标识符,主要有:

  • 硬指令助记符 —— 例如: MOV、ADD
  • 伪指令助记符 —— 例如: DB、EQU
  • 操作符 —— 例如: OFFSET、PTR
  • 寄存器名 —— 例如: AX、CS

# 汇编语言源程序框架

# 简化段定义的源程序框架

1
2
3
4
5
6
7
8
9
10
.model small	;存储模型伪指令.model,small表示小型模式
.stack ;堆栈段
.data ;数据段
... ;数据定义
.code ;代码段
.startup ;指明程序的起始执行点,同时为程序的数据段、代码段、堆栈段设置相应的段寄存器值
... ;主程序代码
.exit 0 ;返回DOS操作系统
... ;子程序代码
end ;汇编结束

MASM 5.0/5.1 不支持 .startup.exit ,可修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.model small				;存储模型伪指令.model,small表示小型模式
.stack ;堆栈段
.data ;数据段
... ;数据定义
.code ;代码段
;;.startup ;指明程序的起始执行点,同时为程序的数据段、代码段、堆栈段设置相应的段寄存器值
start: mov ax, @data
mov ds, ax
... ;主程序代码
;;.exit 0 ;返回DOS操作系统
mov ax, 4c00h
int 21h
... ;子程序代码
;;end ;汇编结束
end start

# 完整段定义的源程序框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
stack	segment stack
...
stack ends
data segment
...
data ends
code segment 'code'
assume cs:code, ds:data, ss:stack ;assume将cs,ds,ss,依次指向名为code,data,stack逻辑段
start: mov ax, data
mov ds, ax ;因为assume不为ds赋值,所以在start中为ds赋值data逻辑段
...
mov ax, 4c00h
int 21h
...
code ends
end start

# 汇编语言程序的开发过程

# DOS 系统功能调用

DOS 系统主要分配 21H 号中断,用于程序员调用 DOS 操作系统工功能

  • 中断调用指令: INT 21H

在 AH 寄存器中设置系统功能调用号

# 字符输入 (01H)

从键盘读入一个字符,并使用 AL 保存出口参数

1
2
MOV AH,01H
INT 21H ;读入的字符在AL寄存器

# 字符输出 (02H)

在显示器当前光标位置显示给定字符,且光标右移一个字符位置,使用 DL 作为入口参数

1
2
3
4
5
MOV AH,02H
MOV DL,字符 ;ASCII码,例如:41H
;MOV DL,41H ;输出‘A’,方法一
;MOV DL,'A' ;输出‘A’,方法二
INT 21H

# 字符串输入 (0AH)

需要事先在主存设置用于保存输入字符串的缓冲区,使用 ** DS:DX 作为出口参数 **

  • 缓冲区的第 1 字节填入最多接收的字符个数(包括回车符
  • 缓冲区的第 2 个字节存放实际输入的字符个数(不包括回车符
  • 从第 3 个字节开始存放输入的字符串,实际输入字符数多余定义数时,多出的字符被丢弃
1
2
3
4
5
6
7
8
9
buffer 	db	81					;可能输入的最大字符数
db 0 ;存放实际输入的字符数
db 81 dup (0) ;存放输入的字符串
...
mov ah, 0AH ;设置功能号
mov dx, seg buffer ;提供入口参数,seg获得buffer的段地址
mov ds, dx ;设置数据段
mov dx, offset buffer
int 21h

# 字符串输出 (09H)

需要事先将欲显示的字符串保存在主存中

  • 设置 ** 入口参数 DS:DX ** 等于该字符串在主存中的首地址
  • 字符串必须以 $(24H) 结束
1
2
3
4
string	db	'hello,everybody','$'
mov ah, 09h
mov dx, offset string
int 21h

# 按键判断 (0BH)

仅判断当前是否有按下的键,设置输出参数 AL 后退出。

  • AL = 0 没有按键
  • AL = FFH 已经按键

# 参数 & 变量 & 标号

# 数值型参数

# 常数

十进制常数 – 以 D/d 结尾,默认情况后缀可省略

十六进制常数 – 以 H/h 结尾

八进制 – 以 Q/q 结尾

二进制 – 以 B/b 结尾

字符串常数 – 用英文缩略号括起来的单个字符或多个字符

符号常数 – 利用一个标识符表达一个数值。符号定义伪指令有 EQU 和 **=**

1
2
3
符号名 equ 数值表达式
符号名 equ <字符串> ;该字符串可以是一条处理器指令
符号名 = 数值表达式

EQU 用于数值等价时不能重复定义符号名,但是 "=" 允许重复赋值

1
2
3
eg:
x = 7 ;x equ 7同样正确
x = x + 5 ;但是x equ x + 5错误

# 数值表达式

算术运算符: +,-,*,/,mod

逻辑运算符: and,or,xor(异或),not

移位运算符: shl,shr

关系运算符: eq,ne,gt(大于),lt(小于),ge(大于等于),le(小于等于)

  • 用 FFFFH (补码 - 1) 表示真
  • 用 0000H 表示假
1
2
3
4
eg:
mov bx, ((port lt 5) and 20) or ((port ge 5) and 30)
;当port < 5时,mov bx, 20
;当port >= 5时,mov bx, 30

高低分离符: high(高字节), low(低字节), highword(高字),lowword(低字)

1
2
mov ah, high 8765h	
;等价于mov ah, 87h

# 变量定义伪指令

格式

1
变量名 伪指令 初值表
  1. 变量名:用户自定义的标识符,表示初值表首元素的逻辑地址。变量名可以没有,在此情况下直接为初值表分配空间,无符号地址

  2. 初值表:用 "," 分隔的参数,主要由数值常数、表达式、"?","DUP" 组成

    • "?" 表示初值不确定
    • 重复初值可以用 DUP 进行定义
1
重复次数 dup(重复参数)
  1. 变量定义伪指令有 DB、DW、DD、DF、DQ、DT

# 定义字节单元伪指令 DB

用于分配一字节或多字节单元,初值表中的每个数据一定是字节量 (Byte),可以是02550\sim 255 的无符号数,或128+127-128\sim +127 带符号数

1
2
3
4
.data
x db 'A', -5
db 2dup(100), ?
y db 'ABC'
存储单元 偏移地址
43h© 0007H
42h(B) 0006H
41h(A) 0005H
——(?) 0004H
64h(100) 0003H
64h(100) 0002H
fbh(-5) 0001H
61h(a) 0000H

# 定义字单元伪指令 DW

用于分配一个或多个单元,初值表中的每个数据一定是字量 (Word),一个字单元可用于存放任何 16 位数据,如:一个段地址、一个偏移地址、两个字符、0655350\sim 65535 之间的无符号数、32768+32767-32768\sim +32767 之间的带符号数

1
2
3
4
5
6
7
8
9
10
11
wnum	equ 5678h	;定义wnum为常量
count dw 20h ;定义count为变量
;假设count在的数据段的偏移地址是10h

mov ax, [bx + si + wnum] ;mov ax, [bx + si + 5678h]
mov ax, count ;mov ax, [0010h]
mov ax, [si + count] ;mov ax, [si + 10h]
;mov ax, count[si]
lea bx, count ;lea bx, [0010h]
;mov bx, 0010h
mov bx, offset count ;mov bx, 0010h
  • 变量实质表达的是主存地址

  • 仅使用变量名或者加个常量是直接寻址

  • 再加一个寄存器是寄存器相对寻址

  • 加两个寄存器是基址变址相对寻址

# 定义双字单元伪指令 DD

用于分配一个或多个双字单元,初值表中的每个数据一定是一个 32 位的双字量

可以用来表达 16 位段地址和 16 位偏移地址的远指针

# 其他数据定义伪指令 DF&DQ&DT

定义 3 字伪指令 DF:为一个或多个 6 字节变量分配空间及初始化

定义 4 字伪指令 DQ:为一个或多个 8 字节变量分配空间及初始化

定义 10 字节伪指令 DT:为一个或多个 10 字节变量分配空间及初始化

** 练习:** 定义一个缓冲区,包含 33H、34H、35H、36H 四个字节字符,把这四个数据复制 20 遍,存入接着的缓冲区,最后显示出复制结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
source	db	33j, 34h, 35h, 36h
target db 80dp(?) ;紧接着分配80个空间

;分别指向源和目的的偏移地址首部
mov si, offset source
mov di, offset target

mov cx 80
again1: mov al. [si]
mov [di], al
inc si
int di
loop again1
mov di, 0
again2: mov dl, target[di]
mov ah, 2
int 21h
inc di
cmp di, 80
jb again2

# 定位伪指针 ORG&EVEN&ALIGN

1
2
3
4
5
6
7

$表示当前的偏移地址

```assembly
;在偏移地址100H单元开始定义
dw 1, 2, $ + 4, $ + 4
;在104H单元的值为108H,106H单元的值为10AH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

```ALIGN n```:使它后面的数据或指令从n的整数倍开始,如果不是就加上$1\sim n-1$中的一个数

n是2的乘方,且小于所在段的定位属性值

## 变量和标号的属性

### 地址操作符

```[]```表示将括起来的表达式作为存储器地址指针

```$```表示当前偏移地址

```:```表示采用指定的段地址寄存器

```offset```返回偏移地址

```seg```返回段地址

### 类型操作符

1. ```ptr```

```类型名 ptr 名字/标号```

2. ```this```

创建时采用当前地址,但为指定类型的操作数

```assembly
;b_var 和 w_var 地址相同
b_var equ this byte ;按字节访问b_var
w_var dw 10 dup(0)`;按字访问w_var
  1. type 名字/标号

    返回一个字量数值,表明名字或标号的类型;对字节、字、双字变量依次返回 1,2,4