PROGRAMMAZIONE IN ASSEMBLY

Programmi residenti in memoria


Questi file sono protetti dal diritto d'autore e sono liberamente
riproducibili a condizione di non farne uso comemrciale o economico o per
fine di lucro senza la preventiva autorizzazione dell'autore. In ogni caso
deve sempre essere indicato il nome dell'autore e il suo indirizzo. Ogni
altra modalita' di utilizzo deve considerarsi contraria alla volonta'
dell'Autore.


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 (inpratica 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 ruotine 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 che rimando ad un altro
tutorial.
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.
Dedicherò comunque un tutorial a questo argomento per ora ci interessano i
programmi TSR.

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 ? ;Puntature 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

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.
;----------------------------------
cmp dl,'b' ;il carattere letto è 'b'
jne EXIT ;se no esci
mov dl,07h ;altrimenti suona
mov ah,02h
int 21h
;----------------------------------

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


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......

Per richieste, consigli, suggerimenti, aiuti, contributi, ecc..., contattatemi

b0nu$,e-mail : bonus@numerica.it

TORNA INDIETRO