|
Programmi residenti in memoria
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 ;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......
|