Controllare il flusso del programma


             
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'istruzuione jge @F salta alla prossima etichetta anonima.

@@:     ...
        ...
        cmp     cx,30
        je      @B

In questo caso invece salta a quella precedente.
Sia in un caso che nell'altro salta a quella più vicina.
questo metodo è comodo per non dover inventare tanti nomi per le etichette.

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:

>LOOP , LOOPE, LOOPZ, LOOPNE, LOOPNZ
Le istruzioni LOOPxx sono usate per effettuare un certo unmero 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 è iol numero di
byte da aggiungere allo Stack Pointer (per i parametri).
Il passaggio degli argomenti alle procedure avviene tramite lo stack oppure
tramite uno dei registri della CPU. ( il più usato è lo stack )
Vediamo un esempio:

        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
           |  arg3   |           |  arg3   |           |---------|
           |---------|           |---------|           |  arg1   |<--BP+8
           |  arg2   |           |  arg2   |           |---------| 
           |---------|           |---------|           |  arg2   |<--BP+6
           |  arg1   |<--SP      |  arg1   |           |---------|
           |---------|           |---------|           |  arg3   |<--BP+4
           |         |           | ret.add.|<--SP      |---------|
           |---------|           |---------|           | ret.add.|
           |         |           |         |           |---------|
                                                       |vecchioBP|<--BP,SP
                                                       |---------|




           dopo POP BP            dopo RET             dopo ADD SP,6
           |---------|           |---------|           |         |<--SP
           |  arg3   |           |  arg3   |           |---------|
           |---------|           |---------|           |         |
           |  arg2   |           |  arg2   |           |---------|
           |---------|           |---------|           |         |
           |  arg1   |           |  arg1   |<--SP      |---------|
           |---------|           |---------|           |         |
           |ret.add. |<--SP      |         |           
           |---------|           |---------|           
           |         |           |         |           
                                                       
                                                       

Spero che ora abbiate chiaro il funzionamento dello stack !!!
In realtà la direttiva PROC permette di specificare i parametri da passare 
alla procedura in accordo con il linguaggio dichiarato nella direttiva .MODEL ma per
ora accontentiamoci di usare quanto detto sopra, poi in uno dei prossimi
tutorial riprenderò meglio il discorso delle direttive messe a disposizione 
dai nuovi Assemblatori.



-- USARE GLI INTERRUPTS --

Gli interrupts sono una particoalre serie di rountine messe a disposizione
dall'hardware e dal sistema operativo.
Essi hanno un nuero di "riconoscimento" che va da 0 a 255 e vengono chiamate 
nel segunte 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 rountine di int.
5. Esegue la rountine fino a quando incontra l'istruzione IRET
6. Ripristina la condizione del processore prima della chiamata estraendo 
   dallo stack IP,CS e i FLAG

L'istruzione INTO (interrupt on overflow) è una variante, essa chiama l'int 
04h quando OF=1


>STI, CLI
Queste due istruzioni servono rispettivamente per abilitre 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 rountine 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.
Il vostro programma deve sostituire l'indirizzo nel vettore di interrupt con
l'indirizzo della vostra routine (tramite opportune chiamte al DOS) dopo aver
salvato quello vecchio, in questo modo tutte le volte che si verifica un int
su quello da voi modificato il programma invece di seguire la consueta 
procedura eseguirà la vostra routine.
Per capire meglio serviamoci come sempre di un esempio, scriviamo un 
frammento di programma che modifica la rountine dell'int 04h (overflow).
Le funzioni del DOS che ci servono per prelevare e per settare l'int sono la 
35h e la 25h rispettivamente:

        int 21h,35h :
                     input  AH=35h - preleva il vettore di int
                            AL=numero dell'interrupt da prelevare
                     output ES:BX=puntatore alla rountine

        int 21h,25  :
                     input  AH=25h - imposta la routine di int
                            AL=numero dell'interrupt
                            DS:DX=puntatore alla rountine
                     output niente !!

Ora ecco il codice:

;Int4.asm - by b0nu$, 1997

.MODEL SMALL
.DATA
messaggio       DB      "Overflow!! Azzeramento risultato...",'$'
old_vector      DD      ?

.CODE
                STARTUPCODE            ;Ve la spiego dopo!!!
start:          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
                ...
                ...
                add     ax,bx           ;faccio una somma
                into                    ;chiamo l'int 4 se c'è overflow
                ...
                ...
                mov     dx,WORD PTR old_vector ;ricarico la routine originale
                mov     ax,2504h               ;e la ripristino
                int     21h

                mov     ax,4C00h
                int     21h             ;termino il programma

new_overflow    PROC    FAR
                sti                     ;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
                iret
new_overflow    ENDP
                END     start

Come potete vedere il programma esegue i seguenti passi:
1. Salva l'indirizzo del vettore di int
2. Imposta l'indirizzo della nuova routine
3. Fa quello che deve fare ....
4. Ripristina la vecchai routine
In questo esempio tutte le volte che viene chiamato l'int 04h si esegue la
procedura new_overflow.
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.

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.


** [email protected] **