子程序设计
当某个程序片段需要反复使用、或是具有通用性可以在多个程序中使用,我们就需要把这段代码设计为子程序。这样能用小缩短程序地长度,节约存储空间,减少程序设计地工作量。此外当某个程序片段地功能相对独立时,可以把它设计成子程序,这样便于模块化,也比那与程序地阅读、调试和修改。
过程调用和返回指令
- 过程调用(call)指令和过程返回(ret)指令属于程序控制指令。通常,过程调用指令用于由主程序转子程序,过程返回指令用于由子程序返回主程序。
- 过程调用指令,可以像无条件跳转指令一样进行跳转,且过程调用指令有段内调用和段间调用之分。与之相对应的。过程返回指令也有段内跳转和段间跳转。
- 段内调用和段内返回称为近调用和近返回,段间调用和段间返回称为远调用和远返回
- 在汇编语言中,过程也有远近之分。
过程调用指令
- 过程调用指令,首先把子程序的返回地址压入堆栈,以便执行完子程序后返回调用程序(主程序)继续往下执行。
- 按照转移目标是否是同一段来分,地址用指令分为段内调用和段间调用。
- 按照获得转移目标地址的方式来分,调用指令分为直接调用和间接调用。
- 过程调用指令不影响标志位。
段内直接调用
段内直接调用指令用于调用当前段内的子程序,格式如下:
call 过程名(标号)
例如:
call sub1
这条指令相当于
1 | push "call sub1的下一条指令的偏移地址" |
具体的操作分解如下:
1 | sp <= sp-2 ;提高栈顶 |
- call指令所在的偏移地址是,子程序的起始地址;
- call 下面一条指令的偏移地址是,子程序的结束地址;
- disp=结束地址-起始地址。
- 段内直接调用指令中,是以一个字表示disp,所以转移范围在 -32768~+32767 之间。
总结验证
起始地址:076B:0008
结束地址:076B:000B
disp=3
从上图中可以看到,call 指令执行前 sp=0000,指令执行之后 sp=sp-2=FFFE。内存中存储偏移地址 000B=起始IP + disp=0008+3。还需要注意一下,这里 IP寄存器 修改成了 0016
段内间接转移
段内间接转移格式如下:
call OPRD
例如:
1 | call BX |
OPRD 是16位通用寄存器或字存储器操作数,具体操作分解如下:
1 | sp <= sp-2 |
总结验证
段间直接调用
段间直接调用,用于调用其他代码段中的子程序。格式如下:call 过程名(标号)
例如:
1 | call far ptr SUBRO |
该指令把返回地址的段值压入堆栈,再把返回地址的偏移压入堆栈,达到保存返回地址的目的。
具体操作分解如下:
1 | sp <= sp-2 |
总结验证
可以看到,远跳转的 call 指令,后面的地址是 “CS:IP”。堆栈中存放的也是 “CS:IP”
段间间接调用
段间间接调用指令也用于调用其他代码段中的子程序。格式如下:call OPRD
OPRD 是双字存储器操作数
具体操作分解如下:
1 | sp <= sp-2 |
例如:
1 | call DWORD PTR [bx] |
总结验证
可以看到,变量类型只要是 dd 就行,当编译程序时会自动加上 far 属性。
过程返回指令
过程返回指令把子程序的返回地址从堆栈弹出到 IP
或 CS:IP
,从而返回到主程序继续我往下执行。过程返回指令不影响标志位。
段内返回指令
指令格式如下:
1 | ret |
该指令完成的具体操作如下所示:
1 | IP <= [SP] |
总结验证
段间返回指令
指令格式如下:
1 | ret |
该指令完成的具体操作如下所示:
1 | IP <= [SP] |
总结验证
编译器自动把 RET 编译成另一个段间返回指令 RETF 。
段间返回指令 RETF
可以看到我们使用段内 call 指令调用子程序,但使用的是 RETF 返回,导致程序返回出错。
无论 RETF 出现在远过程还是近过程中,编译器总是会把它编译成段间返回指令。
返回指令+立即数
指令格式如下:ret 表达式
汇编程序会把表达式的结果取整。
该指令会先弹出 “一个字” 或是 “双字” 作为返回地址,再根据 data 修改堆栈指针。
可以看到上面
ret 4
是吧 SP 寄存器改为了 4
过程定义语句
过程定义语句(子程序定义语句),可把子程序起名,并且定义近类型或远类型。过程定义语句的格式如下:
1 | 过程名 PROC [NEAR | FAR] |
过程定义语句中,过程名必须要一致。现阶段程序的代码比较少,基本都使用 NEAR 属性。后面程序的代码多的时候,使用 FAR 属性会比较方便。
范例
把一个十六进制数转换为对应 ASCII 码
程序逻辑结构图
代码
1 | ;程序名:HTOASC.asm |
子程序举例
例1
把用ASCII码表示的两位十进制数,转换为对应的十进制数的子程序。设X为十位数,Y为个位数
程序流程图
代码
1 | ;程序名:T4-1.asm |
例2
程序流程图
2-1
写一个把一个字大小的16进制数,转换成4个ASCII码的子程序2-2
利用子程序 HTASCS 按十六进制数形式显示地址为 F000:0000H 的字单元内容
代码
T4-2-1.asm
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73;程序名:T4-2-1.asm
;作者:啥都不会
;创建日期:2022/3/14
;修改日期: 修改者:
;子程序名:htascs
;程序的描述:写一个把一个字大小的16进制数,转换成4个ASCII码
;算法:把16进制数向左循环移位4次,使高四位变为低四位,
;析出低四位调用子程序 htoasc 转换 1 位十六进制 ASCII 码,循环四次
;优化:直接循环输出结果
;=============================================================================================
assume cs:code,ds:data
data segment
num dw 1234h
data ends
code segment
start:
mov ax,data
mov ds,ax
;
mov bx,num
call htascs
;
mov ax,4c00h
int 21h
;-----------------------------------------------------
htascs PROC
;
; 功能:把一个字大小的16进制数,转换成4个ASCII码
; 传参方式:寄存器传参
; 入口参数:BX数值
; 出口参数:无
; 说 明:调用子函数 htascs
;-----------------------------------------------------
mov cx,4
htascs1:
ROL BX,1
ROL BX,1
ROL BX,1
ROL BX,1
mov al,bl
call HTOASC
mov dl,al
mov ah,2
int 21h
loop htascs1
ret
htascs endp
;-----------------------------------------------------
HTOASC PROC NEAR
;
; 功能:把一个十六进制数转换为对应的 ASCII 码
; 传参方式:寄存器传参
; 入口参数:al
; 出口参数:al
;-----------------------------------------------------
pushf
and al,0fh
cmp al,0ah
jb num_add
add al,37h
jmp return
num_add:
add al,30h
return:
popf
ret
HTOASC endp
code ends
end startT4-2-2.asm
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60;程序名:T4-2-2.asm
;利用子程序 HTASCS 按十六进制数形式显示地址为 F000:0000H 的字单元内容
assume cs:code
code segment
start:
mov ax,0f000h
mov ds,ax
;
mov bx,[si]
call HTASCS
mov ax,4c00h
int 21h
;-----------------------------------------------------
HTASCS PROC
;
; 功能:把一个字大小的16进制数,转换成4个ASCII码
; 传参方式:寄存器传参
; 入口参数:dx
; 出口参数:DS:BX存储所得ASCII码串的缓冲区首地址
;-----------------------------------------------------
mov cx,4
HTASCS1:
ROL BX,1
ROL BX,1
ROL BX,1
ROL BX,1
mov al,dl
call HTOASC
mov dl,al
mov ah,2
int 21h
loop HTASCS1
ret
HTASCS endp
;-----------------------------------------------------
HTOASC PROC NEAR
;
; 功能:把一个十六进制数转换为对应的 ASCII 码
; 传参方式:寄存器传参
; 入口参数:al
; 出口参数:al
;-----------------------------------------------------
pushf
and al,0fh
cmp al,0ah
jb num_add
add al,37h
jmp pop_stack
num_add:
add al,30h
pop_stack:
popf
ret
HTOASC endp
code ends
end start
例3
程序流程图
T4-3-1
把T3-11.asm改写成子程序,写一个把16位二进制数转换为5位十进制数ASCII码的子程序,为了简单,设二进制数为无符号数。例3的逻辑结构和 T3-11-2 是一样的,这里就不再画了
T4-3-2
把8位二进制数转换为2位十六进制数的 ASCII 码
代码
T4-3-1
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
36
37
38
39
40
41
42
43
44
45
46
47;程序名:T4-3-1.asm
;功能:写一个把16位二进制数转换为5位十进制数ASCII码的子程序,为了简单二进制数为无符号数。把T3-11.asm改写成子程序。
assume cs:code,ds:data
data segment
number dw 0AECFh
result db 0,0,0,0,0,'$'
data ends
code segment
start:
mov ax,data
mov ds,ax
;
mov ax,number
lea si,result
call BTOASC
lea dx,[si]
mov ah,9
int 21h
;
mov ax,4c00h
int 21h
;-----------------------------------------------------
BTOASC PROC
;
; 功能:把16位二进制数转换为5位十进制数
; 传参方式:寄存器传参
; 入口参数:AX=要转换的值、SI=存储结果的缓冲区首地址
; 出口参数:DS:SI存储所得ASCII码串的缓冲区首地址
;-----------------------------------------------------
mov di,5
mov cx,10
L1:
xor dx,dx
div cx
add dl,30h
mov result[di-1],dl ;用di判断循环,但是存储结果di需要减1
dec di
jnz L1
;函数返回
ret
BTOASC endp
code ends
end startT4-3-2
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72;程序名:T4-3-2.asm
;功能:把8位二进制数转换为2位十六进制数的 ASCII 码
assume cs:code,ds:data
data segment
char db 0abh
data ends
code segment
start:
mov ax,data
mov ds,ax
;
mov al,char
call WHTOASC
;
mov cx,2
mov dx,ax
L1:
xchg dh,dl
mov ah,2
int 21h
loop L1
;
mov ax,4c00h
int 21h
;-----------------------------------------------------
WHTOASC PROC
;
; 功能:把8位二进制数转换为2位 ASCII 码
; 传参方式:寄存器传参
; 入口参数:AL=要转换的值
; 出口参数:AH = 十六进制数高位的 ASCII 码
; AL = 十六进制数低位的 ASCII 码
;其他说明:(1)近过程
; (2)除 AX 寄存器外,不影响其他寄存器
; (3)调用HTOASC 实现十六进制数到ASCII码的转换
;-----------------------------------------------------
mov ah,al
shr al,1
shr al,1
shr al,1
shr al,1
call HTOASC
xchg ah,al
call HTOASC
ret
WHTOASC endp
;-----------------------------------------------------
HTOASC PROC NEAR
;
; 功能:把一位十六进制数转换为对应的 ASCII 码
; 传参方式:寄存器传参
; 入口参数:al
; 出口参数:al
;-----------------------------------------------------
pushf
and al,0fh
cmp al,0ah
jb num_add
add al,37h
jmp pop_stack
num_add:
add al,30h
pop_stack:
popf
ret
HTOASC endp
code ends
end start
寄存器的保护与恢复
子程序为了完成其功能,通常需要使用一些寄存器或存储单元存放内容。也就是说,子程序运行时通常会破坏寄存器的数据。所以在调用子程序时,我们还需要将,可能破坏的寄存器数据进行保护与恢复。
寄存器保护与恢复的方法:
在子程序一开始就把子程序中要改变的寄存器内容压入堆栈,在返回之前再恢复这些寄存器的内容。如下图所示:
同时还要注意:子程序运行时,会影响到标志寄存器。因此有时我们还需要保护标志寄存器。指令如下:
pushf(保护标志寄存器)
popf(恢复标志寄存器)
主程序与子程序之间的参数传递
主程序在调用子程序时,通常需要向子程序传递参数(类似函数的参数);同样,子程序运行后也经常要把一些结果参数传给主程序(类似函数的返回值)。主程序与子程序之间的这种信息传递称为参数传递。
- 由主程序传递给子程序的参数称为子程序的入口参数
- 由子程序传给主程序的参数称为子程序的出口参数
- 子程序可以有入口参数,有出口参数;有入口参数,无出口参数;无入口参数,有出口参数
参数传递的方法分别有:寄存器传递法、内存单元传递法、堆栈传递法和call 后续区传递法等。
说到底,只要子程序能定位到参数在哪,就可以用这种方法传递参数。以上这些方法的区别在于,使用寄存器传递法的子程序,比使用其他参数方法的速度要快。
寄存器参数传递法
利用寄存器传递参数就是把参数放在约定的寄存器中。
优点:实现简单、调用方便。
缺点:因为寄存器个数有限,且寄存器还要存放其他数据,所以只适用于传递参数较少的情况
例1
程序流程图
T4-4
写一个 把大写字母改为小写字母的子程序。T4-4-1
写一个把大写字母的字符串更改为小写字母的子程序。
代码
T4-4.asm
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
36
37
38
39
40
41
42
43
44
45;程序名:T4-5.asm
;功能:写一个把一个大写字母更改为小写字母的子程序。
;子程序名:UPTOLW
assume cs:code,ds:data
data segment
char db 'H'
data ends
code segment
start:
mov ax,data
mov ds,ax
;
mov al,char
call UPTOLW
;
mov dl,al
mov ah,2
int 21h
;
mov ax,4c00h
int 21h
;-----------------------------------------------------
UPTOLW PROC
;
; 功能:把一个大写字母更改为小写字母,其他字符不变
; 传参方式:寄存器传参
; 入口参数:AL=字符ASCII码
; 出口参数:AL=字符ASCII码
;-----------------------------------------------------
pushf
cmp al,'A'
jb UPTOLW1
cmp al,'Z'
JA UPTOLW1
add al,'a'-'A'
;
UPTOLW1:
popf
ret
UPTOLW endp
code ends
end startT4-4-1.asm
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63;程序名:T4-4-1.asm
;功能:写一个把大写字母的字符串更改为小写字母的子程序。
;子程序名:UPTOLWS
assume cs:code,ds:data
data segment
char db 'HELLO',0,0dh,0ah,'$'
data ends
code segment
start:
mov ax,data
mov ds,ax
;
lea si,char
call UPTOLWS
;
mov dx,si
mov ah,9
int 21h
;
mov ax,4c00h
int 21h
;-----------------------------------------------------
UPTOLWS PROC
;
; 功能:把一串大写字母更改为小写字母,其他字符不变
; 传参方式:寄存器传参
; 入口参数:SI=要转换字符的首地址
; 出口参数:DS:SI存储所得ASCII码串的缓冲区首地址
;-----------------------------------------------------
pushf
push ax
push cx
push si
;
xor cx,cx
L1:
inc si
inc cx
mov al,byte ptr [si-1]
cmp al,0
jz UPTOLW_POP
cmp al,'A'
jb UPTOLW_POP
cmp al,'Z'
ja UPTOLW_POP
;
add al,20h
mov byte ptr [si-1],al
jmp L1
UPTOLW_POP:
pop si
pop cx
pop ax
popf
;
ret
UPTOLWS endp
code ends
end start
例2
写一个判别字符是否为数字的子程序。并利用该子程序把一个字符出串中的所有数字字符删除。
程序流程图
代码
T4-5
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60;程序名:T4-5.asm
;功能:写一个判别字符是否为数字的子程序。并利用该子程序把一个字符出串中的所有数字字符删除。
;子程序名:ISDECM
;处理方法:循环处理字符并复制到缓冲区中。缺点:多了复制字符的步骤。优点:准确性高
assume cs:code,ds:data
data segment
string db 'H1E2L3L4L5O',0
count dw $ - 1 ;当前的偏移地址($)-1:表示字符串 string 的长度。
result db 10 dup(0),0dh,0ah,'$'
data ends
code segment
start:
mov ax,data
mov ds,ax
;
mov cx,count
L1:
mov al,string[si]
call ISDECM
inc si ;inc 指令不影响 CF 位,所以不影响程序运行结果
jc L2
loop L1
jmp stop
L2:
mov result[di],al ;di 寄存器的默认值是0,且默认使用 ds 段寄存器
inc di
cmp cx,0
jz stop
;dec cx
;jnz L1
loop L1
stop:
lea dx,result
mov ah,9
int 21h ;输出结果
;
mov ax,4c00h
int 21h
;-----------------------------------------------------
ISDECM PROC
;
; 功能:判别一个字符是否为数字
; 传参方式:寄存器传参
; 入口参数:AL=字符
; 出口参数:CF为0表示是数字符,否则字符是非数字符
;-----------------------------------------------------
cmp al,'0'
jb ISDECM1
cmp al,'9'
ja ISDECM1
CLC
ret
ISDECM1:
STC
ret
ISDECM endp
code ends
end start优化1
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58;程序名:T4-5-1.asm
;功能:写一个判别字符是否为数字的子程序。并利用该子程序把一个字符出串中的所有数字字符删除。
;子程序名:ISDECM
;处理方法:循环处理字符,并替换原有字符。缺点:删除数字后,原有字符串与现有字符串宽度不一样,容易出错。优点:代码简洁了一点点。
assume cs:code,ds:data
data segment
string db 'AB=C950=asd',0,0dh,0ah,'$'
data ends
code segment
start:
mov ax,data
mov ds,ax
;
mov si,offset string
mov di,si
next:
mov al,[si]
inc si
or al,al
jz ok
call ISDECM
jnc NEXT
mov [di],al
inc di
jmp next
ok:
mov [di],al
;
lea dx,string
mov ah,9
int 21h
;
mov ax,4c00h
int 21h
;-----------------------------------------------------
ISDECM PROC
;
; 功能:判别一个字符是否为数字
; 传参方式:寄存器传参
; 入口参数:AL=字符
; 出口参数:CF为0表示是数字符,否则字符是非数字符
;-----------------------------------------------------
cmp al,'0'
jb ISDECM1
cmp al,'9'
ja ISDECM1
CLC
ret
ISDECM1:
STC
ret
ISDECM endp
code ends
end start优化2
1 | ;程序名:T4-5-2.asm |
利用存储单元传递参数
在参数较多的情况下,可利用内存变量来传递参数。
优点:子程序要处理的数据或送出的结果都是独立的存储单元,编写子程序时不容易出错。
缺点:占用了一定的存储单元,通用性较差。
解决方法:把参数组织成一张参数表,存放在某个存储区,然后再把存储区首地址传送给子程序。
在当时内存空间是非常少的,因此当时的开发人员都非常注重内存空间的使用,尽量少使用内存。
例1
写一个实现32位数相加的子程序
程序流程图
T4-6
T4-6-1
代码
T4-6
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61;程序名:T4-6.asm
;功能:实现32位数相加
;子程序名:MADD
;算法:使用顺序结构
assume cs:code,ds:data
data segment
DATA1 dd 0ABCDABCDh
DATA2 dd 0CDEFCDEFh
DATA3 dw 0,0,0,0
data ends
code segment
start:
mov ax,data
mov ds,ax
;
call MADD
;
mov ax,4c00h
int 21h
;-----------------------------------------------------
MADD PROC
; 功能:32位数相加
; 传参方式:存储单元传参
; 入口参数:DATA1和DATA2分别存放要相加的32位数
; 出口参数:DATA3缓冲区存放结果
;说明: (1)32位数据的存放次序采用 “高高低低”的原则
; (2)可能产生的进位存放在 DATA3 开始的第5个字节中。
;-----------------------------------------------------
pushf
push ax
push si
push dx
push bx
;
xor si,si
xor dx,dx
;算出低16位,进位值存在bx
mov ax,word ptr DATA1[si]
add ax,word ptr DATA2[si]
mov DATA3[si],ax
;算出高16位,进位值存在dx
mov ax,word ptr DATA1[si+2]
adc ax,0 ;将上一次计算的进位值加进来
add ax,word ptr DATA2[si+2]
adc dx,0
mov word ptr DATA3[si+2],ax
mov word ptr DATA3[si+4],dx
;
pop bx
pop dx
pop si
pop ax
popf
ret
MADD endp
code ends
end startT4-6-1
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62;程序名:T4-6-1.asm
;功能:写一个实现32位数相加的子程序
;子程序名:MADD
;算法:使用循环结构
assume cs:code,ds:data
data segment
DATA1 dd 0ABCDABCDh
DATA2 dd 0CDEFCDEFh
DATA3 dw 0,0,0,0
data ends
code segment
start:
mov ax,data
mov ds,ax
;
call MADD
;
mov ax,4c00h
int 21h
;-----------------------------------------------------
MADD PROC
; 功能:32位数相加
; 传参方式:存储单元传参
; 入口参数:DATA1和DATA2分别存放要相加的32位数
; 出口参数:DATA3缓冲区存放结果
;说明: (1)32位数据的存放次序采用 “高高低低”的原则
; (2)可能产生的进位存放在 DATA3 开始的第5个字节中。
;-----------------------------------------------------
pushf
push ax
push si
push dx
push bx
;初始化
mov cx,2
xor si,si
MADD1:
;算出低16位,进位值存在bx
mov ax,word ptr DATA1[si]
adc ax,word ptr DATA2[si]
mov DATA3[si],ax
inc si
inc si
loop MADD1
;处理进位
mov al,0
adc al,0
mov byte ptr DATA3+4,AL
;
pop bx
pop dx
pop si
pop ax
popf
ret
MADD endp
code ends
end start
顺序结构和循环结构的代码行数都差不多,那哪个代码写的更加好一些。
我认为是顺序结构写的代码更加好一下,因为循环结构执行的代码比顺序结构执行的代码要多一些,其次顺序结构更容易理解。
例2
设计一个以ASCII码表示的十进制数字符串转换为二进制数的子程序。
程序流程图
代码
1 | ;程序名:T4-7.asm |
利用堆栈传递参数
如果使用堆栈传递参数,那么主程序在调用子程序之前,把需要传递的参数依次压入堆栈,子程序从堆栈中取入口参数;如果使用堆栈传递出口参数,那么子程序在返回前,把需要返回的参数存入堆栈,主程序在堆栈中取出口参数。
优点:不占用寄存器,也无需使用额外的存储单元。
缺点:需要考虑保护寄存器
通常利用堆栈传递入口参数,利用寄存器传递出口参数
例1
写一个测量字符串长度的子程序,设字符串以 0 为结束标志。
程序流程图
代码
1 | ;程序名:T4-8.asm |
利用 call 后续区传递参数
CALL 后续区是指位于CALL指令后的存储区域。主程序在调用子程序之前,把入口参数存入CALL指令后的存储单元中,子程序根据保存在堆栈中的返回地址找到入口参数。
例1
写一个把字符串中所有大写字母转换为小写字母子程序
程序结构图
代码
1 | ;程序名:T4-9.asm |
一般不会用这种方法调用参数,容易出错。
DOS 功能调用及应用
MS-DOS 内包含了涉及设备驱动和文件管理等方面的子程序,DOS的各种命令就是通过适当地调用这些子程序实现的。DOS功能调用主要包括三方面的子恒徐:设备驱动(基本I/O)、文件管理和其他(包括内存管理、置取时间、置取中断向量、终止程序等)。
调用方法
- 根据情况准备 DOS 功能调用所需的参数。有部分功能是不需要参数的,但大部分调用需要入口参数,在调用前应按照要求准备好入口参数
- 把功能调用号送入 AH 寄存器
- 发送软中断指令 “int 21h”
例1
调用 “int21” 2号功能,使喇叭发出 “嘟” 的声音。
1 | ;程序名:T4-10.asm |
大部分功能调用都有出口参数,在调用后,可根据有关功能调用的说明取得出口参数,如2号功能。
还有个别功能很特殊,调用后就不再返回。例如 4CH 号功能就是结束程序的运行返回DOS。4CH 号功能调用有一个存放在 AL 寄存器中的入口参数,该入口参数是程序的结束码,其值大小不影响程序的结束。
例2
修改 4CH 号功能入口参数 AL 的值,查看是否会影响程序。
1 | ;程序名:T4-11.asm |
基本的 I/O 功能调用
AH | 功能 | 调用参数 | 返回参数 |
---|---|---|---|
00 | 程序终止(同INT 20H) | CS=程序段前缀 | |
01 | 键盘输入并回显 | AL=输入字符 | |
02 | 显示输出 | DL=输出字符 | |
03 | 异步通迅输入 | AL=输入数据 | |
04 | 异步通迅输出 | DL=输出数据 | |
05 | 打印机输出 | DL=输出字符 | |
06 | 直接控制台I/O | DL=FF(输入)DL=字符(输出) | AL=输入字符 |
07 | 键盘输入(无回显) | AL=输入字符 | |
08 | 键盘输入(无回显)检测Ctrl-Break | AL=输入字符 | |
09 | 显示字符串 | DS:DX=串地址‘$’结束字符串 | |
0A | 键盘输入到缓冲区 | DS:DX=缓冲区首地址(DS:DX)=缓冲区最大字符数 | (DS:DX+1)=实际输入的字符数 |
“int 21h”中断还有很多功能,我们在需要用到某个功能的时候再去查,不需要去记
应用举例
例1
编写程序,从键盘接收输入字符,如果数字是 N,则响铃N次;如果不是数字,则不响铃;如果是 CTRL+C 结束程序。
程序流程图
代码
1 | ;程序名:Ring.asm |
例2
写一个程序,用二进制数形式显示按键的ASCII码。
程序流程图
代码
1 | ;程序名:T4-12.asm |
例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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320;程序名:T4-13.asm
;功能:写一个程序,它先接收一个字符串,然后显示其中数字符的个数、英文字母的个数和字符串的长度
;算法:HEXTODEC 使用除法运算
assume cs:code,ds:data,ss:stack
data segment
buffer db 128 dup(0) ;未初始化数据要放在最前面
string1 db 'The number of letters is: %u',0,0
string2 db 'The number of numbers is: %u',0,0
string3 db 'String length is: %u',0,0
data ends
stack segment
print_buffer db 128 dup(0)
stack ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
;
lea bx,buffer
call INPUT
;
push ds
push bx
call CHARSTAT
add sp,4
;
lea dx,string1
push dx
xor dx,dx
mov dl,ah
push dx
call far ptr print
;
call NEWLINE
;
lea dx,string2
push dx
xor dx,dx
mov dl,al
push dx
call far ptr print
;
call NEWLINE
;
lea dx,buffer
push ds
push dx
call STRLEN
add sp,4
;
lea dx,string3
push dx
push ax
call far ptr print
;
mov ax,4c00h
int 21h
;--------------------------------------------------------
INPUT PROC
; 功能:接收输入,并存储到 128字节大小的 bufer 缓冲区中
; 入口参数:bx=buffer缓冲区首地址
; 出口参数:bx=buffer缓冲区首地址
; 说 明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
pushf
push ax
push bx
INPUT1:
mov ah,1
int 21h
cmp al,0dh
jz INPUT2
mov [bx],al
inc bx
jmp INPUT1
INPUT2:
pop bx
pop ax
popf
;
ret
INPUT endp
;-----------------------------------------------------
CHARSTAT PROC
; 功能:统计字符串中的字母、数字、其他字符的个数
; 传参方式:堆栈传参
; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
; 出口参数:AH=字母个数、AL=数字的个数
;------------------------------------------------------
push bp
mov bp,sp
pushf
push es
push di
push bx
;
mov di,[bp+4]
mov es,[bp+6]
xor ax,ax
CHARSTAT1:
mov bl,es:[di]
;判断是否读取结束
cmp bl,0
jz CHARSTAT_POP
;判断小写字母
cmp bl,'a'
jae CHARSTAT3
;判断大写字母
cmp bl,'A'
jae CHARSTAT2
;判断数字
cmp bl,'0'
jae CHARSTAT0
inc di
jmp CHARSTAT1
CHARSTAT0:
cmp bl,'9'
jbe number
inc di
jmp CHARSTAT1
CHARSTAT2:
;判断字母
cmp bl,'Z'
jb letter
inc di
jmp CHARSTAT1
CHARSTAT3:
cmp bl,'z'
jb letter
inc di
jmp CHARSTAT1
letter:
inc ah
inc di
jmp CHARSTAT1
number:
inc al
inc di
jmp CHARSTAT1
;
CHARSTAT_POP:
pop bx
pop di
pop es
popf
mov sp,bp
pop bp
;
ret
CHARSTAT endp
;-----------------------------------------------------
STRLEN PROC
; 功能:测量字符串的长度
; 传参方式:堆栈传参
; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
; 出口参数:AX=字符串长度
;------------------------------------------------------
push bp
mov bp,sp
push es
push di
;
xor ax,ax
mov di,[bp+4]
mov es,[bp+6]
STRLEN1:
mov bl,es:[di]
cmp bl,0
jz STRLEN2
inc di
inc ax
jmp STRLEN1
STRLEN2:
pop di
pop es
mov sp,bp
pop bp
ret
STRLEN endp
;--------------------------------------------------------
NEWLINE PROC
; 功能:形成回车和换行(光标移到写一行首)
; 入口参数:无
; 出口参数:无
; 说 明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
push ax
push dx
mov dl,0dh
mov ah,2
int 21h
mov dl,0ah
mov ah,2
int 21h
pop dx
pop ax
ret
NEWLINE endp
print proc far
push bp
mov bp,sp
push ds
push es
push ss
push si
push di
push ax
push bx
push cx
push dx
;判断在字符串中是否有 %d、%s等(同时将字符串复制),结束标志0
;初始化寄存器
xor si,si
xor di,di
mov si,[bp+8]
lea di,print_buffer
print1:
mov ax,[si]
cmp ax,0
jz print_b
;
cmp ax,'u%'
jz decs
mov ss:[di],al
inc si
inc di
jmp print1
;有:%d(将十六进制转换为十进制,复制)、%s(指定偏移,复制)
decs:
call HEXTODEC
;无:输出结果
print_b:
mov al,'$'
mov ss:[di],al
mov ax,ss
mov ds,ax
lea dx,print_buffer
mov ah,9
int 21h
;
pop dx
pop cx
pop bx
pop ax
pop di
pop si
pop ss
pop es
pop ds
mov sp,bp
pop bp
ret
print endp
CalOffset proc
;如果 ax <9;di+2
cmp ax,9
jb CalOffset1
;如果 ax <99;di+3
cmp ax,99
jb CalOffset2
;如果 ax <999;di+4
cmp ax,999
jb CalOffset3
;如果 ax <9999;di+5
cmp ax,9999
jb CalOffset4
;如果 ax > 9999;di+5
jmp CalOffset5
CalOffset1:
;di 为了符合循环,di 多加了1
add di,1
jmp CalOffset6
CalOffset2:
add di,2
jmp CalOffset6
CalOffset3:
add di,3
jmp CalOffset6
CalOffset4:
add di,4
CalOffset5:
add di,5
CalOffset6:
;将当前偏移备份到bx中
mov bx,di
ret
CalOffset endp
HEXTODEC PROC
mov cx,10
;mov si,[bp+4]
mov ax,[bp+6]
call CalOffset
L1:
xor dx,dx
div cx
add dl,30h
dec di
mov ss:print_buffer[di],dl ;用di判断循环,但是存储结果di需要减1
cmp ax,0
jnz L1
mov di,bx ;恢复偏移地址
;
ret
HEXTODEC endp
code ends
end start十六进制转十进制(加法运算)
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343;程序名:T4-13-1.asm
;功能:写一个程序,它先接收一个字符串,然后显示其中数字符的个数、英文字母的个数和字符串的长度
;算法:HEXTODEC 使用加法运算
assume cs:code,ds:data,ss:stack
data segment
buffer db 128 dup(0) ;未初始化数据要放在最前面
string1 db 'The number of letters is: %u',0,0
string2 db 'The number of numbers is: %u',0,0
string3 db 'String length is: %u',0,0
data ends
stack segment
print_buffer db 128 dup(0)
stack ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
;
lea bx,buffer
call INPUT
;
push ds
push bx
call CHARSTAT
add sp,4
;
lea dx,string1
push dx
xor dx,dx
mov dl,ah
push dx
call far ptr print
;
call NEWLINE
;
lea dx,string2
push dx
xor dx,dx
mov dl,al
push dx
call far ptr print
;
call NEWLINE
;
lea dx,buffer
push ds
push dx
call STRLEN
add sp,8
;
lea dx,string3
push dx
push ax
call far ptr print
;
mov ax,4c00h
int 21h
;--------------------------------------------------------
INPUT PROC
; 功能:接收输入,并存储到 128字节大小的 bufer 缓冲区中
; 入口参数:bx=buffer缓冲区首地址
; 出口参数:bx=buffer缓冲区首地址
; 说 明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
pushf
push ax
push bx
INPUT1:
mov ah,1
int 21h
cmp al,0dh
jz INPUT2
mov [bx],al
inc bx
jmp INPUT1
INPUT2:
pop bx
pop ax
popf
;
ret
INPUT endp
;-----------------------------------------------------
CHARSTAT PROC
; 功能:统计字符串中的字母、数字、其他字符的个数
; 传参方式:堆栈传参
; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
; 出口参数:AH=字母个数、AL=数字的个数
;------------------------------------------------------
push bp
mov bp,sp
pushf
push es
push di
push bx
;
mov di,[bp+4]
mov es,[bp+6]
xor ax,ax
CHARSTAT1:
mov bl,es:[di]
;判断是否读取结束
cmp bl,0
jz CHARSTAT_POP
;判断小写字母
cmp bl,'a'
jae CHARSTAT3
;判断大写字母
cmp bl,'A'
jae CHARSTAT2
;判断数字
cmp bl,'0'
jae CHARSTAT0
inc di
jmp CHARSTAT1
CHARSTAT0:
cmp bl,'9'
jbe number
inc di
jmp CHARSTAT1
CHARSTAT2:
;判断字母
cmp bl,'Z'
jb letter
inc di
jmp CHARSTAT1
CHARSTAT3:
cmp bl,'z'
jb letter
inc di
jmp CHARSTAT1
letter:
inc ah
inc di
jmp CHARSTAT1
number:
inc al
inc di
jmp CHARSTAT1
;
CHARSTAT_POP:
pop bx
pop di
pop es
popf
mov sp,bp
pop bp
;
ret
CHARSTAT endp
;-----------------------------------------------------
STRLEN PROC
; 功能:测量字符串的长度
; 传参方式:堆栈传参
; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
; 出口参数:AX=字符串长度
;------------------------------------------------------
push bp
mov bp,sp
push es
push di
;
xor ax,ax
mov di,[bp+4]
mov es,[bp+6]
STRLEN1:
mov bl,es:[di]
cmp bl,0
jz STRLEN2
inc di
inc ax
jmp STRLEN1
STRLEN2:
pop di
pop es
mov sp,bp
pop bp
ret
STRLEN endp
;--------------------------------------------------------
NEWLINE PROC
; 功能:形成回车和换行(光标移到写一行首)
; 入口参数:无
; 出口参数:无
; 说 明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
push ax
push dx
mov dl,0dh
mov ah,2
int 21h
mov dl,0ah
mov ah,2
int 21h
pop dx
pop ax
ret
NEWLINE endp
print proc far
push bp
mov bp,sp
push ds
push es
push ss
push si
push di
push ax
push bx
push cx
push dx
;判断在字符串中是否有 %d、%s等(同时将字符串复制),结束标志0
;初始化寄存器
xor si,si
xor di,di
mov si,[bp+8]
lea di,print_buffer
print1:
mov ax,[si]
cmp ax,0
jz print_b
;
cmp ax,'u%'
jz unsigned_int
;
;cmp ax,'%s'
;jz %s
;
;cmp ax,'%x'
;jz %x
mov ss:[di],al
inc si
;inc si
;inc di
inc di
jmp print1
;有:%d(将十六进制转换为十进制,复制)、%s(指定偏移,复制)
unsigned_int:
call HEXTODEC
;无:输出结果
print_b:
mov al,'$'
mov ss:[di],al
mov ax,ss
mov ds,ax
lea dx,print_buffer
mov ah,9
int 21h
;
pop dx
pop cx
pop bx
pop ax
pop di
pop si
pop ss
pop es
pop ds
mov sp,bp
pop bp
ret
print endp
HEXTODEC PROC
xor dx,dx
mov ax,[bp+6]
mov bx,ax
;结果小于9,就直接+30h,存储到ss:di中
cmp ax,9
jbe HEXTODEC4
HEXTODEC1:
add ax,6
adc dl,dl
sub bx,0ah
cmp bx,9
jbe HEXTODEC2
jmp HEXTODEC1
;运算结束后,判断是否有进位
HEXTODEC2:
cmp dl,0
jz HEXTODEC3
;有进位,先存储dl,再存储ax
add dl,30h
mov ss:[di],dl
inc di
HEXTODEC3:
;无进位,处理ax,并存储到 ss:di 中
call HTASCS
jmp HEXTODEC5
HEXTODEC4:
add al,30h
mov ss:[di],al
inc di
HEXTODEC5:
;
ret
HEXTODEC endp
;-----------------------------------------------------
HTASCS PROC
; 功能:把一个字大小的16进制数(每个位必须<=9),转换成4个ASCII码,存储内存中。
; 传参方式:寄存器传参
; 入口参数:AX
; 出口参数:ss:di(位置可自定义)
;-----------------------------------------------------
mov cx,4
mov bh,0 ;bh 作为计数器,判断最高位是否为0。默认最高位是0
HTASCS1:
ROL AX,1
ROL AX,1
ROL AX,1
ROL AX,1
mov bl,al
and bl,0fh
cmp bl,0
jnz HTASCS2 ;判断最高位是否为0,不为0,bh置1
cmp bh,1
jz HTASCS3 ;如果最高位大于等于1,保存结果
jmp HTASCS4 ;如果最高位是0,就不保存结果
HTASCS2:
mov bh,1
HTASCS3:
add bl,30h
mov ss:[di],bl
inc di
HTASCS4:
loop HTASCS1
ret
HTASCS endp
code ends
end start
T4-12-1 的子程序 “HTASCS”,将一个字的十进制数转换为ASCII,并保存到 ss:di。同时会判断最高的位置,并存储相应值。
- 使用 int21 接收字符串输入
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324;程序名:T4-12-2.asm
;功能:写一个程序,它先接收一个字符串,然后显示其中数字符的个数、英文字母的个数和字符串的长度
;使用 int21 中断,10号功能接收输入
assume cs:code,ds:data,ss:stack
data segment
MLENGTH = 128
buffer db MLENGTH
db 0
db MLENGTH dup (0)
string1 db 'The number of letters is: %u',0,0
string2 db 'The number of numbers is: %u',0,0
string3 db 'String length is: %u',0,0
data ends
stack segment
print_buffer db 128 dup(0)
stack ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
;
;lea bx,buffer
;call INPUT
mov dx,offset buffer
mov ah,10
int 21h
;
push ds
mov bx,offset buffer+2
push bx
call CHARSTAT
add sp,4
;
lea dx,string1
push dx
xor dx,dx
mov dl,ah
push dx
call far ptr print
;
call NEWLINE
;
lea dx,string2
push dx
xor dx,dx
mov dl,al
push dx
call far ptr print
;
call NEWLINE
;
mov dx,offset buffer+2 ;使用 int21 中断,10号功能接收字符串,字符串首地址偏移需要+2
push ds
push dx
call STRLEN
add sp,8
;
lea dx,string3
push dx
push ax
call far ptr print
;
mov ax,4c00h
int 21h
;-----------------------------------------------------
CHARSTAT PROC
; 功能:统计字符串中的字母、数字、其他字符的个数
; 传参方式:堆栈传参
; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
; 出口参数:AH=字母个数、AL=数字的个数
;------------------------------------------------------
push bp
mov bp,sp
pushf
push es
push di
push bx
;
mov di,[bp+4]
mov es,[bp+6]
xor ax,ax
CHARSTAT1:
mov bl,es:[di]
;判断是否读取结束,这里用0dh
cmp bl,0dh
jz CHARSTAT_POP
;判断小写字母
cmp bl,'a'
jae CHARSTAT3
;判断大写字母
cmp bl,'A'
jae CHARSTAT2
;判断数字
cmp bl,'0'
jae CHARSTAT0
inc di
jmp CHARSTAT1
CHARSTAT0:
cmp bl,'9'
jbe number
inc di
jmp CHARSTAT1
CHARSTAT2:
;判断字母
cmp bl,'Z'
jb letter
inc di
jmp CHARSTAT1
CHARSTAT3:
cmp bl,'z'
jb letter
inc di
jmp CHARSTAT1
letter:
inc ah
inc di
jmp CHARSTAT1
number:
inc al
inc di
jmp CHARSTAT1
;
CHARSTAT_POP:
pop bx
pop di
pop es
popf
mov sp,bp
pop bp
;
ret
CHARSTAT endp
;-----------------------------------------------------
STRLEN PROC
; 功能:测量字符串的长度
; 传参方式:堆栈传参
; 入口参数:字符串起始地址的段值和偏移地址在堆栈中
; 出口参数:AX=字符串长度
;------------------------------------------------------
push bp
mov bp,sp
push es
push di
;
xor ax,ax
mov di,[bp+4]
mov es,[bp+6]
STRLEN1:
mov bl,es:[di]
cmp bl,0dh ;int 21号功能,字符串最后存储的是0dh
jz STRLEN2
inc di
inc ax
jmp STRLEN1
STRLEN2:
pop di
pop es
mov sp,bp
pop bp
ret
STRLEN endp
;--------------------------------------------------------
NEWLINE PROC
; 功能:形成回车和换行(光标移到写一行首)
; 入口参数:无
; 出口参数:无
; 说 明:通过显示回车符形成回车,通过显示换行符形成换行
;---------------------------------------------------------
push ax
push dx
mov dl,0dh
mov ah,2
int 21h
mov dl,0ah
mov ah,2
int 21h
pop dx
pop ax
ret
NEWLINE endp
print proc far
push bp
mov bp,sp
push ds
push es
push ss
push si
push di
push ax
push bx
push cx
push dx
;判断在字符串中是否有 %d、%s等(同时将字符串复制),结束标志0
;初始化寄存器
xor si,si
xor di,di
mov si,[bp+8]
lea di,print_buffer
print1:
mov ax,[si]
cmp ax,0
jz print_b
;
cmp ax,'u%'
jz unsigned_int
;
;cmp ax,'%s'
;jz %s
;
;cmp ax,'%x'
;jz %x
mov ss:[di],al
inc si
;inc si
;inc di
inc di
jmp print1
;有:%d(将十六进制转换为十进制,复制)、%s(指定偏移,复制)
unsigned_int:
call HEXTODEC
;无:输出结果
print_b:
mov al,'$'
mov ss:[di],al
mov ax,ss
mov ds,ax
lea dx,print_buffer
mov ah,9
int 21h
;
pop dx
pop cx
pop bx
pop ax
pop di
pop si
pop ss
pop es
pop ds
mov sp,bp
pop bp
ret
print endp
HEXTODEC PROC
xor dx,dx
mov ax,[bp+6]
mov bx,ax
;结果小于9,就直接+30h,存储到ss:di中
cmp ax,9
jbe HEXTODEC4
HEXTODEC1:
add ax,6
adc dl,dl
sub bx,0ah
cmp bx,9
jbe HEXTODEC2
jmp HEXTODEC1
;运算结束后,判断是否有进位
HEXTODEC2:
cmp dl,0
jz HEXTODEC3
;有进位,先存储dl,再存储ax
add dl,30h
mov ss:[di],dl
inc di
HEXTODEC3:
;无进位,处理ax,并存储到 ss:di 中
call HTASCS
jmp HEXTODEC5
HEXTODEC4:
add al,30h
mov ss:[di],al
inc di
HEXTODEC5:
;
ret
HEXTODEC endp
;-----------------------------------------------------
HTASCS PROC
; 功能:把一个字大小的16进制数(每个位必须<=9),转换成4个ASCII码,存储内存中。
; 传参方式:寄存器传参
; 入口参数:AX,
; 出口参数:ss:di(位置可自定义)
;-----------------------------------------------------
mov cx,4
mov bh,0 ;bh 作为计数器,判断最高位是否为0。默认最高位是0
HTASCS1:
ROL AX,1
ROL AX,1
ROL AX,1
ROL AX,1
mov bl,al
and bl,0fh
cmp bl,0
jnz HTASCS2 ;判断最高位是否为0,不为0,bh置1
cmp bh,1
jz HTASCS3 ;如果最高位大于等于1,保存结果
jmp HTASCS4 ;如果最高位是0,就不保存结果
HTASCS2:
mov bh,1
HTASCS3:
add bl,30h
mov ss:[di],bl
inc di
HTASCS4:
loop HTASCS1
ret
HTASCS endp
code ends
end start
上面的程序执行结束后会出现问题,第一行的输出结果会错误,但是在调试过程中是正确的。
我认为是 int21 的10号功能,接收字符串输入之后,屏幕光标位置会重新定位还原到之前的输入点。后面输出结果会覆盖之前的输入的字符串,问题在于当前输出的结果,没有输入字符串长,那么多出来的这几个字符并不会被覆盖,导致第一行显示结果错误。
例4
写一个显示指定内存单元内容的程序。具体要求是:用户按十六进制数的形式输入指定内存单元的段值和偏移,然后用十六进制数形式显示指定字节单元的内容。
程序流程图
代码
1 | ;程序名:T4-13.asm |
磁盘文件管理及应用
DOS磁盘文件管理功能调用是 DOS 功能的重要组成部分,不仅有助于汇编语言程序设计练习,也有助于文件管理系统的理解。
DOS 磁盘文件管理功能调用
- DOS 磁盘文件管理功能调用中,用于表示文件名的ASCII字符串必须以 ASCII 码值 0 结尾,这样的字符串通常称为 ASCIIZ 串。
- 文件名可以是包含盘符和路径的文件标识。
- 如果没有盘符,那么默认是当盘,如果路径不是从根目录开始,那么就会从当前目录开始。
这些功能调用均利用标志 CF 表示调用是否从成功,如果不成功,那么AX含有错误代码。常见错误代码如下:
错误代码 | 描述 |
---|---|
01 | 无效的功能号 |
02 | 文件未找到 |
03 | 路径未找到 |
04 | 同时打开文件太多 |
05 | 拒绝存取 |
06 | 无效的文件号(柄) |
创建文件(3CH号功能调用)
功能:创建一个新的或老的文件
入口参数:
DS:DX=文件名字符串首地址
CX=文件属性
出口参数:
CF=0 表示成功,AX=文件号(柄)
CF=1 表示失败,AX=错误代码
说明:
- 可指定的文件属性
代码号 | 描述 |
---|---|
00H | 普通 |
01H | 只读 |
02H | 隐含 |
04H | 系统 |
- 文件创建成功后,文件长度定为0
打开文件(3DH号功能调用)
功能:打开文件
入口参数:
DS:DX=代表文件名的字符串的首地址
AL=存取方式
出口参数:
CF=0 表示成功,AX=文件号(柄)
CF=1 表示失败,AX=错误代码。
- 存取方式规定如下:
代码号 | 描述 |
---|---|
00H | 只读方式 |
01H | 只写方式 |
02H | 独写方式 |
- 文件打开成功后,文件指针定位于开始的第一个字节(偏移0)处。
读文件(3FH号功能调用)
功能:读文件
入口参数:
BX=文件号(柄)
CX=字节数
DS:DX=准备存放所读数据的缓冲区首地址。
出口参数:
CF=0 表示成功,AX=实际读到的字节数。
CF=1 表示失败,AX=错误代码
说明:
- 通常情况下,实际读到的字节数与需要读入的字节数相同,除非不够读。
- 缓冲区应保证能容下所读到的数据
- 文件应以读或读写方式打开
- 读文件后,文件指针将定位到读出字节之后的第一个字节处
写文件(40H号功能调用)
功能:写文件
入口参数:
BX=文件号(柄)
CX=写入字节数
DS:DX=存放写数据的缓冲区的首地址
出口参数:
CF=0 表示成功,AX=实际写入的字节数
CF=1 表示失败,AX=错误代码
说明:
- 通常情况下,实际写入的字节数与需要写入的字节数相同,除非磁盘已满
- 文件应以写或读写的方式打开
- 写文件后,文件指针将定位到写入字节后的第一个字节数
关闭文件(3EH号功能)
功能:关闭文件
入口参数:
CF=0 表示成功
CF=1 表示失败
说明:
- 文件号是打开文件时系统给定的文件号。
移动文件读写指针(42H号功能调用)
功能:移动文件(读写)指针
入口参数:
CF=0 表示成功,此时 DX:AX=移动后的文件指针值。
CF=1 表示失败,此时 AX=1 表示无效的移动方式,AX=6 表示无效的文件号。
说明:
文件指针值(双字)是以问年间首字节为0计算的。
移动方式和表示的意义如下:
代码号 描述 00H 移动文件后指针值=0(文件头)+移动位移量 01H 当前文件指针值+移动位移量 02H 文件长(文件尾)+移动位移量 在第一种移动方式中,移动位移量总是正的
在后两种移动方式中,移动位移量可正可负
该子功能不考虑文件指针是否超出文件范围
删除文件(41H号功能调用)
功能:删除文件
入口参数:
DS:DX=代表文件名的字符串首地址。
出口参数:
CF=0 表示成功
CF=1 表示失败,AX=错误代码
说明:只能删除一个普通文件
例1
显示文本内容。文本文件固定为当前目录下的test.txt
文件读取流程:打开文件、读取文件、输出内容、关闭文件
打开文件:以只读方式打开文件,并且根据返回值输出不同的信息。
读文件:传入相应参数,判断有效读取字节数(AX)和将要读取字节数(CX)是否一致
关闭文件:如不能关闭文件,提示错误信息。
1 | ;程序名:T4-14.asm |
这段代码中多写了一些不必要且重复的判断。这个程序当时写的急,没有把各个子程序的信息详细写明。
1 | ;程序名:T4-14-1.asm |
例2
把键盘上输入的全部字符,存入某个文件的程序。文件固定为当前根目录下的test.txt。如果它已经存在,则更新它。
书中的举例(重新建立 test.txt 覆盖原数据,写入输入数据)
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86;程序名:T4-15.asm
;功能:把键盘上输入的字符全部字符(直到CTRL+Z键,值1AH)存入文件 test.txt 中,
;如果 test.txt 文件已存在则更新它。
assume cs:code,ds:data
EOF = 1AH
data segment
FNAME db '\TEST.txt',0
ERRMESS1 db 'Can not create file',07h,'$'
ERRMESS2 db 'Writing error',07h,'$'
BUFFER db ?
data ends
code segment
start:
mov ax,data
mov ds,ax
;
mov dx,offset FNAME ;建立文件
mov cx,0
mov ah,3CH
int 21h
jnc CREA_OK ;建立成功,跳转
;
mov dx,offset ERRMESS1 ;显示不能新建文件提示信息
call DISPMESS
jmp over
;
CREA_OK:
mov bx,ax ;保存文件句柄
CONT:
call GETCHAR ;接收一个键
push ax
call WRITECH ;向文件写所读的字符
pop ax
jc WERROR ;写出错,转
cmp AL,EOF ;是否已读到文件结束符
jnz CONT ;不是,继续
jmp CLOSEF ;遇文件结束符,转结束
;
WERROR:
mov dx,offset ERRMESS2 ;显示写出错误提示信息
call DISPMESS
;
CLOSEF:
MOV ah,3EH ;关闭文件
int 21h
over:
mov ax,4c00h
int 21h
;-----------------------------------------------------
WRITECH PROC
;
; 功能:把需要写入的一个字节,送入缓冲区
;-----------------------------------------------------
mov BUFFER,AL
mov dx,offset BUFFER
mov cx,1
mov ah,40h
int 21h
ret
WRITECH endp
;-----------------------------------------------------
GETCHAR PROC
; 功能:输入一个字符
;-----------------------------------------------------
mov ah,1
int 21h
ret
GETCHAR endp
;--------------------------------------------------------
DISPMESS PROC
; 功能:显示由 DX 所指的提示信息
; 入口参数:DX=字符串首地址
; 出口参数:无
;---------------------------------------------------------
mov ah,9
int 21h
ret
DISPMESS endp
code ends
end start读 test 文件,在写入输入缓冲区的内容,实现文件更新
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131;程序名:T4-15-1.asm
;功能:把键盘上输入的全部字符,存入某个文件的程序
;文件固定为当前根目录下的test.txt。如果它已经存在,则更新它。
;打开文件(判断是否已存在),如已存在:读取文件并存入缓冲区、读取键盘输入存入缓冲区
;如不存在:创建文件,将输入存入缓冲区
assume cs:code,ds:data
data segment
fname db 'test.txt',0
handle dw 0
flag db 0
read_buffer db 0
input_buffer db 30 dup(0)
len dw 0
message db 'Please enter the data you want to write: ',24h
input_error db 'Please enter char$'
close_error db 'ERROR: cannot close file',07h,24h
data ends
code segment
start:
mov ax,data
mov ds,ax
open:
;打开文件,并判断是否打开成功
call far ptr openfile
jnc create_ok
;创建文件
create:
lea dx,fname
mov cx,0
mov ah,3ch
int 21h
jmp open ;创建文件后,还需要打开文件才能写入数据
;文件创建成功,提示输入
create_ok:
xor di,di
read_ch:
;读文件,并将内容复制到缓冲区中。
mov bx,handle
lea dx,read_buffer
mov cx,1
mov ax,3Fh
int 21h
cmp ax,0 ;如果没有正确读到字符,ax=0
jz input_ch
;将读到的字符传入写字符缓冲区(只需要将文件内容读一遍,原文件字符就不会被覆盖)
;mov al,read_buffer
;mov input_buffer[di],al
;inc di
jmp read_ch
input_ch:
call far ptr input
;输入成功,将字符串写到文件中
write:
lea dx,input_buffer
mov bx,handle
mov cx,len
mov ah,40h
int 21h
close:
call far ptr closefile
mov ax,4c00h
int 21h
openfile proc far
lea dx,fname
mov ax,3d02h ;以读写方式打开文件
int 21h
mov handle,ax
stack:
ret
openfile endp
input proc far
;打印提示信息
lea dx,message
mov ah,9
int 21h
loop_input:
;用户输入
mov ah,1
int 21h
;判断用户输入结束按键
cmp al,0dh
jz returnA
;根据情况设置用户输入规则
cmp al,20h
jb input_err
;
mov input_buffer[di],al
inc di
jmp loop_input
input_err:
;换行
mov dl,0ah
mov ah,2
int 21h
;
lea dx,input_error
mov ah,9
int 21h
jmp stop
stop:
mov ax,4c00h
int 21h
returnA:
mov input_buffer[di],0 ;1AH
mov len,di
ret
input endp
closefile proc far
mov bx,handle
mov ah,3EH
int 21h
jnc stack2
;
mov dl,0ah
mov ah,2
int 21h
mov dx,offset close_error
mov ah,9
int 21h
stack2:
ret
closefile endp
code ends
end start使用42h号功能,修改文件读写指针,实现更新功能
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121;程序名:T4-15.asm
;功能:把键盘上输入的全部字符(直到Ctrl+Z键,值1AH)存入某个文件的程序
;文件固定为当前根目录下的test.txt。如果它已经存在,则更新它
;算法:使用 42H 号功能实现
assume cs:code,ds:data
data segment
fname db 'test.txt',0
handle dw 0
flag db 0
read_buffer db 0
input_buffer db 30 dup(0)
len dw 0
message db 'Please enter the data you want to write: ',24h
input_error db 'Please enter char$'
close_error db 'ERROR: cannot close file',07h,24h
data ends
code segment
start:
mov ax,data
mov ds,ax
open:
;打开文件,并判断是否打开成功
call far ptr openfile
jnc create_ok
;创建文件
create:
lea dx,fname
mov cx,0
mov ah,3ch
int 21h
jmp open ;创建文件后,还需要打开文件才能写入数据
;文件创建成功,提示输入
create_ok:
;修改读写指针
mov bx,handle
xor cx,cx
xor dx,dx
mov ax,4202h
int 21h
input_ch:
call far ptr input
;输入成功,将字符串写到文件中
write:
lea dx,input_buffer
mov bx,handle
mov cx,len
mov ah,40h
int 21h
close:
call far ptr closefile
mov ax,4c00h
int 21h
openfile proc far
lea dx,fname
mov ax,3d02h ;以读写方式打开文件
int 21h
mov handle,ax
stack:
ret
openfile endp
input proc far
;打印提示信息
lea dx,message
mov ah,9
int 21h
loop_input:
xor di,di
;用户输入
mov ah,1
int 21h
;判断用户输入结束按键
cmp al,0dh
jz returnA
;根据情况设置用户输入规则
cmp al,20h
jb input_err
;
mov input_buffer[di],al
inc di
jmp loop_input
input_err:
;换行
mov dl,0ah
mov ah,2
int 21h
;
lea dx,input_error
mov ah,9
int 21h
jmp stop
stop:
mov ax,4c00h
int 21h
returnA:
mov input_buffer[di],0 ;1AH
mov len,di
ret
input endp
closefile proc far
mov bx,handle
mov ah,3EH
int 21h
jnc stack2
;
mov dl,0ah
mov ah,2
int 21h
mov dx,offset close_error
mov ah,9
int 21h
stack2:
ret
closefile endp
code ends
end start
例3
写一个程序把文件2拼接到文件1上。文件1固定为当前目录下的TEST1,文件2固定为当前目录下的TEST2
该示例是使用到 21中断的42h号
功能,疑问文件读写指针实现的。
1 | ;程序名:T4-17.asm |
- 本文标题:8086汇编-子程序设计
- 本文作者:9unk
- 创建时间:2022-08-16 21:53:00
- 本文链接:https://9unkk.github.io/2022/08/16/8086-hui-bian-zi-cheng-xu-she-ji/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!