Abbiamo già visto in uno dei tutorial precedenti le istruzioni per effettuare i salti condizionati, qui li riassumeremo brvemente e vedremo alcune altre istruzioni per effettuare cicli e salti incondizionati.
>JMP
Questa istruzione è usata per effettuare salti incondizionati; la sintassi è:
JMP < registro|memoria >
L'operando deve contenere l'indirizzo a cui saltare e a differenza dei salti
condizionati in cui l'indirizzo doveva essere SHORT qui può essere sia NEAR
che FAR.
>J< condizione >
Questi sono i salti condizionati. Non vi riporto l'elenco che ho già scritto
in passato.
Mi limiterò a dare alcune informazioni aggiuntive. La distanza tra
l'istruzione di salto e l'indirizzo a cui saltare deve essere di tipo SHORT
(max 128 bytes).
Quindi per effetture salti condizionati maggiori di 128 byte si deve
riorganizzare il programma, ad esempio:
cmp ax,6 ;salta se ax=6
jne skip ;non salta
jmp distant ;>128 bytes
skip: ...
...
...
distant:...
A volte invece di dare il nome a tutte le etichette si usano le Anonymous
Label per effettuare i salti che si indicano con @@:
cmp cx,20
jge @F
...
...
@@:
L'istruzione jge @F salta alla prossima etichetta anonima.
@@: ...
...
cmp cx,30
je @B
In questo caso invece salta a quella precedente.Oltre all'istruzione cmp si può usare l'istruzione TEST:
TEST < registro|memoria >,< registro|memoria|valore >
Questa serve per fare un confronto sui bit degli operandi:
es.
.DATA
bits DB ?
.CODE
...
...
test bits,10100b ;se i bit 4 o 2 sono a 1
jnz pippo ;salta a pippo
...
...
pippo:
L'istruzione TEST effettua un AND ma senza cambiare gli operandi (solo i flags).
>LOOP , LOOPE, LOOPZ, LOOPNE, LOOPNZ
Le istruzioni LOOPxx sono usate per effettuare un certo numero di cicli e
sono simili alle istruzioni For, While, Do, ... dei linguaggi ad alto
livello.
Il numero di iterazioni viene fissato nel registro CX, che viene decrementato
ogni volta che si arriva all'istruzione LOOPxx fino a quando CX=0, in questo
caso si continua con l'istruzione successiva a LOOPxx.
La sintassi di queste istruzioni è:
LOOPxx < label >
In particolare le istruzioni LOOPE, LOOPZ, LOOPNE, LOOPNZ oltre al controllo
su CX eseguono un controllo sulla condizione :
LOOPE gira mentre è uguale
LOOPNE gira mentre non è uguale
LOOPZ gira mentre è zero
LOOPNZ gira mentre non è zero
-- USARE LE PROCEDURE --
Sapete già tutti cosa è una procedura quindi non starò qui a spiegarvi come
si comporta il programma quando incontra una procedura e cosa succede al
flusso, mi limiterò a spiegarvi la sintassi delle procedure in Assembly e
a darvi qualche altra informazione aggiuntiva.
Per chiamare una procedure dal programma si usa l'istruzione:
CALL nome_procedura
dopo questa istruzione il controllo passa alla procedura chimata che sarà
dichiarata come segue:
nome_procedura PROC < [NEAR|FAR] >
....
....
....
< RET|RETN|RETF >[costante]
ENDP
Dove RETN sta per Return Near, RETF Return Far e la costante è il numero di
byte da aggiungere allo Stack Pointer (per togliere i parametri).
mov ax,10
push ax ;primo parametro
push dato2 ;secondo
push cx ;terzo
call addup ;chiamo la procedura
add sp,6 ;sposto lo stack pointer (equivale a tre pop)
...
...
addup PROC NEAR
push bp ;salva il Base Pointer (occupa 2 byte !!)
mov bp,sp
mov ax,[bp+4] ;preleva il terzo argomento(è il CX di prima)
add ax,[bp+6] ;lo somma al secondo (cx+dato2)
add ax,[bp+8] ;lo somma al primo (cx+dato2+10)
pop bp ;risistema bp
ret
addup ENDP
Per capire cosa avviene nello stack serviamoci di alcuni disegni:
prima di CALL ADDUP dopo CALL ADDUP dopo PUSH BP
|---------| |---------| MOV BP,SP
| arg1 | | arg1 | |---------|
|---------| |---------| | arg1 |<--BP+8
| arg2 | | arg2 | |---------|
|---------| |---------| | arg2 |<--BP+6
| arg3 |<--SP | arg3 | |---------|
|---------| |---------| | arg3 |<--BP+4
| | | ret.add.|<--SP |---------|
|---------| |---------| | ret.add.|
| | | | |---------|
|vecchioBP|<--BP,SP
|---------|
dopo POP BP dopo RET dopo ADD SP,6
|---------| |---------| | |<--SP
| arg1 | | arg1 | |---------|
|---------| |---------| | |
| arg2 | | arg2 | |---------|
|---------| |---------| | |
| arg3 | | arg3 |<--SP |---------|
|---------| |---------| | |
|ret.add. |<--SP | |
|---------| |---------|
| | | |
Da notare che la parte alta dei disegni rappresenta in realtà la parte
bassa della memoria e viceversa. Lo stack infatti cresce dalla parte
alta verso la parte bassa della memoria (vale a dire verso indirizzi
maggiori), ma questa è solo una scelta fatta dai progettisti del
microprocessore arbitrariamente, senza una ragione particolare.
-- USARE GLI INTERRUPTS --
Gli interrupts sono una particolare serie di routines messe a disposizione
dall'hardware e dal sistema operativo.
Essi hanno un numero di "riconoscimento" che va da 0 a 255 e vengono chiamate
nel seguente modo:
INT numero
INTO
Quando viene chiamato un interrupt il processore esegue i seguenti passi:
1. Cerca l'indirizzo della routine nella tavola dei descrittori all'indirizzo 0000:0000 + 4*numero 2. Salva il registro di flag, il CS e l'IP corrente (per poter tornare) 3. Azzera il TF e setta a 1 IF 4. Salta all'indirizzo della routine di int. 5. Esegue la routine fino a quando incontra l'istruzione IRET 6. Ripristina la condizione del processore prima della chiamata estraendo dallo stack IP,CS e i FLAGL'istruzione INTO (interrupt on overflow) è una variante, essa chiama l'int 04h quando OF=1
>STI, CLI
Queste due istruzioni servono rispettivamente per abilitare e disabilitare gli
interrupt hardware. Questo significa che dopo la CLI il programma in
esecuzione non può essere interrotto da un interrupt esterno.
-- DEFINIRE E RIDEFINIRE LE ROUTINE DI INTERRUPT --
Visto che il DOS è un sistema aperto vi permette di sostituire o di
riscrivere le routine di interrupt per i vostri programmi.
La sintassi della routine che scriverete sarà all'incirca così:
label PROC FAR
....
....
....
IRET
label ENDP
Come vedete dovete dichiarare una procedura di tipo far e questa deve
terminare con l'istruzione IRET.
int 21h,35h :
input AH=35h - legge dal vettore di int
AL=numero dell'interrupt
output ES:BX=puntatore alla routine
int 21h,25h :
input AH=25h - imposta la routine di int
AL=numero dell'interrupt
DS:DX=puntatore alla routine
output niente !!
Ora ecco il codice:
Come potete vedere il programma esegue i seguenti passi:
INT.EXE INT.ASM ;Int.asm - by b0nu$, 1997 .MODEL SMALL .DATA messaggio DB "Overflow!! Azzeramento risultato...",'$' old_vector DD ? .STACK .CODE start: STARTUPCODE ;Ve la spiego dopo!!! ; inizio codice di test ----------------------------------------------- mov ax,06FF0h mov bx,04FF0h add ax,bx ;faccio una somma into ;chiamo l'int 4 se c'è overflow ;l'int 4 del DOS non fa nulla ; fine codice di test ------------------------------------------------- mov ax,3504h ;ah=35, al=04 int 21h ;preleva l'int mov WORD PTR old_vector[2],es ;salva l'indirizzo mov WORD PTR old_vector[0],bx ;NB:Le convenzioni Intel ;dicono di salvare prima ;il byte meno signif. ;poi quello più sign. ?? push ds ;salvo il DS mov ax,cs mov ds,ax ;carico l'ind. della nuova mov dx,OFFSET new_overflow ;routine in DS:DX mov ax,2504h int 21h ;setto la nuova routine pop ds ; ... ; ... ; inizio codice di test ----------------------------------------------- mov ax,06FF0h mov bx,04FF0h add ax,bx ;faccio una somma into ;chiamo l'int 4 se c'è overflow ; fine codice di test ------------------------------------------------- ; ... ; ... LDS dx,old_vector ;ricarico la routine originale mov ax,2504h ;e la ripristino int 21h ; inizio codice di test ------------------------------------------------- mov ax,06FF0h mov bx,04FF0h add ax,bx ;faccio una somma into ;chiamo l'int 4 se c'è overflow ;se non succede niente, vuol dire ;che il ripristino della vecchia ;routine è corretto ; fine codice di test --------------------------------------------------- mov ax,4C00h int 21h ;termino il programma new_overflow PROC FAR cli ;disattivo le interruzioni mov ax,SEG messaggio mov ds,ax mov dx,OFFSET messaggio mov ah,09h int 21h ;stampo il messaggio xor ax,ax ;azzero il risultato xor dx,dx sti ;abilita le interruzioni iret new_overflow ENDP END start
1. Salva l'indirizzo del vettore di int
2. Imposta l'indirizzo della nuova routine
3. Fa quello che deve fare ....
4. Ripristina la vecchia routine
In questo esempio tutte le volte che viene chiamato l'int 04h si esegue la
procedura new_overflow.
A proposito del passo 4, l'istruzione:
mov dx,WORD PTR old_vector ;ricarico la routine originale (errore!)non bastarebbe a ripristinare la routine originale; devo anche passare in DS il numero del segmento in cui si trova (che ho salvato nella word superiore di old_vector). Il modo più semplice è usare un'istruzione LDS:
LDS dx,DWORD PTR old_vector(la DWORD PTR è opzionale)
Spesso invece di sostituire totalmente la routine si cerca di estenderne le sue funzionalità, in questi casi all'interno della nuova routine di int dopo aver svolto il compito si mette una chiamata a quella vecchia in modo che vengano eseguite in cascata.
Nota: nel programma ho usato la direttiva STARTUPCODE, serve per inizializzare i segmenti DS, SS eil registro SP. Naturalmente potete sostituirlo con le istruzioni usate negli esempi precedenti. Inoltre questa direttiva va inserita dopo l'etichetta (start in tal caso) che segna il punto d'inizio del nostro prg (di solito si mette subito dopo); se la metto prima di start: il codice corrispondente a STARTUPCODE non viene mai eseguito!!
Abbiamo visto così i metodi per eseguire salti cicli e procedure, strumenti
indispensabli per scrivere del codice efficiente.
Prestate particolare attenzione alla modifica delle routine di interrupt che
riprendermo nel prossimo tutorial riguardante i programmi residenti in
memoria.
Per vi lascio al vostro lavoro.
| Assembly Page di Antonio |
|