汇编编写DOS下的内存驻留程序.docx
- 文档编号:7169600
- 上传时间:2023-01-21
- 格式:DOCX
- 页数:33
- 大小:45.33KB
汇编编写DOS下的内存驻留程序.docx
《汇编编写DOS下的内存驻留程序.docx》由会员分享,可在线阅读,更多相关《汇编编写DOS下的内存驻留程序.docx(33页珍藏版)》请在冰豆网上搜索。
汇编编写DOS下的内存驻留程序
汇编编写DOS下的内存驻留程序
绪言
0.1内存驻留与中断
内存驻留程序英文叫TerminateandStayResidentProgram,缩写为TSR.这些程序加载进内存,执行完后,就驻留在内存里,当满足条件时,调到前台来执行。
内存驻留程序的常用形式有:
>诸如Borland的SideKick弹出式实用程序
>日历系统
>网络服务器
>通讯程序
>本地的DOS扩展(如CCDOS,UCDOS等中文系统都属于这个范畴)
>一些可恶的人利用TSR技术制作很多可恶的病毒程序,几乎所有的病毒程序都是TSR程序.
就象多任务系统调度一个进程有一个调度程序一样,在PC中从前台程序进入到一个TSR,也要有一个调度者,只是PC操作系统的调度不称为调度程序,而只称为触发机制.触发机制调度TSR执行在PC机上党称为激活一个TSR.触发机制主要有以下几种:
>硬件中断:
党用的是键盘中断INT9H,时钟中断INT8H,通讯中断INT14H,磁盘中断INT13H等等.
>软件中断:
党用的是键盘中断INT16H,时钟中断INT1CH,DOS中断INT21H,等等.
>以上各种的结合.
从以上的触发机制可以看出,TSR和PC机的中断系统有着密切的关系.每种激活方式实际上都是与中断有关的.常用特殊的击键序列的识别码是通过截获INT9H和INT16H来实现.实际上不管TSR程序的哪一个环节,都与中断有着密切的关系.因此在具体进行TSR和程序设计之前,先介绍PC中断系统.在此只作简单说明.
在PC机内存的最低端(0000H开始)的1K字节中,存放着256个指针即常说的中为向量或中断矢量(Interruptvertor),每个中断向量都指向一个子程序,该程序称为中断处理程序(Interruphandler).一个中断向量由四个字节组成,有一个字是中断处理程序的偏移量值,后一个字是中断处理程序的段值.256中断向量一起称为中断向量表.
手式计算中断向量的首址,可通过以下的公式来求得:
X号中断向量的首址=0000H:
X*4
当产生一个中断时,处理器都按顺序执行以下步骤:
>在堆栈上压入处理器的标志(相当于指令PUSHF).
>在堆栈上压入当前CS和IP值(相当于指令PUSHCS和PUSHIP).
>关闭中断(CLI)
>从中断向量加载的CS和IP,执行中断处理程序.
当执行完中断处理程序后,一般用IRET返回,它的作用是:
>从堆栈上取出保存的IP和CS(相当于指令POPCS和PUSHCS).
>同时恢复中断前的处理器标志(相当于指令POPF).
中断有多种分类,由触发的原因和实现的性质来分,可分为硬件中断和软件中断,从操作系统分层实现来说,可以分成BIOS中断,BOS中断和用户中断.
一方面,BIOS和DOS通过中断系统向用户提供一个操作系统功能界面.也就是说用户(一般来说是前台程序)的功能主要是通过调用DOS和BIOS的中断服务来实现的,具体来说就是通过INT指令来实现的.另一方面,BIOS和DOS由中断系统所构成,BIOS对硬件成为高层的功能,并通过中断的形式向用户提供.
如果在当前程序执行的同时,能将一块代码放在内存,把中断向量指向代码中的子程序,那么在当前程序执行中产生中断时,就有可能执行不属于当前程序和操作系统的代码,产生的中断可能是当前程序产生的软件中断,也可能是由硬件产生的硬件中断.这就是单任务的PC操作系统可能执行多于一个进程的简单说明.
在PC中断系统中有几个中断具有周期性,即INT8H,INT1CH和INT28H.它们或者周期性被执行用于时间计时,或者周期性产生用于等待.它们是在实现TSR时进行轮询触发的基础.键盘中断(INT9H和INT16H)当用户击键时发生,利用它们是进行热键处理的基础.串行口通讯也是触发的一个重要机制.此外众多的软件中断也是触发的媒介.
0.2DOS的可重入性分析
一个多任务操作系统之所以能使多个进行并存,是因为操作系统的大部分代码是可以了重的,对于临界资源有相应的PV操作,使得当调度一个新的进程时,能完整地保存前一个里程的现场,当再一次调度被挂起的进程时能象没有被中断一样继续执行.
对于PC机来说,代码的重入性比较弱,对临界资源没有PC操作.当我们用中断程序启动用户的TSR时,如果只保存标志和寄存器,以及当前进程一些信息,那么只保存了当前程序的一部分现场,DOS的临界资源不会自动保存.在进行TSR设计时,一定要了解PC操作系统的重入性和临界资源.
重入性总是体现在代码上,所谓可重入代码的指这样的代码,即该代码被执行时还没有从中退出,由于某种原因又一次或者多次进入相同的代码,该代码每次的执行结果都是正确的,就说该代码是可重入的.相反,如果结果不正确,那么就就该代码是不可重入的.下面是一个可重入的子程序的例子:
Addprocnear
cmpDS:
wordptr[si],0
jeDonotAddTheValue
addax,DS:
wordptr[si]
DonotAddTheValue:
ret
Addendp
上面的例子不管在其中任何一处再一次执行该子程序,执行结果不变.为了说明,只举多种可能性中的一种.
movds,0100h;ds=0100h
movsi,0010h;si=0010h
movax,0001h;ax,=0001h
callAdd
cmp0100h:
wordptr[0010h],0;CallAddsubroutine
pushds;Interrupted
pushsi
pushax
movds,0200h;ds=0200h
movsi,0200h;si=0020h
movax,0003h;ax=0003h
callAdd
cmp0200h:
wordptr[0020h],0;0200:
0020h=0004h
jne
addax,0200h:
wordptr[0020h];ax=0007h
ret;Return
popax;ax=0001h
popsi;si=0010h
popds;ds=0100h
iret;ReturntoAddsubroutine
jne
addax,0100h:
wordptr[0100h];ax=0001h
;0100h:
0010h=0002h
;----------------------------------------
;ax=0003h
ret
movbx,ax
而下面的子程序是不可重入的:
Addprocnear
movTemp,ax
movax,DS:
wordptr[si]
cmpax,0
jeDonotTheValue
addax,Temp
DonotTheValue:
ret
Temp:
dw0
Addendp
可以利用检查可重入子程序的方法检查这个子程序的不可重入性,尝试一下在"movax,DS:
wordptr[si]"指令后再次执行该子程序,那么就会出第一次调用返回的结果不对.
movds,0100h;ds=0100h
movsi,0010h;si=0010h
movax,0001h;ax,=0001h
callAdd
movTemp,ax;CallAddsubroutine
;Temp=0001h
movax,0100h:
wordptr[0010h];0100h:
0010h=0002h
;ax=2
pushds;Interrupted
pushsi
pushax
movds,0200h;ds=0200h
movsi,0020h;si=0020h
movax,0003h;ax=0003h
callAdd
movTemp,ax;Temp=0003h
movax,0200h:
wordptr[0020h];0200h:
0020h=0004h
cmpax,0;ax=0004h
jne;Notequal,add
addax,Temp;ax=0007h
ret;Returntotheinterruptedpoint
popax;ax=0002h
popsi;si=0010h
popds;ds=0100h
iret;ReturntoAddsubroutine
cmpax,0;ax=2
jne;Noequal,add
addax,Temp;ax=0002h
;0100h:
0010h=0003h
;----------------------------------------
;ax=0005h
ret
movbx,ax
上面执行的结果是AX=5,实上正确的结果应该是AX=3,这是由于当Add子程序从中断子程序再一次被调用时,修改了Temp的值,当从中断返回时不能正确恢复其值.
解决的方法是把Temp放在堆栈中,当每次Add子程序被调用时Temp的地址都不一样,因此原调用的Temp值不会被第二次在中断中调用的Add所破坏.
Addprocnear
pushbp;StoreBP
subsp,2;distributeabytespaceinthestack
movbp,sp;SS:
BPpointtothestackhead
tempequSS:
wordptr[BP+0];ExplainthepointertoSS:
BP
movTemp,ax
movax,DS:
wordptr[si]
cmpax,0
jeDonotAddTheValue
addax,Temp
DonotAddTheValue:
addsp,2;Releasethedsitributedspaceinthestack
popbp;RestoreBP
ret
Addendp
对于DOS来说,DOS的内存数据就象Temp变量,它被分配在数据区,而不在堆栈上,因此DOS从总体上是不可重入的.从最后的一个例子看来.重入性跟堆栈有很大的关系.可重入代码允许在任何时候被中断,其所有的变量都存放在该代码的私有堆栈中.DOS是一个单任务的操作系统,在执行INT21H的代码时是不允许中断DOS,并再次调用INT21H的.每个时该最多有一个进程在调用DOS的代码.
对DOS的重入性,以及相应所作的处理总结如下:
>当通过INT21H调用DOS时,DOS会使三个内部栈之一:
I/O栈,磁盘栈和辅助栈.功能00H到处0CH使用I/O栈,除了不致命错误处理程序以外使用磁盘栈,致命错误处理程序使用辅助栈.在这种栈切换模式下,如果前台处在INT22H中,而TSR调用了使用相同栈的DOS功能,就会使前台程序保存栈中的数据被TSR的数据覆盖掉;但如果调用不同栈的DOS功能,那将是安全的.INT21H中的几个功能调即33H,50H,51H,62H,和64H由于非常简单,使用用户栈,因此在任何情况下都是可重入的.避免这种不可重入的简单方法是当前台程序正处在INT21H中时,不要调用INT21H.或者如果前台程序正在处理INT21H时,只允许调用不同栈的INT21H功能.
>DOS数据区中有一个InDOS标志,也探源为DOS安全标志,表示当前访问DOS功能是来否安全.由于DOS不可重入,它指示当前是否处于DOS中,激活TSR和代码可检查该标志(34H),如果DOS忙,则不能激活使用INT21H调用的TSR.
>当前台程序执行能设置错误状态的DOS功能时,DOS会把扩展错误信息存放起来,正常情况下,前台程序可以读取扩展错误信息;如果在前台程序读取信息之前激活TSR,且TSR也执行能报告错误信息的DOS功能,则后来的错误信息会覆盖原来的错误信息,前台程序就会得不到正确的错误信息.因此必须在激活TSR之前保存(59H)这些错误信息,并在退出以前把它们恢复(5D0AH)成原来的值.
>大多硬件中断如INT13H,INT0BH和INT0CH等都是不可重往返.如果设置一引起寄存器,而在此时被TSR打断,执行类似的设置,就会出现非常情况,端口是不会自动保持值的.在进入这些中断时设置一个进入的标志,如果TSR检查到标志已置,则不调用相应的中断.
>最好也不要重入INT10H,INT25H,和INT26H中断.在进入这些中断时设置一个进入的标志,如果TSR检查到标志已置,则不调用相应的中断.
>最好能接管INT1BH,INT23H和INT24H中断.
>保存DOS的数据交换区(SDA)可以安全地使用的DOS的功能.SDA保存了DOS几乎所有内部数据,如果保存(5D06H)和恢复(5D0BH)SDA,DOS就变成在任何时候都可以重入的了.当DOS处在关键区中时,调用INT2AH.一旦处在关键区中,就不能改变SDA.在关键区的结束处会调用INT21H的81H和82H功能.
0.3内存驻留程序设计一般过程
驻留程序分成两个部分,即暂驻部分和驻留部分.驻留程序要完成安装检测,激活和删除等过程.
基本上可抽象成以下几个过程:
>取中断向量
>保存旧的中断向量
>设置或恢复中断向量
>中断处理程序的链接
>检测是来呀已驻留
>执行终止并驻留
>TSR的删除
删除TSR比较复杂,必须按下列步骤进行:
>检查中断向量是否已经被替换.如果没有替换,就恢复所有的中断向量;如果某个中断向量被替换,则跳过下面各步,不能删除该TSR.
>TSR的PSP中偏移量16H存放着父进程的PSP.把这个值改为当前进程的地址.
>把当前PSP设为TSR的PSP
>执行INT21H的4CH功能,释放TSR占用的内存,关闭所有文件,并使用PSP中存放的父进程地址和终止地址.
>这里控制返回到初始进程中,当前PSP也指向初始进程,所有寄存器值包括SS和SP都不确定.
在执行完上述步骤后,要恢复寄存器.
如果要无条件地删除TSR,必须监控每个TSR对中断向量表,内存控制块和设备驱动程序链的修改.
0.5缩写语表
ASCIZ:
以零结束的ASCII字符串.
BPD:
"BIOSParameterBlock(BIOS参数块)"的缩写.含有对驱动器的低级参数的说明.
CDS:
"CurrentDirectoryStructure(当前目录结构)"的缩写,含有某个逻辑驱动器的当前目录,类型和其它信息.
DPB:
"DOSDriveParameterBlock(DOS驱动器参数块)"的缩写,含有某个逻辑驱动器的介质说明及一些内部信息.
DPL:
"DOSParameterList(DOS参数表)"的缩写,该数据结构用来传递参数给SHARE和网络功能调用.
DTA:
"DiskTransferAddress(磁盘传输地址)"的缩写,指示对磁盘进行数据读写的功能调用不必显式地给出缓冲区地址.
FAT:
"FileAllocationTable(文件分配表)"的缩写,磁盘的文件分配表记录了所使用的簇信息.
FCB:
"FileControlBlock(文件控制块)"的缩写,在DOS的1.X版本中,用FCB来记录文件打开的状态..
IFS:
"InstallableFileSystem(可安装的文件系统)"的缩写,它允许一个非DOS格式的介质被DOS所使用.大多数情况下IFS与网络驱动器非常相似,尽管IFS最典型的情况是一个本地驱动器而不是一个远程驱动器.
JFT:
"JobFileTable(工作文件表)或OpenFileTable(打开文件表)"的缩写,程序PSP中的JFT可用来将文件句柄转换成SFT值.
NCB:
"NetworkcontrolBlock(网络控制块)"的缩写.NCB可用传递对NETBIOS的请求和接受来自NETBIOS处理程序的状态信息.
PSP:
"PorgramSegmentPrefix(程序段前缀)"的缩写.当程序被装入时,PSP为一个预留的256字节的数据区它包含了程序调用时的命令行内容和一些DOS的内部信息.
SDA:
"DOSSwappableDataArea(DOS对换数据区)"的缩写.SDA中包含有DOS内部使用的记录某个正在处理的功能调用状态的所有变量.
SFT:
"SystemFileTable(系统文件表)"的缩写,SFT是一个DOS内部数据结构,在DOS2+版本的句柄功能调用中用于管理某个已打开文件的状态,这就和在DOS1.X中,FCB管理已打开文件状态一样.
基本原理
2.18086/8088
IBMPC中央处理单元(CentralProcessingUnit)是微处理器Inter8088,8088是8086是小的版本.对于编写程序而言,两者几乎完全相同.两者之间的差别是在于:
它们对外的沟通.8086和外界沟通时是经由16位的输入输出通道,内存存取也是每次以16位为单位,8088和8086极为相似,但是它和外界沟通时就必须经由16位的通道.
2.1.1寄存器
8086/8088的结构简单,其中包含了一组一般用途的16位寄存器.AX,BX,CX,DX,BP,SI,DI.其中AX,BX,CX,DX还可以分成8位的寄存器,譬如:
AX可分为AH,AL;BX可分为BH,BL;CX可分为CH,CL;DX可分为DH,DL.寄存器BP,SI,DI的用途也没有特别的限制,但是却不能分成两个字节.另外寄存器SP主要是用来当做堆栈指针.除此之外,还有四个非常重要的段寄存器(SegmentRegister):
CS,DS,SS,ES.指令指针(Instru-ctionpointer)IP是用来控制目前CPU执行到哪一个指令.
8086设计时考虑到要和8位的CPU8080兼容.8位的计算机是使用两个字节(亦即16位)来定址,因此其定址空间可以达64K字节.16位的CPU在地址设定上选择了完全不同的方法.CPU以段(Segment)为单位,每一段范围内包括64K字节,而内存中则可以包含许多段.所以,操作系统可以在一个段内执行.而使用者的程序则可以在另一个段内执行.在一个段内,程序包可以把计算机视为只有64K字节内存空间.因此原先8位计算机上执行的程序就可以很容易地移植到16位计算机上.除此之外,内存段也可以彼此重叠,因而两个不同的程序就可以共用某一块内存.段值是以寄存器来设定的,而实际的地址值则是把段值(16位)往左移4位,然后再加上16位的位移(Offset),因此构成20位的地址值.所以8086可以直接做20位的地址,也就是可能存取到一兆字节的内存.在这一兆字节的内存中,IBMPC保留了最前面的320K字节给系统的ROMBIOS和显示内存,因此使用者最多也就能使用640K字节.
2.1.2寻址方式
寻址方式(Addressingmode)是一台计算机上许多复杂操作的关键所在.8086提供了以下几种寻址方法:
立即寻址,内存间接寻址,寄存器间接寻址等.
立即寻址,直接使用数字.
内存间接寻址,数值存放在数据段中的某个位置.
movbx,foo
foodw5
寄存器间接寻址.有两种寄存器可以使用在这种寻址方式下:
基址寄存器(BaseRegister)和索引寄存器(IndexRegister).基址寄存器分别是BX和BP,索引寄存器则是SI和DI.在这种寻址方式下,寄存器存放了数据段中的地址值.
movax,0F000h
moves,ax
movsi,0FFFEh
movdl,byteptres:
[si]
上面的程序使用间接寻址方式,由寄存器SI读出位于F000:
FFFE位置的数据.寄存器间接存取时,最多只能使用玛个基址寄存器各一个索引寄存器.
以上的寻址方式可以做不同的结合,因此组合后的结果很多.
2.1.3标志
8086有9个一位的标志(Flag),它们可以用指示CPU的各种状态.以下是9个标志的简介:
CF(CarryFlag):
CF为1时就表示算术运算的结果超出正确的长度.
PF(ParityFlag):
PF为1就表示使用偶校验,PF为0就表示使用奇校验.
AF(AuxiliaryCarryFlag):
和CF相同,只是它使用在低4位的结果.AF通常都使用在20位的地址计算上.
ZF(ZeroFlag):
ZF为1就表示运算结果是0,否则ZF就为0.
SF(SignFlag):
SF为1就表示运算结果的最高位是1,否则SF就为0.
TF(TrapFlag):
TF为1,CPU就单步地执行,在这种模式下每完成一个指令就发生一个特殊的中断.
IF(InterruptEnableFlag):
IF为1,允许CPU接收外界的中断,否则IF就为0.
DF(DirectionFlag):
这个标志使用在循环指令,譬如:
MOVS,MOVSB,MOVSW,CMPS,CMPSB和CMPSW.如果DF为1,循环运行时就使地址值往前增加.如果DF为0,则使地址往后减少.
OF(OverFlag):
OF为1,表示一个考虑正负号的运算超出了正确的字节的长度.
2.1.4循环
所有的循环指令都是以CX作为计数器.一个循环会反复地执行直到CX等于某一特定值为止.以下的程序就是利用反复地相加,完成两个数的相乘.
movax,0
movcx,4
next:
addax,6
loopnext
在上面的程序中,LOOP指令执行时会把CX减1,并且检查CX的内容;如果CX等于0,就转移到下一条指令,否则就跳到NEXT标示的地方执行.
也可以用下面的程序完成相同的功能:
movax,0
movcx,4
next:
addax,6
deccx
cmpcx,0
jnenext
2.1.5内存的数据结构
8088是以字节为存取数据的基本单位.计算机的存储结构是8位的字节,但是CPU本身处理数据则是以16位为单位.在内存中,都遵循一个原则,即:
高高低低的存储方式.高字节对应高地址,低字节对应低地址.
下面是一个简单程序,在AX中放入一个字节的内容并显示:
csegsegment
org100h
assumecs:
cseg,ds:
cseg
start:
movbx,cs
movds,bx
movah,'H'
moval,'L'
movtest,ax
moval,[si];Firstbyteoftest
calldchar
moval,[si+1];Secondbyteoftest
calldchar
ret
;DisplaythecharactercontainedinAL
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 汇编 编写 DOS 内存 驻留 程序