第三节 汇编语言程序举例例一,数据传送程序例二,查找关键字
▲ 掌握 DOS的装入和返回功能
▲ 复习 DOS系统的启动过程执行用户程序后,
若要返回 DOS状态,
即在屏幕上出现 DOS提示符,
等待输入新的命令,
应在用户程序的最后安排完成此功能的程序段 。
FF FF,0000 H
1
2
4
3
ROM
系统检测程序
IO,SY S 模块
COM MAN D,CO M
MS DOS,SY S 模块用户程序空 间引导程序
1K B 中断向量表
RA M
内 存
ROM BIO S
DO S
IO,SY S
COM MAN D,CO M
MS DOS,SY S
引导程序磁 盘
EDIT,EX E
MA SM,EX E
LINK,E XE
DE BUG.e x e
应用程序其他系统程序
he llo,asm
he llo,obj
he llo,exe
通常采用调用
DOS 系统功能完成 。
调用 DOS系统的 4CH 功能,返回 DOS
方法,在要返回 DOS 处,安排指令:
MOV AH,4CH
INT 21H
执行完 4CH的功能调用,即返回 DOS 。
例 code SEGMENT
ASSUME CS,code
start:,、,;程序主体部分
、、、
、、、
MOV AH,4CH ;返回 DOS
INT 21H
code ENDS
END start
例一 编写完整汇编语言程序,完成内存数据块传送功能 。
将某段中的字符串,Hello!”传送到另一段中 。
开始建立传送方向
DS,SI ← 源串首地址
ES,DI ←目的串首地址
CX ← 串长度串传送返回 D O S
aa SEGMENT ; 数据段 1
xx DB 'Hello!’ ; 定义源串
aa ENDS
bb SEGMENT ;数据段 2
yy DB 6 dup (?) ; 定义目的缓冲区
bb ENDS
cc SEGMENT ;代码段
ASSUME CS:cc,DS:aa,ES:bb ;指示指令中标号,变量所在段
start,CLD ;设置传送方向
MOV AX,aa ;DS,SI ← 源串首地址
MOV DS,AX
LEA SI,xx
MOV AX,SEG yy ;ES,DI ← 目的首地址
MOV ES,AX
MOV DI,OFFSET yy
MOV CX,6 ;CX ← 串的长度
REP MOVSB ;串传送
MOV AH,4CH ;调用 4CH系统功能,返回 DOS
INT 21H
cc ENDS
END start ;指示程序结束和程序入口
D:\>EDIT hello.asm ;编写源程序
D:\>MASM hello ; ;汇编源程序
Microsoft (R) Macro Assembler Version 5.10
Copyright (C) Microsoft Corp 1981,1988,All rights reserved.
49860 + 421241 Bytes symbol space free
0 Warning Errors
0 Severe Errors
D:\>LINK hello ; ;连接程序
Microsoft (R) Overlay Linker Version 3.61
Copyright (C) Microsoft Corp 1983-1987,All rights reserved.
LINK,warning L4021,no stack segment
D:\>hello ;执行程序
D:\>
警告性错误:无堆栈段此错误可忽略
D:\>DEBUG hello1.exe ;利用 DEBUG查看结果
-U ;查看程序代码
129F:0000 FC CLD
129F:0001 B89D12 MOV AX,129D
129F:0004 8ED8 MOV DS,AX
129F:0006 8D360000 LEA SI,[ 0000 ]
129F:000A B89E12 MOV AX,129E
129F:000D 8EC0 MOV ES,AX
129F:000F BF0000 MOV DI,0000
129F:0012 B90600 MOV CX,0006
129F:0015 F3 REPZ
129F:0016 A4 MOVSB
129F:0017 B44C MOV AH,4C
129F:0019 CD21 INT 21
、、、、、、
-D 129D:0 L20 ;执行程序前,查看源串内容
129D:0000 48 65 6C 6C 6F 21 00 00-00 00 00 00 00 00 00 00 Hello!..........
129D:0010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00,...............
-D 129E:0 L20 ;执行程序前,查看目的串
129E:0000 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00,...............
129E:0010 FC B8 9D 12 8E D8 8D 36-00 00 B8 9E 12 8E C0 BF,......6........
-
-G ; 执行程序
Program terminated normally
-D 129E,0 L20 ; 查看目的串
129E:0000 48 65 6C 6C 6F 21 00 00-00 00 00 00 00 00 00 00 Hello!..........
129E:0010 FC B8 9D 12 8E D8 8D 36-00 00 B8 9E 12 8E C0 BF,......6........
-D 129d,0 L50 ; 从源串重新查看
129D:0000 48 65 6C 6C 6F 21 00 00-00 00 00 00 00 00 00 00 Hello!..........
129D:0010 48 65 6C 6C 6F 21 00 00-00 00 00 00 00 00 00 00 Hello!..........
129D:0020 FC B8 9D 12 8E D8 8D 36-00 00 B8 9E 12 8E C0 BF,......6........
129D:0030 00 00 B9 06 00 F3 A4 B4-4C CD 21 09 E8 08 00 03,.......L.!.....
129D:0040 F1 E8 03 00 3C 0D C3 AC-E8 EC F8 75 04 3C 3B 75,...<......u.<;u
-
问题思考,(实验课上讨论 )
1,一个段的大小为多少? 一定是 64K吗?
根据 DEBUG下查看的结果,画出程序各段在内存的存放情况 。
2,假如将程序中的 MOV CX,6 改为 MOV CX,100H
程序执行的结果会如何? 试用 DEBUG观察结果 。
3,假如程序中没有返回 DOS的功能,程序执行的后果?
aa SEGMENT ; 数据段 1
xx DB 'Hello!’ ; 定义源串
aa ENDS
bb SEGMENT ;数据段 2
yy DB 6 dup (?) ; 定义目的缓冲区
bb ENDS
cc SEGMENT ;代码段
ASSUME CS:cc,DS:aa,ES:bb ;指示指令中标号,变量所在段
start,CLD ;设置传送方向
MOV AX,aa ;DS,SI ← 源串首地址
MOV DS,AX
LEA SI,xx
MOV AX,SEG yy ;ES,DI ← 目的首地址
MOV ES,AX
MOV DI,OFFSET yy
MOV CX,6 ;CX ← 串的长度
REP MOVSB ;串传送; MOV AH,4CH ;调用 4CH系统功能,返回 DOS; INT 21H
cc ENDS
END start ;指示程序结束和程序入口
D:\MASM\DEBUG hello1.exe
- U
129F:0000 FC CLD
129F:0001 B89D12 MOV AX,129D
129F:0004 8ED8 MOV DS,AX
129F:0006 8D360000 LEA SI,[0000]
129F:000A B89E12 MOV AX,129E
129F:000D 8EC0 MOV ES,AX
129F:000F BF0000 MOV DI,0000
129F:0012 B90600 MOV CX,0006
129F:0015 F3 REPZ
129F:0016 A4 MOVSB
129F:0017 46 INC SI
129F:0018 43 INC BX
129F:0019 50 PUSH AX
129F:001A FF36C601 PUSH [01C6]
129F:001E FFFF DI
-
hello.exe
用户程序与用户程序相连的内存内容被看作程序时死机
4,可否在程序结束处用一条 RET返回指令,返回 DOS?
aa SEGMENT ; 数据段 1
xx DB 'Hello!’ ; 定义源串
aa ENDS
bb SEGMENT ;数据段 2
yy DB 6 dup (?) ; 定义目的缓冲区
bb ENDS
cc SEGMENT ;代码段
ASSUME CS:cc,DS:aa,ES:bb ;指示指令中标号,变量所在段
start,CLD ;设置传送方向
MOV AX,aa ;DS,SI ← 源串首地址
MOV DS,AX
LEA SI,xx
MOV AX,SEG yy ;ES,DI ← 目的首地址
MOV ES,AX
MOV DI,OFFSET yy
MOV CX,6 ;CX ← 串的长度
REP MOVSB ;串传送
RET
cc ENDS
END start ;指示程序结束和程序入口
D:\MASM\DEBUG hello1.exe
- U
129F:0000 FC CLD
129F:0001 B89D12 MOV AX,129D
129F:0004 8ED8 MOV DS,AX
129F:0006 8D360000 LEA SI,[0000]
129F:000A B89E12 MOV AX,129E
129F:000D 8EC0 MOV ES,AX
129F:000F BF0000 MOV DI,0000
129F:0012 B90600 MOV CX,0006
129F:0015 F3 REPZ
129F:0016 A4 MOVSB
129F:0017 F3 RET
129F:0018 43 INC BX
129F:0019 50 PUSH AX
129F:001A FF36C601 PUSH [01C6]
、、、

hello.exe
用户程序与用户程序相连的内存内容被看作程序时
5,如下操作为何查看不到结果?
D:\>MASM hello; ;汇编源程序
D:\>LINK hello; ;连接程序
D:\>hello ;先在 DOS下运行程序
D:\>DEBUG ;再进入 DEBUG查看结果
- D 129E:0 L30 ;查看目的串
129E:0000 2A 75 05 80 0E 2D DA 02-3A 06 14 D4 75 C9 4E 32 *u...-..:...u.N2
129E:0010 C0 86 04 46 3C 0D 75 02-88 04 89 36 EB D8 89 0E,..F<.u....6....
129E:0020 E9 D8 C3 BE CE DC 8B 4C-05 8B 74 09 E8 08 00 03,......L..t.....
-D 129D:0 L30 ;查看源串
129D:0000 75 04 FE 06 29 DA 3C 3F-75 05 80 0E 2D DA 02 3C u...).<?u...-..<
129D:0010 2A 75 05 80 0E 2D DA 02-3A 06 14 D4 75 C9 4E 32 *u...-..:...u.N2
129D:0020 C0 86 04 46 3C 0D 75 02-88 04 89 36 EB D8 89 0E,..F<.u....6....
原因是,程序 hello,exe及其执行结果不常驻内存 。
D:\>hello
在用户空间的低端,
装入 hello.exe,
然后执行 hello.exe
并返回 DOS。
D:\>DEBUG
在用户空间的低端,
装入 DEBUG.exe,
然后执行 DEBUG.exe
▲ 后装入的 DEBUG.exe
将其前装入的
hello.exe及其执行结果覆盖
ROM
系统检测程序
IO,SY S 模块
COM MAN D,CO M
MS DOS,SY S 模块用户程序空 间
DO S 工作区
1K B 中断向量表
RA M
内 存
ROM BIO S
DO S
IO,SY S
COM MAN D,CO M
MS DOS,SY S
引导程序磁 盘
EDIT,EX E
MA SM,EX E
LINK,E XE
DE BUG.e x e
应用程序其他系统程序
h e l l o,a s m
h e l l o,o b j
h e l l o,e x e
D:\>DEBUG hello.exe
装入 DEBUG.exe 和 hello.exe
ROM
系统检测程序
IO,SYS 模块
COM MAN D,CO M
MS DOS,SYS 模块
DEBUG,exe
He ll o.exe
DO S 工作区
1KB 中断向量表
RA M
内 存
ROM BIO S
DO S
IO,SYS
COM MAN D,CO M
MS DOS,SYS
引导程序磁 盘
EDIT,EX E
MA SM.EX E
LINK.E XE
DE BUG.ex e
应用程序其他系统程序
he llo,asm
he llo,obj
he llo,exe
例二 用二进制显示中断向量表中数据 D0H的个数 。
分析,中断向量表指内存 0,0 ~ 0,3FFH
大小 400H (即 1K ) 字节空间 ;
查找关键字 D0H;
将关键字的个数,存放在 BX中,
采用二进制显示出来 BX内容,16个字符;
采用子程调用结构。
主程序流程图取内存单元内容,
与关键字比较,相等?
N
开始
Y
Y
N
BX ← 计数值加 1
修改指针,指向下一单元
CX ← CX - 1,查找结束?
DS,SI ← 查找区域首地址 0,0
CX ← 查找长度 4 0 0 H
BX ← 计数值初值 0
调用子程,显示 B X 的内容返回 D O S
子程序流程图开始
Y
N
清 D L 的高 7 位,只保留要显示位的值调用 D O S 系统 02 功能,显示 DL 中的字符
CX ← CX - 1,显示结束?
CX ← 显示字符个数 16
RE T 返回
BX 循环左移 1 位,
将要显示的位移至最低位,保存在 DL 中
DL ← DL +3 0H,
完成数值 0 ~ 1 的 A S C II 码转换;汇编语言程序结构例二 ( 子程结构 );用二进制显示中断向量表中数据 D0H的个数
key EQU D0H ;用符号表示常量 (关键字 )
code SEGMENT ;代码段开始
ASSUME CS:code
begin,MOV AX,0000H
MOV DS,AX
MOV SI,0000H
MOV CX,0400H
MOV BX,0
MOV AL,key
next,CMP [ SI ],AL
JNZ point
INC BX
point,INC SI
LOOP next
CALL display ; 调用显示子程
MOV AH,4CH ; 返回 DOS
INT 21H;用二进制显示 BX内容子程
display PROC
MOV CX,16
rotate,ROL BX,1
MOV DL,BL
AND DL,01H
ADD DL,30H
MOV AH,2H
INT 21H
LOOP rotate
RET ;子程返回
display ENDP
code ENDS ;代码段结束
END begin ;指示程序结束和;程序入口汇编源程序 scans.asm
D:\MASM\MASM scans;
Microsoft (R) Macro Assembler Version 5.10
Copyright (C) Microsoft Corp 1981,1988,All rights reserved.
scans.ASM (1 ),error A2009,Symbol not defined,D0H
scans.ASM (9 ),error A2009,Symbol not defined,KEY
49924 + 419241 Bytes symbol space free
0 Warning Errors
2 Severe Errors
D:\masm>
汇编程序提示出错的行号,据此可修改程序中的语法错误。;汇编语言程序结构例二 ( 子程结构 );用二进制显示中断向量表中数据 D0H的个数
key EQU 0D0H ;用符号表示常量 (关键字 )
code SEGMENT ;代码段开始
ASSUME CS:code
begin,MOV AX,0000H
MOV DS,AX
MOV SI,0000H
MOV CX,0400H
MOV BX,0
MOV AL,key
next,CMP [ SI ],AL
JNZ point
INC BX
point,INC SI
LOOP next
CALL display ; 调用显示子程
MOV AH,4CH ; 返回 DOS
INT 21H;用二进制显示 BX内容子程
display PROC
MOV CX,16
rotate,ROL BX,1
MOV DL,BL
AND DL,01H
ADD DL,30H
MOV AH,2H
INT 21H
LOOP rotate
RET ;子程返回
display ENDP
code ENDS ;代码段结束
END begin ;指示程序结束和;程序入口思考,1.用指令 LODSB如何改写程序?
2,用 SCASB如何改写该程序? (自己上机调试 )
汇编、连接后,执行程序:
D:\masm>scans ;在 DOS下运行程序
0000000000000100
D:\masm>debug ;利用 DEBUG检测结果
-S 0:0 L400 D0
0000:0023
0000:0043
0000:0073
0000:009C
-Q
D:\masm>
▲ 了解 DOS的装入功能内 存
256K B RO M
000 0,0000H
004 0,0000H
A000:0000H
C800:0000H
FE00:0000H
FE00:1FFF H
640K B R AM
基本内存显示器显示缓存区
ROM BIO S
系统检测程序
IO,SY S 模块
CO MMAND,CO M 常驻 模块
MS DOS,SY S 模块用户程序空间
CO MMAND,CO M 暂驻 模块
DO S 工作区
BIO S 工作区
1K B 中断向量表
128K B R AM
保留区
DO S
其他系统程序磁 盘应用程序
IO,SY S
COM MAN D,CO M
MS DOS,SY S
引导程序
EDIT,EX E
MA SM,EX E
LINK,E XE
DE BUG.e x e
hel lo.asm
hel lo.o bj
hel lo.e xe
sc ans.asm
sc ans.obj
sc ans.e xe
加载 DOS后的系统状态
DOS的装入功能 (又称 EXEC系统功能 )
可执行文件,exe,应装入内存方能执行 。
由 DOS的装入功能完成 。
在 DOS的提示符后输入可执行文件的文件名,
按回车键,DOS系统即调用装入功能,
将可执行程序装入内存 。
完成以下操作:
确定内存可用部分,
以便存放要执行的,exe 文件。
建立程序段前缀 PSP
( Program Segment Prefix)
◢ 程序段前缀大小 100H,
即 256个字节。
◢ 存放进程间的控制信息。
◢ PSP最开始的两个字节 CD 20,
是一条 INT 20H指令。
装入可执行程序,exe
000 0,0 000H
可用内存空间内 存
FF FF,0 00 0 H
ROM BIO S
系统检测程序
COM M AN D,COM
DO S 系统
1K B 中断向量表
CD 20
,、、
,、、
,、、
hel lo,exe
xxx x,0 000H
xxx x,0 0FFH
程序段前缀用户程序
修改以下寄存器的值
◢ DS,ES设置为程序段前缀所在内存的段值;
(DS)=xxxxH
(ES)=xxxxH
◢ SS,SP设置为由连接程序传过来的值;
◢ CS,IP设置为程序的入口地址,
即伪操作 END后跟的符号名对应的物理地址;
此时 CS:IP 指向用户程序,
开始执行用户程序 。
000 0,0 000H
可用内存空间内 存
FF FF,0 00 0 H
ROM BIO S
系统检测程序
COM M AN D,COM
DO S 系统
1K B 中断向量表
CD 20
,、、
,、、
,、、
hel lo,exe
xxx x,0 000H
xxx x,0 0FFH
程序段前缀
CS,I P
在 DEBUG下查看例一(数据传送)程序段前缀,
D:\masm>debug scans.exe
- R ;查看当前寄存器内容
AX=0000 BX=0000 CX=0035 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=128D ES=128D SS=129F CS=129F IP=0000 NV UP EI PL NZ NA PO NC
129D:0000 1E PUSH DS
- D DS:0 L100 ;查看此时数据段内容 (程序段前缀 )
128D:0000 CD 20 00 A0 00 9A F0 FE-1D F0 4F 03 F2 0B 8A 03,,.......O.....
128D:0010 F2 0B 17 03 F2 0B E1 0B-01 01 01 00 02 FF FF FF,...............
、、、
- U DS:0 l10 ;反汇编当前数据段内容 (程序段前缀 )
128D:0000 CD20 INT 20
128D:0002 00A0009A ADD [BX+SI+9A00],AH
、、、
- U ;查看程序代码
129F:0000 FC CLD
129F:0001 B89D12 MOV AX,129D
129F:0004 8ED8 MOV DS,AX
129F:0006 8D360000 LEA SI,[ 0000 ]
129F:000A B89E12 MOV AX,129E
129F:000D 8EC0 MOV ES,AX
、、、
129F:0017 B44C MOV AH,4C
129F:0019 CD21 INT 21

注意:
不能破坏程序段前缀内容,
否则无法返回 DOS,造成死机 。
下面介绍的第二种返回 DOS的方法 不作学习要求 。
(在老的教材或参考书中使用该法,新版的书中很少使用)。
想要掌握的同学可自学。
第二种 DOS返回方法,调用 20H类型的中断程序 。
◢ 20H 中断程序的功能:
处理程序结束,返回系统 。
◢ 调用 20H中断程序是有条件的:
要求当前的 CS应为程序段前缀在内存的段值 。
问题:
如何保证执行到 INT 20H时,
当前 CS的值为程序段前缀在内存的段值?
xxxx,00 00 INT 20
采用下面的程序框架,
可保证执行 INT 20H时,
当前的 CS值为程序段前缀在内存的段值 。
code SEGMENT
ASSUME CS:code
main PROC FAR ;使 RET为远返回
start,PUSH DS ;入栈保存地址
MOV AX,0 ;程序段前缀的首地址
PUSH AX
、,;程序主体部分
、、
RET ;取程序段前缀首地址
main ENDP
code ENDS
END start
CD 20
xxxx
,、、
0000
xxxx,0
SS:S P
SS:S P
程序段前缀 P S P
PS P 的段值
PS P 的偏值用户程序
CS IP;用二进制显示 BX内容子程
display PROC
MOV CX,16
rotate,ROL BX,1
MOV DL,BL
AND DL,01H
ADD DL,30H
MOV AH,2H
INT 21H
LOOP rotate
RET ;子程返回
display ENDP
code ENDS ;代码段结束
END begin ;指示程序结束和;程序入口
key EQU 0D0H ;用符号表示常量 (关键字 )
code SEGMENT ;代码段开始
ASSUME CS:code
mm PROC FAR ;使 RET为远返回
begin,PUSH DS ;入栈保存程序段前缀
MOV AX,0 ;首地址的段值,偏移值
PUSH AX
MOV AX,0000H
MOV DS,AX
MOV SI,0000H
MOV CX,0400H
MOV BX,0
MOV AL,key
next,CMP [ SI ],AL
JNZ point
INC BX
point,INC SI
LOOP next
CALL display ;调用显示子程
RET ;从堆栈弹出程序段前缀首地址
mm ENDP ;执行 INT 20H,返回 DOS;汇编语言程序结构例二 ( 子程结构 ) scans1.asm;用二进制显示中断向量表中数据 D0H的个数采用第二种 DOS返回方法编写例二。
在 DEBUG下查看程序段前缀,
D:\masm>debug scans1.exe
-R ;查看当前寄存器内容
AX=0000 BX=0000 CX=0035 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=128D ES=128D SS=129D CS=129D IP=0000 NV UP EI PL NZ NA PO NC
129D:0000 1E PUSH DS
-D DS:0 L30 ;查看此时数据段内容 (程序段前缀 )
128D:0000 CD 20 00 A0 00 9A F0 FE-1D F0 4F 03 F2 0B 8A 03,,.......O.....
128D:0010 F2 0B 17 03 F2 0B E1 0B-01 01 01 00 02 FF FF FF,...............
128D:0020 FF FF FF FF FF FF FF FF-FF FF FF FF 7E 12 4C 01,...........~.L.
-U DS:0 l10 ;反汇编当前数据段内容 (程序段前缀 )
128D:0000 CD20 INT 20
128D:0002 00A0009A ADD [BX+SI+9A00],AH
128D:0006 F0 LOCK
128D:0007 FE1D CALL FAR [DI]
128D:0009 F0 LOCK
128D:000A 4F DEC DI
128D:000B 03F2 ADD SI,DX
128D:000D 0B8A03F2 OR CX,[BP+SI+F203]
-
-R ;查看程序入口
AX=0000 BX=0000 CX=0035 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=128D ES=128D SS=129D CS=129D IP=0000 NV UP EI PL NZ NA PO NC
129D:0000 1E PUSH DS
-U 129D:0 L22 ;反汇编程序
129D:0000 1E PUSH DS
129D:0001 B80000 MOV AX,0000
129D:0004 50 PUSH AX
129D:0005 B80000 MOV AX,0000
129D:0008 8ED8 MOV DS,AX
129D:000ABE0000 MOV SI,0000
129D:000D B90004 MOV CX,0400
129D:0010 BB0000 MOV BX,0000
129D:0013 B0D0 MOV AL,D0
129D:0015 3804 CMP [SI],AL
129D:0017 7501 JNZ 001A
129D:0019 43 INC BX
129D:001A46 INC SI
129D:001B E2F8 LOOP 0015
129D:001D E80100 CALL 0021
129D:0020 CB RETF
129D:0021 B91000 MOV CX,0010
-G ;运行程序
0000000000000100
Program terminated normally
-
思考:
1,在第二种返回 DOS的方法中,
可否将主过程的类型 FAR去掉,为什么?
试描述去掉 FAR后程序执行的流程 。
( 可利用 DEBUG进行跟踪查看 )
2,可否在程序的最后直接用 INT 20H返回 DOS?
3,可否在程序的最后用 INT 3返回 DOS?