Zoom Icon

Controllo del Flusso del Programma

From UIC

Controllare il Flusso del Programma

Contents


Infos
Author: b0nu$
Email: bonus@numerica.it
Website:
Date: 01/01/2001 (dd/mm/yyyy)
Level: No brain needed
Language: Italian Image:Flag_Italian.gif
Comments:



Introduzione

Abbiamo già visto in uno dei tutorial precedenti le istruzioni per effettuare i salti condizionati, qui li riassumeremo brevemente e vedremo alcune altre istruzioni per effettuare cicli e salti incondizionati.


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.


Salti Condizionati

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.

L'Istruzione TEST

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:


I Cicli

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 :

IstruzioneCondizione
LOOPEgira mentre è uguale
LOOPNEgira mentre non è uguale
LOOPZgira mentre è zero
LOOPNZgira 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 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:

StepDescrizione
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:

InterruptInputOutput
int 21h,35h AH = 35h -> preleva il vettore di int ES:BX=puntatore alla rountine
AL = numero dell'interrupt da prelevare
int 21h,25h AH = 25h -> imposta la routine di int niente!
AL = numero dell'interrupt
DS:DX = puntatore alla rountine

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:

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


Note Finali

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 ora vi lascio al vostro lavoro.


Disclaimer

I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.

Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevoli e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.