Programmi TSR


Trattiamo oggi un argomento non troppo facile ma interessante: i programmi TSR,
Terminate but Stay Resident, si tratta di quei programmi che rimangono in 
memoria e si attivano solo in determinate situazioni(di solito al verificarsi
di particolari interrupt).
Analizzeremo  in particolare un programmino che a ogni pressione di un tasto 
emette un beep.
Per la realizzazione di un programma TSR serve conoscere l'int 27h che ha per
parametri l'indirizzo dell'ultimo byte del programma più uno e il DS del
programma.
Il procedimento e il seguente :
        - Memorizza l'indirizzo della vecchia routine di interrupt
        - Rimpiazza la routine di interrupt con una nuova 
        - Chiama l'interrupt 27h

Ma vediamo subito il programma e poi lo commentiamo:

;Beep.asm - by b0nu$, 1997

.286c
.MODEL  SMALL

INTERRUPT_NUM   EQU             9	;Interrupt da intercettare

                .CODE
                ORG             100H
FIRST:          JMP             LOAD_PROG	;Carico in memoria il prg.
                OLD_KEYBOARD_INT DD     ?	;memorizza l'indirizzo al 
                 				;vecchio vettore di int.
                
PROG            PROC
                pusha		;salvo i registri
                pushf	
	
                call OLD_KEYBOARD_INT	;chiamo la vecchia routine di int.

;QUI CI VA IL PROGRAMMA: In questo esempio ho deciso di emettere un BEEP ma 
;si può fare qualunque cosa.
;----------------------------------
	in	al,61h	;Per il BEEP programmo il Timer
	test	al,3
	jne 	skippa		
	or	al,3
	out	61h,al
	mov	al,0B6h
	out	43h,al
	
skippa:	mov	al,06h	;frequenza LSB
	out	42h,al
	mov	al,01h	;frequenza MSB
	out	42h,al
	
	mov	cx,0FFFFh
wait_loop:
	loop	wait_loop	;ciclo di attesa
	
	in	al,61h	;silenzio
	and 	al,0FCh
	out	061h,al
;----------------------------------		
		
EXIT:          
                popa                
                iret
PROG            ENDP

LOAD_PROG       PROC			;Precedura che carica in memoria il prg.
                mov     ah,35h                
                mov     al,INTERRUPT_NUM
                int     21h		;Prelevo il vecchio vettore 
                mov     WORD PTR OLD_KEYBOARD_INT,bx               
                mov     WORD PTR OLD_KEYBOARD_INT[2],es
		
                mov	al,INTERRUPT_NUM
                mov     ah,25h
                lea     dx,PROG 
                int     21h		;Imposto quello nuovo
		
                mov     dx,OFFSET LOAD_PROG	;in DX ci va l'ultimo byte del
						;prg. + 1
                int     27h		;Termina ma rimani in memoria
LOAD_PROG       ENDP
                END     FIRST

Come potete vedere la prima operazione svolta dal programma è quella di
chiamare la procedura LOAD_PROG.
Questa memorizza il vecchio vettore di interrupt e imposta quello nuovo, infine
chiama l'int 27h per rimanere residente in memoria.
In questo modo tutte le volte che viene generato un int 09h (in pratica tutte le 
volte che viene premuto un tasto)verrà eseguita la procedura PROG scritta da 
noi.
Essa come prima cosa salva il valore dei registri (cosa da fare sempre in questi
casi), poi chiama la vecchia routine di int; in questo caso ci serve per
visualizzare il carattere relativo al tasto premuto in pratica a questo livello
lasciamo tutto come era prima.
Il programma vero e proprio arriva subito dopo e nel nostro caso emette un BEEP
dallo speaker e lo fa andando a programmare direttamente il timer.

Il timer consiste in un dispositivo che puo lavorare in diverse modalità
a seconda dei valori che immetto nella porta 43h.
Non sto qui a spiegarvi tutti i dettagli del timer, vi dico solo nel programma 
attivo il timer tramite la porta 61h,immetto nella porta 43h la modalità di 
funzionamento (Square Wave Generator) e nella porta 42h
la frequenza del suono sotto forma di due byte, prima quello meno significativo
poi quello più significativo, infine spengo tutto tramite la porta 61h.

Dopo aver emesso il suono la procedura ripristina i registri e rilascia il
controllo.
Come vedete non è poi cosi difficile e i passi per la realizzazione sono
abbastanza standard.
In questo modo la parte residente è solo la procedura PROG tutto il resto viene
scaricato dopo l'int 27h.
Naturalmente la parte residente deve stare nei 64Kb di un segmento e cosi pure
il programma deve essere un file .COM.

Questo programma non è però tanto utile se non a livello di folklore.
Infatti sarebbe più interessante sapere quale tasto è stato premuto per poter
intercettare una particolare combinazione di tasti.
Per far ciò devo spendere due parole per dirvi dove vengono memorizzati i tasti
premuti.
Una parte del BIOS viene memorizzato in RAM a partire dall'indirizzo 400h fino
all'indirizzo 4FFh; in quest'area sono memorizzate numerose informazioni
riguardanti l'hardware del PC come gli indirizzi delle porte seriali e parallele
il numero di dischi il tipo di computer la modalit… video ecc... tra le tante
cose all'indirizzo 41Ah (0040:001A) c'è un puntatore (2 byte) alla testa del
buffer dei caratteri arrivati dalla tastiera, subito dopo (41Ch) un puntatore
alla coda dello stesso buffer e all'indirizzo  41Eh c'è il buffer circolare
composto da 32 bytes (0040:001E --> 0040:003E) che contiene i codici ASCII e gli
SCAN CODE dei tasti premuti.
Bene ora che sappiamo dove sono basta andare a prenderli.
Ecco un programma che lo fa...

;Beep2.asm -  by b0nu$, 1997
.286c
.MODEL  SMALL

INTERRUPT_NUM   EQU             9	;Interrupt da intercettare
                

ROM_BIOS_DATA   SEGMENT AT      40H		;Questi sono dati memorizzati
                ORG             1AH		;nel BIOS all'ind. 0040:001A
                HEAD            DW      ?	;Puntatore alla testa del buffer
                TAIL            DW      ?	;Puntatore alla coda del buffer
                BUFF            DW      16 DUP(?);Buffer
                BUFF_END        LABEL   WORD
ROM_BIOS_DATA   ENDS

                .CODE
                ORG             100H
FIRST:          JMP             LOAD_PROG	;Carico in memoria il prg.
                OLD_KEYBOARD_INT DD     ?	;memorizza l'indirizzo al 
						;vecchio vettore di int.
                

PROG            PROC
                pusha		;salvo i registri
                pushf	
		
                call OLD_KEYBOARD_INT	;chiamo la vecchia routine di int.

                ASSUME  ds:ROM_BIOS_DATA
		push	ds
                mov     bx,ROM_BIOS_DATA	;Questo gruppo di istruzioni
                mov     ds,bx			;mi serve per gestire il buffer
                mov     bx,TAIL			;dei caratteri letti
                cmp     bx,HEAD
                je      EXIT			;Non ci sono caratteri
                sub     bx,2                    ;si sposta di due bytes
                cmp     bx,OFFSET BUFF          ;controlla di non uscire
                jae     NO_WRAP
                mov     bx,OFFSET BUFF_END
                sub     bx,2			;BX punta al carattere
NO_WRAP:        mov     dx,[bx]			;in DL c'è il carattere letto


;QUI CI VA IL PROGRAMMA: In questo esempio ho deciso di emettere un BEEP ma 
;si può fare qualunque cosa.
; tranne che chiamare un interrupt del DOS!!
;----------------------------------
                ;routine di beep col timer vista prima
        in	al,61h	;Per il BEEP programmo il Timer
	test	al,3
	jne 	skippa		
	or	al,3
	out	61h,al
	mov	al,0B6h
	out	43h,al
	
skippa:	mov	al,06h	;frequenza LSB
	out	42h,al
	mov	al,01h	;frequenza MSB
	out	42h,al
	
	mov	cx,0FFFFh
wait_loop:
	loop	wait_loop	;ciclo di attesa
	
	in	al,61h	;silenzio
	and 	al,0FCh
	out	061h,al
;----------------------------------		
		
		
EXIT:           pop 	ds
		popf
                popa                
                iret
PROG            ENDP

LOAD_PROG       PROC		;Precedura che carica in memoria il prg.
                mov     ah,35h                
                mov     al,INTERRUPT_NUM
                int     21h			;Prelevo il vecchio vettore 
                mov     WORD PTR OLD_KEYBOARD_INT,bx               
                mov     WORD PTR OLD_KEYBOARD_INT[2],es
		
                mov	al,INTERRUPT_NUM
                mov     ah,25h
                lea     dx,PROG 
                int     21h			;Imposto quello nuovo
		
                mov     dx,OFFSET LOAD_PROG	;in DX ci va l'ultimo byte del
                                                ;prg.+1
                int     27h			;Termina ma rimani in memoria
LOAD_PROG       ENDP
                END     FIRST


La variabile ROM_BIOS_DATA memorizza i due puntatori e il buffer e le istruzioni
aggiunte dopo la chiamata al vecchio int si occupano di prelevare il codice
ASCII del tasto premuto.
Alla fine di quelle istruzioni avremo in DH lo SCAN CODE e in DL il codice ASCII
e possiamo confrontare il carattere letto con quello da intercettare e fare
quello che vogliamo.
Nell'esempio si controlla se e stata premuta le lettera b e in tal caso di
emette un lungo BEEP.

OK spero che sia tutto chiaro so che non è facilissimo ma provate a scrivere
qualcosa magari per cominciare modificate uno dei due programmi in modo che
intercettino altri tasti o che facciano qualcos'altro.
L'argomento è abbastanza complicato e richiede un codice molto pulito per non
andare ad interferire con altri programmi residenti o altri driver, ma non
scoraggiatevi e continuate......

** [email protected] **