Assembly V

[La logica di presentazione degli argomenti di questo modulo si ispira a Linguaggio Assembly per pc IBM di Peter Norton]

Istruzioni call - ret

Se volessimo stampare un carattere per un tot numero di volte potremo scrivere per quel numero di volte la procedura di stapa o utilzzare una iterazione. Proviamo quest'ultima soluzione.



itera:




stampa:

mov dl, 41
mov cx, 000Ah

call stampa
loop itera
int 20
...

mov ah,02
int 21
ret
; muoviamo il carattere 41h [ascii A] nel registro DL
; muoviamo 10 esadecimale nel registro contatore CX

; richiama la rotine stampa [la parola stampa e' una etichetta]
; torna a itera e scala da 1 da CX
; per CX=0: fine programma
;

; procedura di stampa

; ritorna all'istruzione loop

Ecco il risultato di questo programma: AAAAAAAAAA. Abbiamo sostituito i numeri delle righe, indicati fino a questo momento con il termine etichette, con dei nomi: itera e stampa. Per provare con debug si puo' continuare a fare uso dei delle locazioni di memoria rappresentate dalle righe a sinistra della schermata di debug.
Esaminiamo il programma: call stampa, sposta il controllo del programma alla routine chiamata stampa. Qui viene stampato il carattere del registro DL. Poi attraverso la istruzione ret il controllo del programma torna all'istruzione successiva a quella di chiamata della routine stampa. L'istruzione successiva e' loop itera, ovvero torna ad itera e scala 1 dal registro contatore CX. Itera e' l'istruzione call che rinvia per una seconda volta alla rotine stampa. Tutto questo si ripete per 10 volte, ovvero fino a quando CX=0. Quando CX=0 si esce dal loop per realizzare l'istruzione successiva Int 20, ovvero ritorno a DOS; il programma e' finito.

Lo stack

L'istruzione ret necessita di un indirizzo a cui tornare dopo l'esecuzione della chiamata call. L'indirizzo call viene quindi inserito in una speciale area di memoria chiamata stack. Lo stack che vedremo essere utilizzato per moltissime funzioni, memorizza indirizzi di memoria seguendo una procedura cosidetta LIFO: Last In First Out [l'ultimo ad entrare e' il primo ad uscire]. Lo stack si comporta come il processo di sedimentazione della terra: ogni strato si sovrappone a quello precedente e se qualcuno preleva la terra raccoglie prima quella sedimentata per ultima. Allo stesso modo, lo stack sedimenta indirizzi di memoria [inviati secondo certe regole con determinate istruzioni] e il primo inirizzo che si preleva e' quello entrato per ultimo.
La gestione del contenuto dello stack e' controllato da due registri: SP [Stack Pointer] e SS [Stack Segment].
Nel programma appena scritto, l'indirizzo di call era l'ultimo ad essere entrato nella pila di indirizzi dello stack. E' l'istruzione stessa call ad occuparsi dell'invio del proprio indirizzo allo stack, mentre l'istruzione ret preleva tale indirizzo [lo carica nel registro IP] e quindi passa il controllo del programma alla istruzione successiva a call.
Le chiamate call - ret posso essere anche annidate seconde questa logica:

1 call
2 call
3 call
...
3 ret

2 ret
1 ret

L'ultima call [3] deve essere anche la prima ad essere risolta e quindi la prima ret si riferira' ad essa. A ben vedere, questa sequenza segue quella di accumulazione degli indirizzi nello stack.
Le operazioni di invio e recupero degli indirizzi nello stack che abbiamo visto realizzate da call e da ret, possono essere compiute dalle istruzioni push e pop. Push e pop estendono l'utilizzo dello stack ad operazioni non necessariamente legate alle chiamate delle routine.
Diciamo subito che push e pop sono istruzioni importantissime inquanto vengo incontro all'esigenza del programmatore di possedere un numero piu' elevato di registri di lavoro. Push e pop consentono di utilizzare i registri in maniera locale salvando temporaneamente il contenuto di questi cosi' da renderli disponibili per altre operazioni in altre sezioni di un programma.
Push e' l'istruzione di inserimento di parole di dati [2 byte] nello stack, mentre pop e' l'istruzione di prelievo di questi dati dallo stack.
Le istruzioni push e pop posso essere anche annidate seconde questa logica:

1 push
2 push
3 push
...
3 pop

2 pop
1 pop

ad esempio:

push cx
push dx
mov cx, 000Ah
call stampa
...
pop dx
pop cx







In questo caso il valore contenuto nel registro CX [cosi' come quello contenuto in DX] vengono temporaneamente salvati nello stack dall'istruzione push. Successivamente CX viene caricato con 000ah. Probabilmente esso verra' decrementato in relazione all'istruzione loop, mentre altre istruzioni interesseranno il registro DX. Quando il programma incontra le istruzioni pop i valori originari di CX e di DX [per CX il valore contenuto prima di mov CX, 000A] verranno ripristinati. Come e' possibile constatare, se queste operazioni complessificano un po' la gestione del programma, esse decuplicano il numero dei registri a disposizione.
Procediamo con un nuovo programma; questo dovra' leggere da tastiera un numero esadecimale a due cifre [ad esmpio 39h] e stamparlo.
Prima di procedere facciamo un piccolo riassunto di quanto finora realizzato cosi' da avere le idee piu' chiare su cio' che andremo a fare.
Fino ad ora abbiamo stampato un carattere esadecimele [modulo II] osservando come int 21 funzione 02 si occupasse di trasformare automaticamente il numero esadecimale in un carattere ascii.
In seguito [modulo IV] abbiamo preso un numero esadecimale come F e lo abbiamo trasformato [< 9 0 +30; > 9 = + 37] in modo da stampare il carattere ascii corrispondente al numero esadecimale [questa procedura vale per i caratteri ascii 0 - 9 e A - F].
Ora vogliamo trasformare un numero esadecimale in un carattere ascii avendo sulla stessa riga del risultato il numero esadecimale e la sua codifica in ascii
. Si ricordi che ogni numero digitato viene interpretato da debug come numero esadecimale, quindi se digitiamo 5 per debug sara' 35h.

mov ah, 01  
  int 21 ; la funzione 01 per INT 21 legge un numero digitato dalla tastiera
mov dl, al ; l'esadecimale preso si inserisce in AL. faccio una copia di al in dl
  sub al, 30 ; sottrai dal numero esadecimale messo in DL l'esadecimale 30. ad esempio:
    ; il numero 9 corrisponde all'esadecimale 39 a cui viene sottratto 30h.
  cmp al, 09 : compara il risultato con l'esadecimale 9
  jle shift ; se uguale o minore salta a shift dove il numero sara' spostato a sinistra di 4 bit
  ; altrimenti... significa che si tratta di una lettera [> 9] e quindi necessita di una
  ; ulteriore sottrazione esistendo tra l'ascii 9 e l'ascii A, 7 posizioni
  sub al, 07 ; sottrai 7h dopo aver sottratto 30h... e poi passa alla procedura di shl
shift:
  mov cl, 04 ; carica il contatore CL con 4h
  shl dl, cl ; slitta DL di 4 bit verso sinistra, quindi il numero binario si sposta nel semibyte di
    ; sinistra lasciando a 0000 quello di destra
  int 21 ; leggi il secondo numero digitato dall'utente
  sub al, 30 ; l'istruzione e' come quella sub precedente ma non abbiamo doppiato AL
  cmp al, 09 ;
  jle fine ; il secondo numero esadecimale e' compreso tra 0 e 9 allora salta alla fine
    ; altrimenti...
  sub al, 07 ; il secondo numero e' compreswo tra A e F allora sottrai ancora 7h
fine:    
  mov ah, 02  
  mov dl, al ; DL aveva gia il primo numero alla estrema sinistra a cui si aggiunge il secondo
  ; numero alla estrema destra cosi' da avere in un unico byte le due cifre
  int 21  
  int 20  

Se scritto tramite debug, questo programma una volta lanciato con il comando g resta in attesa che l'utente scriva un numero a due cifre. Mentre l'utente digita il primo numero il programma processa l'esadecimale giungendo fino al secondo int 21. Il primo carattere trattato viene accantonati in DL e quando anche il secondo numero viene trattato anch'esso viene inserito in DL e poi stampato.
Giungiamo finalmente all'applicazione delle regole sullo stack apprese precedentemente per ampliare e perfezionare il programma precedente.

Il problema che il programma incorpora e' quello di non discriminare tra numeri esdecimali, infatti a noi interessa tradurre solo numeri da 30 a 39 e da 41 a 46 [0 a F ascii] e tralasciare il resto.
Quella che segue e' la procedura di verifica per cifre esadecimeli comprese tra 30 e 39 e 41 e 46. Inoltre essa si occupa di trasformare la cifra esadecimale nel segno ascii [sottraendo 30 per 30 - 39 e 37 per 41 - 46]

inizio:    

push dx ; salva il valore di DX proveniente dalla call [che vedremo fra poco]
mov ah,08h ; nuova funzione per INT 21: legge un numero digitato dalla tastiera
    ; ma non lo visualizza sullo schermo
nuovo:  
  int 21 ; legato alla istruzione precedente
  cmp al, 30h ; compara il numero letto da tastiera a 0 esadecimale
  jb nuovo ; se il numero e' inferiore a 30 non prenderlo in considerazione e torna a
  ; leggere un nuovo numero, ovvero all'etichetta nuovo... altrimenti...
cmp al, 46h ; compara il numero digitato con 46h ovvero con il carattere ascii F
  ja nuovo ; se superiore a 46h non prenderlo in considerazione e vai a leggere un
    ; un nuovo numero... altrimenti... siamo in presenza di un numero
  ; tendenzialmente significativo...
cmp al, 39h ; comparalo con 9h per scoprire se deve essere tradotto in una lettera o
; in un numero
ja lettera ; se maggiore di 39h [9 ascii] vai a trattarlo come una lettera, altrimenti...
; e' una cifra da tradurre in numero [> 39h]
mov ah, 02 ; preparati a stamparlo
mov, dl, al ;
int 21 ; stampalo
  sub al, 30 ; trasforma il numero esadecimale in un carattere ascii [0 - 9]
  pop dx ; stiamo per tornare alla call quindi ripristina il registro CX originario
  ret ; fai ritorno alla call d'origine [che vedremo fra poco]
lettera:    
  cmp al, 41h ; compara al con 41h [A ascii]
  jb nuovo ; per minore scarta [da 3Ah a 40h] numero e vai a nuovo... altrimenti...
  mov ah, 02h ; preparati a stamparlo
  mov dl, al  
  int 21 ; stampalo
  sub al, 37h ; trasforma il numero esadecimale in un carattere ascii [A - F]
  pop dx ; stiamo per tornare alla call quindi ripristina il registro CX originario
  ret ; fai ritorno alla call d'origine [che vedremo fra poco]

Questa e' una routine a cui un programma rimanda immediante l'istruzione call. Le istruzioni precedenti dovrebbero essere inserite in locazioni di memoria a partire dallo spostamento 200. Allo spostamento 100 introduciamo la prcedura di controllo:

call inizio ; vai immediatamente alla richiesta di un numero all'utente
mov dl, al ; quando torno a questa istruzione grazie ad uno dei ret
    ; il numero esadecimale in AL e' stato trasformato; viene messo in DL
  mov cl, 04 ; viene caricaro il registro contatore CL con 04h
  shl dl, cl ; procede alla rotazione del numero a sinistra
  call inizio ; torno a inizio per il secondo numero
  add dl, al ; addiziono il secondo numero al registro DL dove c'e' gia' il numero
    ; spostao a sinistra. Il secondo numero verra' posizionato a destra
  mov ah, 02 ; prepara alla stampa
int 21 ;
  int 20 ;

Riassumendo: questo programma legge un numero esadecimale a 2 cifre. Prende la prima la trasforma in un carattere ascii [infatti ad esempio nel caso di 43 se il programma non trasformasse la cifra in carattere ascii vedremo stampato il carattere ascii per il numero esadecimale 4 invece il nostro programma stampa 4 sottraendo 30 a 34 (4 esadecimale)] e la deposita in un registro, legge il secondo numero, lo trasforma in un carattere ascii e la deposita nello stesso registro. Poi il programma stampa le due cifre digitate e ci fornisce la codifica ascii.
Cio' che distingue questo programma dal precedente e' che esso controlla che il carattere digitato sia corretto, ovvero compreso nell'intervallo ritenuto significativo tra 30 e 39 e 41 e 46. Se e' compreso tra questo intervallo, dopo essere stato trasformato in carattere ascii viene stampato immediatamente sul video. La stessa cosa avviene per la seconda cifra. Una volta inseriti due numeri compresi nei giusti intervalli, il programma stampa di seguito il corrispondente carattere ascii frutto dell'unione dei due numeri esadecimali. Abbiamo utilizzato la funzione 8 di int 21 perche' essa legge un numero dalla tastiera senza eco, ovvero non lo visualizza sullo schermo a differenza della funzione 1 che lo visualizzava.
Data la lunghezza del programma segue la versione da copiare e incollare nella shell di DOS:

call 0114
mov dl, al
mov cl, 04
shl dl, cl
call 0114
add dl, al
mov ah, 02
int 21
int 20
push dx
mov ah, 08
int 21
cmp al, 30
jb 0117
cmp al, 46
ja 0117
cmp al, 39
ja 012f
mov ah, 02
mov, dl, al
int 21
sub al, 30
pop dx
ret
cmp al, 41
jb 0117
mov ah, 02
mov dl, al
int 21
sub al, 37
pop dx
ret

torna a modulo IV  
 
 

Assembly modulo V lo stack e le istruzioni per controllarlo.