- Memorizza l'indirizzo della vecchia routine di interrupt - Rimpiazza la routine di interrupt con una nuova - Chiama l'interrupt 27hMa vediamo subito il programma e poi lo commentiamo:
Come potete vedere la prima operazione svolta dal programma è quella di chiamare la procedura LOAD_PROG.
BEEP.COM BEEP.ASM ;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. Tranne che chiamare un interrupt del DOS!! ;-------------------------------------------------------------------------- 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 ;Procedura 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
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.
Torniamo a descrivere il programma TSR. Dopo aver emesso il suono la
procedura ripristina i registri e rilascia il controllo.
Come vedete non è poi così 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.
Aggiungo inoltre alcune ulteriori piccole spiegazioni:
DOMANDA CHE UN PRINCIPIANTE PUO' PORSI:
nella procedura PROG (vedi Beep.asm) perchè alla PUSHF non corrisponde una POPF ?
Penso che la PUSHF non sia necessaria, perchè nel tutorial:
10) Controllare il flusso del programmaavevi detto che quando viene chiamato un interrupt, i flag vengono automaticamente salvati e ripristinati dal processore, perciò a che serve questa istruzione PUSHF ?
RISPOSTA:
Serve a simulare una chiamata al "vero" interrupt; è vero che l'istruzione INT (INT 9 nel
nostro caso) salva i flag e che una istruzione IRET li recupera, però anche nella routine
originale c'è una IRET, perciò serve la pushf, altrimenti avrò un grave errore sullo stack!
Infatti invece di scrivere così:
pushf call OLD_KEYBOARD_INT ;chiamo la vecchia routine di int.ho preferito non lasciare la riga vuota:
pushf call OLD_KEYBOARD_INT ;chiamo la vecchia routine di int.perchè rende più evidente che quella pushf è una premessa per la call.
ALTRA DOMANDA:
A che serve la direttiva .286c?
RISPOSTA:
la direttiva .286c serve perchè l'808x non ha le istruzioni PUSHA e POPA
(quindi questo programma richiede almeno un 286). Comunque nessuno (o quasi)
ormai ha più l'8086/8, quindi nessun problema.
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.
Note: il byte 3E è escluso (quello a 1E è incluso invece).
Questo buffer di tastiera è un "buffer di accomodamento circolare".
Bene ora che sappiamo dove sono basta andare a prenderli.
Ecco un programma che lo fa...
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.
BEEP2.COM BEEP2.ASM ;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 mov bx,TAIL ;buffer 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!! ;-------------------------------------------------------------------------- cmp dl,'b' ;il carattere letto è 'b' ? jne EXIT ;se no esci ;altrimenti suona ;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 popa iret PROG ENDP LOAD_PROG PROC ;Procedura 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 STORIA E I PROBLEMI DI QUESTO PROGRAMMA
Il programma riportato è corretto: provate ad eseguirlo e funziona correttamente
(anche sotto la shell di Windows). Nella prima versione di questo prg. che avevo
scritto, avevo commesso un paio di gravi errori che descrivo con queste note,
perchè penso sia istruttivo:
"Ho provato ad eseguire Beep2 e va in crash; non riuscivo a spiegarmelo, poi tracciandolo istruzione per istruzione ho capito che il problema era che nè la chiamata INT, nè la PUSHA salvavano il registro di segmento DS; questo viene modificato dalla routine di sostituzione dell'interrupt facendolo puntare al segmento del BIOS RAM tramite queste due istruzioni:
mov bx,ROM_BIOS_DATA mov ds,bxe quando poi si ritorna, il sistema va in crash.
push dsprima della mov bx,ROM_BIOS_DATA
pop dsprima della popa (in corrispondenza dell'etichetta EXIT)"
Capite adesso come tutto deve essere preciso? Basta dimenticarsi di una piccola cosa e non funziona più niente. E per scovare gli errori, a volte ci vuole un sacco di tempo!
All'inizio avevo deciso di far emettere un lungo BEEP quando veniva premuta la lettera b e per fare questo avevo semplicemente usato queste istruzioni:
;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 ;----------------------------------MA IL PRG. BEEP2.ASM HA UN ALTRO PROBLEMA: quando premo b il sistema va in crash, subito dopo aver fatto beep. Su un vecchio libro di Norton ho trovato scritto questo, a proposito dei prg residenti in memoria che si incatenano ad un interrupt:
"la tecnica presentata in questa sezione (la stessa illustrata da noi) funziona con la maggior parte delle routine del BIOS. Dato che il DOS non è un sistema operativo multitasking, non potete fare delle chiamate a funzioni DOS con un interrupt a meno che non siate assolutamente sicuri che il DOS non sia a metà di un'operazione. Ci sono alcuni modi per assicurarsi di questo, ma sono abbastanza difficoltosi, e non saranno spiegati in questo libro"Dopo questo ho capito che il problema è quella chiamata INT 21 all'interno di PROG.
Infatti se tolgo INT 21, il programma non fa più niente, però non provoca il crash. Quindi quando "riprogrammo" un interrupt, purtroppo non posso chiamare altri interrupt del DOS, pena il crash del sistema (quelli del BIOS invece sì); il libro dice che c'è un modo per porvi rimedio, ma non dice quale e io non lo conosco.
Percui ho dovuto effettuare un beep programmando il timer come avevavamo fatto in BEEP.ASM e ho dovuto cambiare il commento:
;si può fare qualunque cosacon
;si può fare qualunque cosa. Tranne che chiamare un interrupt del DOS!!
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......
Ad esempio ho immaginato una semplice ma originale variante di BEEP2.ASM che
sotto DOS, quando uno preme INVIO, stampa il carattere ascii 27 sulla riga di
comando. Vedi appunto il file INVIO.ASM e INVIO.COM allegati.
Per fare ciò infatti uso l'interrupt 10 del BIOS e quindi non ci sono problemi.
Assembly Page di Antonio |
|