汇编语言-程序篇

汇编语言程序实际架构。

基本知识篇

常用DEBUG指令

-r 显示 CPU 寄存器值

-t 单步执行,同时显示新寄存器值

-d xxxx 查看内存地址

-u 显示接下来的指令

-g xxxx 指令跳转

寄存器显示中,各标志位代码对应值如下:

汇编语言源程序框架

MASM 6.x 版本简化段定义的源程序框架

1
2
3
4
5
6
7
8
9
10
.model small            ; 定义程序存储模式 (small 表示小型模式)
.stack ; 定义堆栈段 (默认 1KB 空间)
.data ; 定义数据段
... ; 数据定义
.code ; 定义代码段
.startup ; 程序起始点,并设置DS和SS内容
... ; 主程序代码
.exit 0 ; 程序终止点,返回DOS
... ; 子程序代码
end ; 汇编结束

以输出 “Hello, world!” 为例展示完整代码:

1
2
3
4
5
6
7
8
9
10
11
.model small            ; 定义程序存储模式 (small 表示小型模式)
.stack ; 定义堆栈段 (默认 1KB 空间)
.data ; 定义数据段
string db 'Hello, world!', 0dh, 0ah, '$'
.code ; 定义代码段
.startup ; 程序起始点,并设置DS和SS内容
mov dx, offset string
mov ah, 9
int 21h
.exit 0 ; 程序终止点,返回DOS
end ; 汇编结束

任何版本都能使用的完整段定义的源程序框架

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 ; 确定cs/ds/ss指向的逻辑段
start: mov ax, data ; 定义数据段的段地址ds
mov ds, ax
... ; 主程序代码
mov ax, 4cc00h
int 21h ; 程序执行终止,返回DOS
... ; 子程序代码
code ends ; 代码段结束
end start ; 汇编结束,程序起始点为start

上述输出 “Hello, world!” 的代码改写为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
stack   segment stack        ; 定义堆栈段
db 1024 dup(?)
stack ends ; 堆栈段结束
data segment ; 定义数据段
string db 'Hello, world!', 0dh, 0ah, '$'
data ends ; 数据段结束
code segment 'code' ; 定义代码段
assume cs:code, ds:data, ss:stack ; 确定cs/ds/ss指向的逻辑段
start: mov ax, data ; 定义数据段的段地址ds
mov ds, ax
mov dx, offset string
mov ah, 9
int 21h
mov ax, 4c00h
int 21h ; 程序执行终止,返回DOS
code ends ; 代码段结束
end start ; 汇编结束,程序起始点为start

DOS 系统功能调用

  1. 在AH寄存器中设置系统功能调用号
  2. 在指定寄存器中设置入口参数
  3. 调用指令 int 21h 执行功能调用
  4. 根据出口参数分析功能调用情况
子功能号 功能 入口参数 出口参数
AH=01H 输入一个字符 AL = 输入字符的ASCII码
AH=02H 输出一个字符 DL = 输出字符的ASCII码
AH=09H 输出一个字符串 DS:DX = 字符串地址
AH=0AH 输入一个字符串 DS:DX = 缓冲区地址
AH=0BH 判断是否有键按下 AL = 0,无;AL = FFH, 有
AH=4CH 程序执行终止 AL = 返回代码

数值表达式

个人把常数理解为宏定义,在汇编过程中完成替换与计算,不占用实际存储空间。

指由运算符连接的各种常数所构成的表达式,会在汇编过程中得到结果。

运算符类型 运算符号
算术运算符 +,-,*,/,MOD
逻辑运算符 and,or,xor,not
移位运算符 shl,shr
关系运算符 eq,ne,gt,lt,ge,le
高低分离符 high,low,highword,lowword

变量定义格式

定义伪指令

1
变量名 伪指令 初值表

伪指令有 db dw dd(以及不常用的df dq dt

初值表可以是直接由 分隔的参数,也可以由 dup 定义重复参数:

1
重复次数 dup(重复次数)

定位伪指令

org 参数,使其后面的数据或指令从参数指定地址开始。

1
2
org 100h                ; 从100h处开始安排数据或程序
org $+10 ; 使偏移地址+10

even 参数,使其后面的数据或指令从偶地址开始。

若原地址为偶地址,则不变,否则将地址指针+1。

align n 参数,使其后面的数据或指令从 n 的整数倍开始。

处理类似 even

这三个指令都可以在代码段使用,用于指定随后指令的偏移地址。

地址操作符

1
2
3
4
[]                    ; 表示将括起来的表达式作为存储器地址指针
$ ; 表示当前偏移地址
offset 名字/标号 ; 返回名字或标号的偏移地址
seg 名字/标号 ; 返回名字或标号的段地址

类型操作符

1
2
3
类型名 ptr 名字/标号        ; 使名字或标号具有指定类型
this 类型名 ; 创建采用当前地址,但为指定类型的操作数
type 名字/标号 ; 返回一个字量数值,表明名字或标号的类型

逻辑控制篇

分支结构

1
2
3
4
5
6
if(condition){
// action1;
}
else{
// action2;
}

在汇编中可以用如下结构实现:

1
2
3
4
5
6
7
8
    cmp ..., ...
jcc satisfied
; action2
jmp continue;
satisfied:
; action1
continue:
...

例:判断方程 $ax^2+bx+c=0$ 是否有实根,若有实根,将tag置1,否则置0.

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
.model small
.stack
.data
_a db ?
_b db ?
_c db ?
tag db ?
.code

.startup
mov al, _b
imul al
mov bx, ax ; 计算b^2放入bx中
mov al, _a
imul _c
mov cx, 4
imul cx ; 计算4ac放入ax中
cmp bx, ax
jge yes
mov tag, 0
jmp done
yes:
mov tag, 1
done:

.exit 0
end

循环结构

计数控制循环

例:计算1~100数字和,将结果存入字变量sum中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.model small
.stack
.data
sum dw ?
.code
.startup

xor ax, ax
mov cx, 100
again:
add ax, cx ; 从100, 99, ..., 1 倒序相加
loop again
mov sum, ax

.exit 0
end

例:确定字变量wordX中1的最低位数(0~15),并将结果存于变量byteY中,若wordX没有为1的位,将-1存入byteY中。

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
.model small
.stack
.data
wordx dw 56
bytey db ?
.code
.startup

mov ax, wordx
mov cx, 16
mov dl, -1
again:
inc dl
test ax, 1
ror ax, 1
loope again ; 运算结果为0, 即ax最低位非1, 继续循环
je notfound
mov bytey, dl
jmp done
notfound:
mov bytey, -1
done:

.exit 0
end

条件控制循环

例:将一个字符串中的所有大写字母改为小写字母,该字符串以 0 结尾

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
.model small
.stack
.data
string db 'Hello, Everybody!', 0
.code
.startup

mov bx, offset string
again:
mov al, [bx]
or al, al
jz done
cmp al, 'A'
jb next
cmp al, 'Z'
ja next
or al, 20h
mov [bx], al
next:
inc bx
jmp again
done:

.exit 0
end

多重循环

例:冒泡排序将长度已知的数组升序排序。

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
.model small
.stack
.data
array db 56h, 23h, 37h, 78h, 0ffh, 0, 12h
count equ ($ - array) / type array
.code
.startup

mov cx, count
dec cx ; cx为外层循环次数
outlp:
mov dx, cx ; dx为内层循环次数
mov bx, offset array ; bx获取偏移地址
inlp:
mov al, [bx]
cmp al, [bx + 1]
jna next ; 不大于就跳过,实现每次将最大的数放在队尾
xchg al, [bx + 1]
mov [bx], al
next:
inc bx
dec dx
jnz inlp
loop outlp

.exit 0
end

子程序设计

1
2
3
过程名 proc[near\far]
过程体
过程名 endp

例:实现回车、换行功能的子程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
dpcref proc
push ax
push dx
mov dl, 0hd
mov ah, 2
int 21h
mov dl, 0ah
mov ah, 2
int 21h
pop dx
pop ax
ret
dpcref endp

参数传递的实现:

  1. 用寄存器传递参数:将参数存于约定的寄存器中。
  2. 用变量传递参数:直接用同一个变量名访问传递的参数。
  3. 用堆栈传递参数:主程序将入口参数压入堆栈,子程序弹出数据。

(堆栈传递参数)递归实现阶乘计算:

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
34
35
.model small
.stack
.data
n dw 3
result dw ?
.code
.startup
mov bx, n
push bx
call fact
pop result
.exit 0

fact proc
push ax
push bp
mov bp, sp
mov ax, [bp+6]
cmp ax, 0
jne fact1
inc ax
jmp fact2
fact1:
dec ax
push ax
call fact
pop ax
mul word ptr[bp+6]
fact2:
mov [bp+6], ax
pop bp
pop ax
ret
fact endp
end