Le istruzioni per lo spostamento dati

Ora che abbiamo un'idea di come è fatto un programma in Assembly vedimo un po'
piu' a fondo come si usano le istruzioni per per realizzare i nostri programmi.
In questa prima parte dei tutorial dedicati alle istruzioni tratterò in
dettaglio le istruzioni che servono per spostare, salvare e caricare i dati
dalla/in memoria.

Muovere i dati è una dei principali compiti di un programma Assembly, abbiamo
già visto che sono possibili solo i seguenti spostamenti:

                memoria  <-->  registro
                registro <-->  registro

Non sono possibili spostamenti da memoria a memoria e per far ciò si deve
ricorrere all'uso di registri di supporto:

                memoria <--> registro <--> memoria


> MOV
Il metodo piu' comune per spostare i dati è quello di utilizzare l'istruzione
MOV come abbiamo già visto nei precedenti esempi. La sintassi completa
dell'istruzione è le seguente:

        MOV < registro|memoria > , < registro|memoria|valore imm. >

Alcuni esempi sono:

        mov     ax,7            ;valore --> registro
        mov     mem,45          ;valore --> memoria
        mov     ax,bx           ;registro --> registro
        mov     mem[bx],7       ;valore --> memoria (indiretto)
        ...
        ...

> XCHG 
Un'altra istruzione per eseguire spostamenti di dati è XCHG che scambia tra loro
i due operandi :

        XCHG < registro|memoria > , < registro|memoria >

Ad esempio:

        xchg ax,bx      ; pippo:=ax;
                        ; ax:=bx;
                        ; bx:=pippo;


> LAHF e SAHF 
Per esaminare il registro dei flag esistono queste due instruzioni: LAHF che
carica in AH, SAHF che li salva. Spero vi sia sorto un dubbio. Come faccio a
far stare l'intero registro di flag in 8 bit ??
Bene quete due instruzioni lavorano solo sugli 8 bit meno significativi del
registro.
Esistono comunque altre due istruzioni per salvare e ripristinare il registro
di flag nello Stack ma le vedremo dopo.

-- CONVERTIRE LE DIMENSIONI DI UN DATO --
Siccome trasferire dati tra registri di diversa dimensione non è consentito,per
fare questo tipo di operazione si devono prendere alcuni accorgimenti.
Prima di tutto ci si comporta in modo diverso a seconda che il dato abbia o no
il segno.
Nel caso di SIGNED VALUE l'Assembly mette a disposizione due istruzioni:CBW
(Convert Byte to Word) e CWD (Convert Word to Doubleword).
CBW converte il dato a 8 bit con segno contenuto in AL mettendolo in AX (16bit).
CWD come la precendente prende il dato in AX (16bit) e lo mette in DX:AX.
Per capire meglio vediamo un esempio:

        .DATA
mem8    DB      -5
mem16   DW      -5

        .CODE
        ....
        ....
        mov     al,mem8         ;mette in AL -5 (FBh)
        cbw                     ;converte (8-->16)(FBh-->FFFBh) in AX

        mov     ax,mem16        ;AX=-5 (FFFBh)
        cwd                     ;converte (16-->32)(FFFB-->FFFF:FFFBh) in DS:AX

-- USARE I PUNTATORI --

>LEA
L'istruzione LEA (Load Effective Address) carica un puntatore di tipo NEAR nel
registro specificato, la sua sintassi è :

        LEA     registro,memoria

Ad esempio:

        LEA     dx,stringa   ;carica in dx l'offset di stringa

equivale a

        MOV     dx,OFFSET stringa

NOTA : il secondo modo (con l'uso di MOV) è piu' veloce essendo l'OFFSET una
costante nota all'assembler che quindi non deve calcolarla.
L'istruzione LEA è piu' utile per calcolare l'indirizzo indiretto:
        LEA     dx,stringa[si]  ;mette in dx l'indirizzo di stringa[si]

>LDS e LES
Queste sono le istruzioni analoghe a LEA solo che si usano per puntatori FAR.
La loro sintassi è uguale all'istruzione LEA.
Il registro segmento nel quale viene messo il dato dipende dall'istruzione: LDS
mette il segmento in DS e LES lo mette (indovinate un po !!) in ES.
Queste istruzioni sono spesso usate con le stringhe come vedremo piu' avanti.
Vediamo comunque un esempietto:
        .DATA

stringa DB      "A me piace la Nutella"
fpStr   DD      stringa                 ;Puntatore FAR a stringa
punt    DD      100 DUP(?)
        .CODE
        ...
        ...
        les     di,fpStr                ;mette l'indirizzo in ES:DI
        lds     si,punt[bx]             ;mette l'indirizzo in DS:SI

-- OPERAZIONI SULLO STACK --
Lo stack è un'area di memoria di fondamentale imporatanza nei processo 80x86 e
molte istruzioni quali  CALL, INT, RET, IRET fanno uso dello stack per salvare
parametri, indirizzi e registri.

>PUSH e POP
Queste sono le due istruzioni base  rispettivamente per scrivere e leggere nello
stack, esse modificano automaticamente il valore di SP.
La loro sintassi è la seguente:

        PUSH    < registro|memoria|valore >
        POP     < registro|memoria >

NOTA: PUSH valore è disponibile solo sui processori 80186 e superiori.
Attenzione che le dimensioni di una "cella" di stack è di 2 byte.
Ho già spiegato in un precedente tutorial l'uso dello stack (struttura LIFO) e
qui non mi soffermo oltre e vi lascio con qualche esempio:

        mov     bp,sp           ;preparo bp alla base dello stack
        push    ax              ;salva il primo 
        push    bx              ;salva il secondo 
        push    cx              ;...
        ...
        ...
        pop     cx              ;prelevo il terzo  
        pop     bx              ;prelevo il secondo
        pop     ax

Una breve nota: lo stack a differenza di altre strutture comincia a memorizzare
i dati nelle locazioni alte di memoria e via via scende verso il basso man mano
che aggiungo dati.
Quindi se decido di accedere allo stack tramite indirizzamento indiretto dovrò
fare nel seguente modo:

       mov      bp,sp
       push     ax              ;salvo i 3 regsitri
       push     bx
       push     cx
       ...
       ...
       mov      ax,[bp-2]       ;prelevo ax
       mov      bx,[bp-4]       ;prelevo bx
       mov      cx,[bp-6]       ;...

A questo punto però non ho vuotato lo stack (ho solo letto i valori) per
ripristinarlo come prima devo aggiungere l'istruzione:

        sub     sp,6

sottraggo cioè 6 byte dallo Stack Pointer (2 byte per ogni registro).
Graficamente la situazione è questa:

                          Stack
          High Mem      ---------
             ^          |  ???  | <-- BP
             |          |-------|
             |          |   ax  | <-- BP-2
             |          |-------|
             |          |   bx  | <-- BP-4
             |          |-------|
             |          |   cx  | <-- BP-6 = SP
          Low Mem       |-------|
                        |       |
Questo prima della sub.

>PUSHF e POPF
Sono istruzioni che salvano nello stack e ripristano il valore del registro di
flag.
Per processori 386 e sup. ci sono anche le due istruzioni rispettive per i
registri a 32 bit : PUSFD e POPFD.
 

>PUSHA e POPA
Salvano e ripristinano tutti i registri nel seguente ordine: AX,CX,DX,BX,SP,BP,
SI e DI. Il valore di SP che viene salvato è quello prima dell'istruzione PUSHA
Naturalmente la POPA li estrae in ordine inverso.
Per processori 386 e sup. ci sono anche le due istruzioni rispettive per i
registri a 32 bit : PUSHAD e POPAD.

-- SCRIVERE E LEGGERE SULLE PORTE DI I/O --
Le porte sono il mezzo per far comunicare la CPU con le schede presenti nel
computer. Ogni porta ha un suo numero che può essere usato per accedervi.

>IN e OUT
IN legge il dato dalla porta e OUT lo manda alla porta, la loro sintassi è:

        IN      accumulatore,< numero porta|DX >
        OUT     < numero porta|DX >,accumulatore

NOTA: ricordo che l'accumulatore è per le famiglie 80x86 il registro ax che può
essere usato a 8,16 o 32 bit (al/ah,ax,eax).
Si deve necessariamente usare il registro DX per accedere ad una porta il cui
numero è maggiore di 256.
Vediamo un esempio dell'uso di queste istruzioni con questa sequenza di
istruzioni che fanno suonare lo speaker:

        sound   EQU     61h             ;porta che controlla lo speaker
        timer   EQU     42h             ;porta che fa suonare lo speaker
        on      EQU     00000011b       ;i 2 LSB attivano lo speaker

                in      al,sound        ;preleva lo stato attuale della porta
                or      al,on           ;attiva lo speaker
                out     sound,al        ;rimanda il valore

                mov     al,250          ;comincia da 250
        suona:  out     timer,al        
                mov     cx,0FFFFh       ;aspetta "FFFF volte"
        aspetta:loop    aspetta

                dec     al
                jnz     suona

                in      al,sound
                and     al,NOT on       ;spegne lo speaker
                out     sound,al        ;ripristina la porta

Nei processori 80186 e superiori sono state introdotte altre istruzioni per
leggere e scrivere stringhe :

        INS     < [ES:]destinazione >,DX
        INSB
        INSW

e le rispettive out:

        OUTS
        OUTSB
        OUTSW

La INS e la OUTS non accettato un valore immediato per l'indirizzo e si deve
far uso di DX.
Vedremo meglio queste istruzioni quando parleremo di stringhe.

Per adesso mi fermo qui, questa volta non vi presento nessun programma di
piu' che altro perchè non mi viene in mente niente su questo argomento;
comunque potete sempre provare a scrivere qualcosa voi e se incontrate
difficoltà, beh fatemelo sapere cercherò di aiutarvi.


** [email protected] **