本节内容:介绍DOS程序段前缀及其应用。
■程序段前缀PSP:DOS系统加载程序时,在程序段前设置一个具有256字节的信息区称为程序段前缀PSP。
■终止程序的另一途径:利用程序段前缀PSP偏移地址0处的“INT 20H”终止程序。示例代码t19-1.asm。
■应用程序取得命令行参数:DOS系统通过程序段前缀PSP的偏移80H处获取命令行参数。示例代码t19-2.asm、t19-3.asm。
19.1.1 程序段前缀PSP
程序段前缀是DOS加载一个外部命令或应用程序(EXE或COM类型)时,在程序段之前设置的一个具有256字节的信息区。
DOS程序段前缀PSP含有许多可用信息,其中常用信息的安排如表19.1所示:
偏移 | 内容及意义 | 偏移 | 内容及意义 |
00H | 程序终止退出命令INT 20H | 16H | 保留 |
02H | 可用的内存空间高端段值 | 2CH | 环境块段值 |
04H | 保留 | 2EH | 保留 |
05H | DOS系统功能远调用指令 | 5CH | 格式化的未打开的文件控制块1 |
0AH | 程序处理中断向量原内容 | 6CH | 格式化的未打开的文件控制块2 |
0EH | Ctrl+C键处理中断向量原内容 | 80H | 命令行参数长度(字节数) |
12H | 严重错误处理中断向量原内容 | 81H | 命令行参数 |
表19-1 程序段前缀PSP的常用信息
当DOS把控制权转给外部命令或应用程序之时,数据段寄存器DS和附加段寄存器ES均指向其PSP,即均含有PSP的段值,而不是指向程序的数据段或附加段。这样应用程序可方便的使用PSP中的有关信息,参见例1。
如图19-1所示,debug调试器刚加载test.exe程序时,段寄存器DS和ES的值均为075AH。从075A:0开始的256个字节就是程序段前缀PSP区域。请读者对照表19-1,了解程序段前缀PSP区域的内容。需要特别关注的是偏移地址0处的INT 20H和偏移地址80H处的命令行参数,其他内容这里不再赘述。
图19-1 程序段前缀PSP
19.1.2 终止程序的另一途径
回顾一下我们前面写过的所有程序,都是利用DOS的4CH号系统功能调用能够终止程序,然后把控制权转交回DOS。但早期是利用DOS提供的20H号中断处理程序来终止程序的。
通过20H号中断处理终止程序有一个条件,即进入20H中断处理程序之前,代码段寄存器CS必须含有PSP的段值。由于对EXE类型的应用程序而言,其代码段和PSP段不是同一个段,所以不能简单的直接利用指令“INT 20H”来终止程序。DOS系统注意到了这一点,在PSP的偏移0处,安排了一条“int 20h”指令(机器码CD 20)。于是,应用程序只要转到PSP的偏移0处,就能实现程序的终止。请看下面的例子。
■例1:INT 20H退出程序
动手实验111:写一个显示字符串'HELLO'的程序。要求使用INT 20H退出程序。
在理解下面示例程序的基础上,自己独立编写源程序。编译完成后,在debug调试器中单步跟踪调试,以验证程序的正确性。
●算法分析:
使用ret指令转移到DOS段前缀PSP的 int 20H处退出程序。
●示例代码87:
;程序名:t19-1.asm
;功能:显示信息'HELLO'
;INT 20H
;---------------------------------
assume cs:code,ds:data,ss:stack
stack segment
dw 256 dup(?)
stack ends
data segment
mess db 'HELLO',0dh,0ah,'$'
data ends
code segment
main proc far
start:
push ds ;把PSP的段值压入堆栈
xor ax,ax
push ax ;把0000H偏移压入堆栈
;
mov ax,data
mov ds,ax
mov dx,offset mess
mov ah,9
int 21h
ret ;转PSP的偏移0处执行
main endp
code ends
end start
标号start开始处的三条指令将PSP的段值和偏移压入堆栈,最后返回指令RET从堆栈弹出程序开始时压入堆栈的PSP段值和偏移0到CS:IP,随后CPU就执行位于PSP首的“INT 20H”,此时CS含PSP的段值,程序终止,控制返回DOS。
提示
注意main proc far远过程伪指令,对应的返回指令ret,masm编译时当作远过程返回指令,TASM汇编时,则必须使用RETF指令。
不鼓励采用这种特殊形式,建议使用int 21H 4ch指令退出程序。
19.1.3 应用程序取得命令行参数
DOS加载一个外部命令或应用程序时,允许在被加载的程序名之后,输入多达127个字符(包括最后的回车符)的参数,并把这些参数送到PSP的非格式化参数区,即PSP中从偏移80H开始的区域。
应用程序可从PSP中获得命令行参数。PSP的偏移80H处含命令行参数的长度(字节数),从PSP的偏移81H开始存放命令行参数。通常以空格符引导,至回车符(0dh)结束。注意:命令行中的重定向符和管道符及有关信息不作为命令行参数送到PSP。
■例2:显示命令行参数
动手实验112:写一个显示命令行参数的程序。
在理解下面示例程序的基础上,自己独立编写源程序。编译完成后,在debug调试器中单步跟踪调试,以验证程序的正确性。
●算法分析:
先从PSP中把命令行参数传到程序定义的缓冲区,然后再显示。
●示例代码88:
;例1:写一个显示命令行参数的程序
;程序名:t19-2.asm
;先从PSP中把命令行参数传到程序定义的缓冲区,然后再显示。
;--------------------------------------------------------------
assume cs:code,ds:code
code segment
buffer db 128 dup(?) ;存放命令行参数的缓冲区
start:
cld
mov si,80h
lodsb ;取得命令行参数的长度,字节数
mov cl,al
xor ch,ch
push cs ;该程序数据和代码在同一个段中
pop es
mov di,offset buffer
push cx
rep movsb ;传命令行参数
pop cx
;
push es
pop ds ;置数据段寄存器
mov si,offset buffer
mov ah,2
jcxz over
next:
lodsb ;显示命令行参数
mov dl,al
int 21h
loop next
over:
mov ax,4c00h
int 21h
code ends
end start
注意
1.必须输入命令行参数才会显示:
图19-2显示命令行参数
2.没有输入命令行参数和输入命令行参数对比:
图19-3 没有命令行参数与有命令行参数对比
■例3:命令行参数给出文件名
动手实验113:写一个显示文本文件内容的程序,文件名作为命令行参数给出。
在理解下面示例程序的基础上,自己独立编写源程序。编译完成后,在debug调试器中单步跟踪调试,以验证程序的正确性。
●算法分析:
第一步:从DOS程序段前缀PSP中取出文件名命令行参数;
第二步:打开文件;
第三步:读取文件内容;
第四步:关闭文件。
●示例代码89:
;例3:写一个显示文本文件内容的程序。文件名作为命令行参数给出。
;程序名:t19-3.asm
;功能:显示文本文件的内容
;=======================================================
assume cs:code,ds:data
;符号常量定义
lenofid=128 ;文件标识符最大长度
space=' ' ;空格
tab=09h
eof=1ah
data segment
fname db lenofid dup(?) ;准备存放的文件名串
error0 db 'Required parameter missing',0
error1 db 'File not found',07h,0 ;提示信息07h=响铃
error2 db 'Reading error',07h,0
buffer db ? ;1字节的缓冲区
data ends
code segment
start:
cld
mov si,80h
lodsb ;取命令后参数长度
or al,al ;是否有命令行参数
jnz getfid1 ;有
fiderr:
mov ax,seg error0 ;没有命令行参数处理
mov ds,ax
mov si,offset error0
call dmess
jmp over
getfid1:
mov cl,al
xor ch,ch ;cx含命令行参数长度
getfid2:
lodsb ;取参数一字节
cmp al,space ;为空格
jz getfid3 ;是,跳过
cmp al,tab ;为制表符?
jnz getfid4 ;不是,表示已去掉前导空格
getfid3:
loop getfid2 ;跳过前导空格和制表符
jmp fiderr ;命令行参数没有其他符号,转
getfid4:
dec si ;字符串指令自动+1,需要回退1
mov ax,seg fname ;把剩下的命令行参数送入
mov es,ax
mov di,offset fname
rep movsb ;文件标识符区
xor al,al
stosb ;再补一个0,形成ASCIIZ串
;
mov ax,data
mov ds,ax
;
mov dx,offset fname ;置文件名首地址
mov ax,3d00h ;只读方式打开文件
int 21h
jnc open_ok ;CF=0打开成功转
mov si,offset error1 ;打开失败
call dmess ;显示打开不成功提示信息
jmp over ;结束
open_ok:
mov bx,ax ;保存文件柄
;从文件中读取一个字符
cont:
call readch ;从文件中读取一个字符
jc readerr ;如读错,则转
cmp al,eof ;读到文件结束符?
jz type_ok
call putch ;显示所读字符
jmp cont
readerr:
mov si,offset error2
call dmess ;显示错误提示信息
type_ok:
mov ah,3eh ;关闭文件
int 21h
over:
mov ax,4c00h
int 21h
;--------------------------------------------------
;子程序 ,屏幕显示al内容
;2号调用功能,向屏幕写入dl内容
;入口参数dl
putch proc
push dx
mov dl,al
mov ah,2
int 21h
pop dx
ret
putch endp
;--------------------------------------------------
;子程序:从文件中读取一个字符
;子程序名:readch
readch proc
mov cx,1 ;置读字节数
mov dx,offset buffer ;置读缓冲区地址
mov ah,3fh ;置功能调用号
int 21h
jc readch2 ;cf=1
cmp ax,cx ;判断文件是否结束,ax为实际读到的字节数
mov al,eof ;设文件已结束,置文件结束符,此时CF=0
jb readch1 ;文件已结束,转
mov al,buffer ;文件结束,取所读字符
readch1:
clc
readch2:
ret
readch endp
;--------------------------------------------------
;子程序显示一个以0结尾的字符串
;子程序名:dmess
;入口参数si=字符串首地址
;出口参数无
dmess proc
dmess1:
mov dl,[si]
inc si
or dl,dl
jz dmess2
mov ah,2
int 21h
jmp dmess1
dmess2:
ret
dmess endp
code ends
end start
注意
1.文件名必须是零结尾的字符串。
2.取命令行参数时,需要考虑过滤掉前导空格。
本文摘自编程达人系列教材《X86汇编语言基础教程》。