緒言
0.1 內存駐留與中斷
內存駐留程序英文叫Terminate and Stay Resident Program,縮寫為TSR.這些程序加載進內存,執行完後,就駐留在內存裡,當滿足條件時,調到前台來執行。
內存駐留程序的常用形式有:
>諸如Borland 的SideKick彈出式實用程序
>日歷系統
>網絡服務器
>通訊程序
>本地的DOS擴展(如CCDOS,UCDOS等中文系統都屬於這個范疇)
>一些可惡的人利用TSR技術制作很多可惡的病毒程序,幾乎所有的病毒程序都是TSR程序.
就象多任務系統調度一個進程有一個調度程序一樣,在PC中從前台程序進入到一個TSR,也要有一個調度者,只是PC操作系統的調度不稱為調度程序,而只稱為觸發機制.觸發機制調度TSR執行在PC機上黨稱為激活一個TSR.觸發機制主要有以下幾種:
>硬件中斷:黨用的是鍵盤中斷INT 9H,時鐘中斷INT 8H,通訊中斷INT 14H,磁盤中斷INT 13H等等.
>軟件中斷:黨用的是鍵盤中斷INT 16H,時鐘中斷INT 1CH,DOS中斷INT 21H,等等.
>以上各種的結合.
從以上的觸發機制可以看出,TSR和PC機的中斷系統有著密切的關系.每種激活方式實際上都是與中斷有關的.常用特殊的擊鍵序列的識別碼是通過截獲INT 9H和INT 16H來實現.實際上不管TSR程序的哪一個環節,都與中斷有著密切的關系.因此在具體進行TSR和程序設計之前,先介紹PC中斷系統.在此只作簡單說明.
在PC機內存的最低端(0000H開始)的1K字節中,存放著256個指針即常說的中為向量或中斷矢量(Interrupt vertor),每個中斷向量都指向一個子程序,該程序稱為中斷處理程序(Interrup handler).一個中斷向量由四個字節組成,有一個字是中斷處理程序的偏移量值,後一個字是中斷處理程序的段值.256中斷向量一起稱為中斷向量表.
手式計算中斷向量的首址,可通過以下的公式來求得:
X號中斷向量的首址=0000H:X*4
當產生一個中斷時,處理器都按順序執行以下步驟:
>在堆棧上壓入處理器的標志(相當於指令PUSHF).
>在堆棧上壓入當前CS和IP值(相當於指令PUSH CS和PUSH IP).
>關閉中斷(CLI)
>從中斷向量加載的CS和IP,執行中斷處理程序.
當執行完中斷處理程序後,一般用IRET返回,它的作用是:
>從堆棧上取出保存的IP和CS(相當於指令POP CS和PUSH CS).
>同時恢復中斷前的處理器標志(相當於指令POPF).
中斷有多種分類,由觸發的原因和實現的性質來分,可分為硬件中斷和軟件中斷,從操作系統分層實現來說,可以分成BIOS中斷,BOS中斷和用戶中斷.
一方面,BIOS和DOS通過中斷系統向用戶提供一個操作系統功能界面.也就是說用戶(一般來說是前台程序)的功能主要是通過調用DOS和BIOS的中斷服務來實現的,具體來說就是通過INT指令來實現的.另一方面,BIOS和DOS由中斷系統所構成,BIOS對硬件成為高層的功能,並通過中斷的形式向用戶提供.
如果在當前程序執行的同時,能將一塊代碼放在內存,把中斷向量指向代碼中的子程序,那麼在當前程序執行中產生中斷時,就有可能執行不屬於當前程序和操作系統的代碼,產生的中斷可能是當前程序產生的軟件中斷,也可能是由硬件產生的硬件中斷.這就是單任務的PC操作系統可能執行多於一個進程的簡單說明.
在PC中斷系統中有幾個中斷具有周期性,即INT 8H,INT 1CH和INT 28H.它們或者周期性被執行用於時間計時,或者周期性產生用於等待.它們是在實現TSR時進行輪詢觸發的基礎.鍵盤中斷(INT 9H和INT 16H)當用戶擊鍵時發生,利用它們是進行熱鍵處理的基礎.串行口通訊也是觸發的一個重要機制.此外眾多的軟件中斷也是觸發的媒介.
0.2 DOS的可重入性分析
一個多任務操作系統之所以能使多個進行並存,是因為操作系統的大部分代碼是可以了重的,對於臨界資源有相應的PV操作,使得當調度一個新的進程時,能完整地保存前一個裡程的現場,當再一次調度被掛起的進程時能象沒有被中斷一樣繼續執行.
對於PC機來說,代碼的重入性比較弱,對臨界資源沒有PC操作.當我們用中斷程序啟動用戶的TSR時,如果只保存標志和寄存器,以及當前進程一些信息,那麼只保存了當前程序的一部分現場,DOS的臨界資源不會自動保存.在進行TSR設計時,一定要了解PC操作系統的重入性和臨界資源.
重入性總是體現在代碼上,所謂可重入代碼的指這樣的代碼,即該代碼被執行時還沒有從中退出,由於某種原因又一次或者多次進入相同的代碼,該代碼每次的執行結果都是正確的,就說該代碼是可重入的.相反,如果結果不正確,那麼就就該代碼是不可重入的.下面是一個可重入的子程序的例子:
Add proc near
cmp DS:word ptr [si],0
je DonotAddTheValue
add ax,DS:word ptr [si]
DonotAddTheValue:
ret
Add endp
上面的例子不管在其中任何一處再一次執行該子程序,執行結果不變.為了說明,只舉多種可能性中的一種.
mov ds,0100h ;ds=0100h
mov si,0010h ;si=0010h
mov ax,0001h ;ax,=0001h
call Add
cmp 0100h:word ptr [0010h],0 ;Call Add subroutine
push ds ;Interrupted
push si
push ax
mov ds,0200h ;ds=0200h
mov si,0200h ;si=0020h
mov ax,0003h ;ax=0003h
call Add
cmp 0200h:word ptr [0020h],0 ;0200:0020h=0004h
jne
add ax,0200h:word ptr [0020h] ;ax=0007h
ret ;Return
pop ax ;ax=0001h
pop si ;si=0010h
pop ds ;ds=0100h
iret ;Return to Add subroutine
jne
add ax,0100h:word ptr [0100h] ;ax= 0001h
;0100h:0010h= 0002h
;----------------------------------------
;ax = 0003h
ret
mov bx,ax
而下面的子程序是不可重入的:
Add proc near
mov Temp,ax
mov ax,DS:word ptr [si]
cmp ax,0
je DonotTheValue
add ax,Temp
DonotTheValue:
ret
Temp:
dw 0
Add endp
可以利用檢查可重入子程序的方法檢查這個子程序的不可重入性,嘗試一下在" mov ax,DS:word ptr [si]"指令後再次執行該子程序,那麼就會出第一次調用返回的結果不對.
mov ds,0100h ;ds=0100h
mov si,0010h ;si=0010h
mov ax,0001h ;ax,=0001h
call Add
mov Temp,ax ;Call Add subroutine
;Temp=0001h
mov ax,0100h:word ptr [0010h] ;0100h:0010h=0002h
;ax=2
push ds ;Interrupted
push si
push ax
mov ds,0200h ;ds=0200h
mov si,0020h ;si=0020h
mov ax,0003h ;ax=0003h
call Add
mov Temp,ax ;Temp=0003h
mov ax,0200h:word ptr [0020h] ;0200h:0020h=0004h
cmp ax,0 ;ax=0004h
jne ;Not equal ,add
add ax,Temp ;ax=0007h
ret ;Return to the interrupted point
pop ax ;ax=0002h
pop si ;si=0010h
pop ds ;ds=0100h
iret ;Return to Add subroutine
cmp ax,0 ;ax=2
jne ;No equal,add
add ax,Temp ;ax =0002h
;0100h:0010h =0003h
;----------------------------------------
;ax =0005h
ret
mov bx,ax
上面執行的結果是AX=5,實上正確的結果應該是AX=3,這是由於當Add子程序從中斷子程序再一次被調用時,修改了Temp的值,當從中斷返回時不能正確恢復其值.
解決的方法是把Temp放在堆棧中,當每次Add子程序被調用時Temp的地址都不一樣,因此原調用的Temp值不會被第二次在中斷中調用的Add所破壞.
Add proc near
push bp ;Store BP
sub sp,2 ;distribute a byte space in the stack
mov bp,sp ;SS:BP point to the stack head
temp equ SS:word ptr [BP+0] ;Explain the pointer to SS:BP
mov Temp,ax
mov ax,DS:word ptr [si]
cmp ax,0
je DonotAddTheValue
add ax,Temp
DonotAddTheValue:
add sp,2 ;Release the dsitributed space in the stack
pop bp ;Restore BP
ret
Add endp
對於DOS來說,DOS的內存數據就象Temp變量,它被分配在數據區,而不在堆棧上,因此DOS從總體上是不可重入的.從最後的一個例子看來.重入性跟堆棧有很大的關系.可重入代碼允許在任何時候被中斷,其所有的變量都存放在該代碼的私有堆棧中.DOS是一個單任務的操作系統,在執行INT 21H的代碼時是不允許中斷DOS,並再次調用INT 21H的.每個時該最多有一個進程在調用DOS的代碼.
對DOS的重入性,以及相應所作的處理總結如下:
>當通過INT 21H調用DOS時,DOS會使三個內部棧之一:I/O棧,磁盤棧和輔助棧.功能00H到處0CH使用I/O棧,除了不致命錯誤處理程 序以外使用磁盤棧,致命錯誤處理程序使用輔助棧.在這種棧切換模式下,如果前台處在INT 22H中,而TSR調用了使用相同棧的DOS功能, 就會使前台程序保存棧中的數據被TSR的數據覆蓋掉;但如果調用不同棧的DOS功能,那將是安全的.INT 21H中的幾個功能調即33H,50H, 51H,62H,和64H由於非常簡單,使用用戶棧,因此在任何情況下都是可重入的.避免這種不可重入的簡單方法是當前台程序正處在INT 21H 中時,不要調用INT 21H.或者如果前台程序正在處理INT 21H時,只允許調用不同棧的INT 21H功能.
>DOS數據區中有一個InDOS標志,也探源為DOS安全標志,表示當前訪問DOS功能是來否安全.由於DOS不可重入,它指示當前是 否處於DOS中,激活TSR和代碼可檢查該標志(34H),如果DOS忙,則不能激活使用INT 21H 調用的TSR.
>當前台程序執行能設置錯誤狀態的DOS功能時,DOS會把擴展錯誤信息存放起來,正常情況下,前台程序可以讀取擴展錯誤信息; 如果在前台程序讀取信息之前激活TSR,且TSR也執行能報告錯誤信息的DOS功能,則後來的錯誤信息會覆蓋原來的錯誤信息,前台程序就 會得不到正確的錯誤信息.因此必須在激活TSR之前保存(59H)這些錯誤信息,並在退出以前把它們恢復(5D0AH)成原來的值.
>大多硬件中斷如INT 13H,INT 0BH和INT 0CH等都是不可重往返.如果設置一引起寄存器,而在此時被TSR打斷,執行類似的設置 ,就會出現非常情況,端口是不會自動保持值的.在進入這些中斷時設置一個進入的標志,如果TSR檢查到標志已置,則不調用相應的中斷.
>最好也不要重入INT 10H,INT 25H,和INT 26H中斷.在進入這些中斷時設置一個進入的標志,如果TSR檢查到標志已置,則不調用 相應的中斷.
>最好能接管INT 1BH,INT 23H和INT 24H中斷.
>保存DOS的數據交換區(SDA)可以安全地使用的DOS的功能.SDA保存了DOS幾乎所有內部數據,如果保存(5D06H)和恢復(5D0BH)SDA ,DOS就變成在任何時候都可以重入的了.當DOS處在關鍵區中時,調用INT 2AH.一旦處在關鍵區中,就不能改變SDA.在關鍵區的結束處會 調用INT21H的81H和82H功能.
0.3 內存駐留程序設計一般過程
駐留程序分成兩個部分,即暫駐部分和駐留部分.駐留程序要完成安裝檢測,激活和刪除等過程.
基本上可抽象成以下幾個過程:
>取中斷向量
>保存舊的中斷向量
>設置或恢復中斷向量
>中斷處理程序的鏈接
>檢測是來呀已駐留
>執行終止並駐留
>TSR的刪除
刪除TSR比較復雜,必須按下列步驟進行:
>檢查中斷向量是否已經被替換.如果沒有替換,就恢復所有的中斷向量;如果某個中斷向量被替換,則跳過下面各步,不能刪除該 TSR.
>TSR的PSP中偏移量16H存放著父進程的PSP.把這個值改為當前進程的地址.
>把當前PSP設為TSR的PSP
>執行INT 21H的4CH功能,釋放TSR占用的內存,關閉所有文件,並使用PSP中存放的父進程地址和終止地址.
>這裡控制返回到初始進程中,當前PSP也指向初始進程,所有寄存器值包括SS和SP都不確定.
在執行完上述步驟後,要恢復寄存器.
如果要無條件地刪除TSR,必須監控每個TSR對中斷向量表,內存控制塊和設備驅動程序鏈的修改.
0.5 縮寫語表
ASCIZ: 以零結束的ASCII字符串.
BPD: "BIOS Parameter Block (BIOS 參數塊)"的縮寫.含有對驅動器的低級參數的說明.
CDS: "Current Directory Structure(當前目錄結構)"的縮寫,含有某個邏輯驅動器的當前目錄,類型和其它信息.
DPB: "DOS Drive Parameter Block(DOS驅動器參數塊)"的縮寫,含有某個邏輯驅動器的介質說明及一些內部信息.
DPL: "DOS Parameter List (DOS參數表)"的縮寫,該數據結構用來傳遞參數給SHARE和網絡功能調用.
DTA: "Disk Transfer Address(磁盤傳輸地址)"的縮寫,指示對磁盤進行數據讀寫的功能調用不必顯式地給出緩沖區地址.
FAT: "File Allocation Table(文件分配表)"的縮寫,磁盤的文件分配表記錄了所使用的簇信息.
FCB: "File Control Block(文件控制塊)"的縮寫,在DOS的1.X版本中,用FCB來記錄文件打開的狀態..
IFS: "Installable File System(可安裝的文件系統)"的縮寫,它允許一個非DOS格式的介質被DOS所使用. 大多數情況下IFS 與網絡驅動器非常相似,盡管IFS最典型的情況是一個本地驅動器而不是一個遠程驅動器.
JFT: "Job File Table(工作文件表)或Open File Table(打開文件表)"的縮寫,程序PSP中的JFT可用來將文件句柄轉換成SFT值.
NCB: "Network control Block(網絡控制塊)"的縮寫.NCB可用傳遞對NETBIOS的請求和接受來自NETBIOS處理程序的狀態信息.
PSP: "Porgram Segment Prefix(程序段前綴)"的縮寫.當程序被裝入時,PSP為一個預留的256字節的數據區它包含了程序調用時 的命令行內容和一些DOS的內部信息.
SDA: "DOS Swappable Data Area (DOS對換數據區)"的縮寫.SDA中包含有DOS內部使用的記錄某個正在處理的功能調用狀態的 所有變量.
SFT: "System File Table(系統文件表)"的縮寫,SFT是一個DOS內部數據結構,在DOS 2+版本的句柄功能調用中用於管理某個已打 開文件的狀態,這就和在DOS1.X中,FCB管理已打開文件狀態一樣.