![]() |
(Incompleto) MINI CORSO DI ASSEMBLER 8086 |
![]() |
Elenchiamo di seguito le istruzioni + utilizzate:
CLD
DIV
JMP
MOV
NEG
REP
STD
IN
OUT
Funzioni Statistiche
MOV <sorgente>, <destinatario>
Questa istruzione copia l'operando
<sorgente>
nell'operando
<destinatario>.
Gli operandi possono avere dimensione di un byte, di una word o di una
doubleword. L'istruzione non modifica il registro dei flag.
Le tipologie dei suoi operandi sono:
- tra registro e registro di segmento (MOV
AX, ES)
- tra registro di segmento e memoria (MOV
ES,
mov ax,SEG Messaggio
Questa prima istruzione (vera e propria) sposta in AX il valore del puntatore al
Data Segment. Mov è (penso) l'istruzione più usata nei programmi, è quella che
permette di spostare dati dalla memoria alla cpu e viceversa. Vediamo quali sono
i modi di indirizzamento (cosi si chiamano) possibili:
1. Indirizzamento Immediato
mov ax, 1643
1643 è il dato numerico da mettere in AX.
2. Indirizzamento Assoluto
mov ax, [7563]
7563 è l'indirizzo del dato da mettere in AX.
3. Indirizzamento con Indiretto
mov ax, [si]
Metto in AX il dato puntato da SI (che si trova all'indirizzo SI). Si può anche
scrivere:
mov ax, [si+45] ; avete capito cosa fa vero?
J.. - Istruzione di salto
JMP <etichetta>
L' istruzione
JNS <etichetta>
L' istruzione
JNE <etichetta>
Questa istruzione esegue un salto condizionato dal risultato di una operazione
aritmetico-logica tra numeri senza segno. Si evidenzia anche che JNE (salta se
non uguale) viene eseguita se [flag di Zero]=0; nessuna flag è invece
influenzata da questa istruzione. Il servizio di questa istruzione è identico a
quello offerto dalla JNZ (salta se non è zero)
LOOP <etichetta>
Salta incondizionatamente CX volte - In pratica prima decrementa il
valore di CX e in funzione del risultato:
- se CX=0000 prosegue con l'istruzione successiva.
- se CX è diverso da 0000 salta all'indirizzo corrispondente
all'etichetta.
In entrambi i casi modifica il puntatore di codice CS:IP.
Naturalmente perchè sia significativa bisogna predisporre in CX un numero
compreso tra 0 e 65535 (=216); con 80386/486 il registro di
riferimento diventa ECX, per cui il range diventa a 32 bit.
Attenzione: il numero caricato in CX è quello dei giri (loop, cicli) che si desidera fare; non dimenticare che l'istruzione prima decrementa CX e poi, eventualmente, salta: per questa ragione se, per errore (o coscientemente..) carichi CX con 0000 il programma sembra bloccarsi. Per forza! l'hai obbligato a fare 65535 giri!
L' istruzione
CLD
imposta a 0 il flag di direzione DF. Quando il
flag DF è a 0 le funzioni di manipolazione delle stringhe agiscono in
avanti. L'istruzione corrispondente è
STD.
DIV <divisore>
Questa istruzione esegue la divisione tra operandi ritenuti interi senza segno;
il dividendo (destinazione) è per default:
- AX (numero a 16 bit), se il <divisore> ha la dimensione di un byte; il
risultato (quoziente) è lasciato in AL e il resto in AH.
- DX:AX (numero a 32 bit), se il <divisore> ha la dimensione di una word;
il risultato (quoziente) è lasciato in AX e il resto in DX.
- EAX:EDX (con 80386/486, numero a 64 bit), se il <divisore> ha la
dimensione di una doubleword; il risultato (quoziente) è lasciato in EAX
e il resto in EDX.
Il <divisore> non può essere una costante, cioè un dato immediato come, per
esempio, in DIV 05H.
Se gli operandi sono incoerenti il processore si ribella ed esegue un INT 00,
una speciale procedura di sistema messa in esecuzione quando viene tentata una
divisione per 0; : il programma in esecuzione viene terminato e sul monitor
appare la segnalazione d'errore "overflow di divisione".
Questa situazione si può verificare in 2 occasioni:
- il divisore è effettivamente nullo (per esempio DL=00H=0);
- il divisore è troppo piccolo, o meglio il quoziente ottenuto è troppo grande
per essere contenuto nel registro destinazione
L' istruzione
STD
imposta a 1 il flag di direzione DF. Quando il
flag DF è a 1 le funzioni di manipolazione delle stringhe agiscono
indietro. L'istruzione corrispondente è
CLD.
CMP <operando1>, <operando2>
L' istruzione CMP si comporta come una sottrazione: sottrae il secondo operando dal primo senza modificare alcun valore. L'unico effetto è quello di impostare i flag di stato in base al risultato calcolato. Ad ogni flag di stato sono associate due istruzione di salto condizionato: una che salta se il flag è a 1 e altra se il flag è a 0. Vediamo un esempio:
ESEMPIO FUNZIONAMENTO CMP | CREA CON: DEBUG<CMP.TXT |
C:\trans>debug
cmp.com -u 100 ld 168C:0100 B81000 MOV AX,0010 168C:0103 3D0900 CMP AX,0009 168C:0106 3D1000 CMP AX,0010 168C:0109 3D1100 CMP AX,0011 168C:010C C3 RET -t AX=0010 BX=0000 CX=000D DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000 DS=17B1 ES=17B1 SS=17B1 CS=17B1 IP=0103 NV UP EI PL NZ NA PO NC 17B1:0103 3D0900 CMP AX,0009 -t AX=0010 BX=0000 CX=000D DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000 DS=17B1 ES=17B1 SS=17B1 CS=17B1 IP=0106 NV UP EI PL NZ AC PO NC 17B1:0106 3D1000 CMP AX,0010 -t AX=0010 BX=0000 CX=000D DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000 DS=17B1 ES=17B1 SS=17B1 CS=17B1 IP=0109 NV UP EI PL ZR NA PE NC 17B1:0109 3D1100 CMP AX,0011 -t AX=0010 BX=0000 CX=000D DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000 DS=17B1 ES=17B1 SS=17B1 CS=17B1 IP=010C NV UP EI NG NZ AC PE CY 17B1:010C C3 RET |
a mov AX,10 cmp AX,9 cmp AX,10 cmp AX,11 RET n cmp.com r cx c w q |
Legenda; Flag segno: PL positivo - NG negativo Flag Zero: ZR zero - NZ non zero Flag Parità: PE pari - PO dispari Flag Riporto: NC no riporto - CY si riporto Flag Riporto Ausiliario: NA no riporto - AC si riporto |
il registro flag è così fatto:
FLAG | SET | RESET | |||
Overflow | Overflow | OV | si | NV | no |
Direction | Direzione | DN | decremento | UP | incremento |
Interrupt | Interruzione | EI | abilitato | DI | disabilitato |
Sign | Segno | NG | negativo | PL | positivo |
Zero | Zero | ZR | si | NZ | no |
Auxiliary Carry | Riporto ausiliario | AC | si | NA | no |
Parity | Parità | PE | pari | PO | dispari |
Carry | Riporto | CY | si | NC | no |
NEG <operando>
L' istruzione
PUSH <origine>
L' istruzione
POP <destinazione>
L' istruzione
ESEMPIO FUNZIONAMENTO CMP | CREA CON: DEBUG<POPPUSH.TXT |
C:\trans>debug
poppush.com -u 100 l9 168E:0100 B81000 MOV AX,0010 168E:0103 50 PUSH AX 168E:0104 B80000 MOV AX,0000 168E:0107 58 POP AX 168E:0108 C3 RET -t AX=0010 BX=0000 CX=0009 DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000 DS=168E ES=168E SS=168E CS=168E IP=0103 NV UP EI PL NZ NA PO NC 168E:0103 50 PUSH AX -t AX=0010 BX=0000 CX=0009 DX=0000 SP=FFFC BP=0000 SI=0000 DI=0000 DS=168E ES=168E SS=168E CS=168E IP=0104 NV UP EI PL NZ NA PO NC 168E:0104 B80000 MOV AX,0000 -t AX=0000 BX=0000 CX=0009 DX=0000 SP=FFFC BP=0000 SI=0000 DI=0000 DS=168E ES=168E SS=168E CS=168E IP=0107 NV UP EI PL NZ NA PO NC 168E:0107 58 POP AX -t AX=0010 BX=0000 CX=0009 DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000 DS=168E ES=168E SS=168E CS=168E IP=0108 NV UP EI PL NZ NA PO NC 168E:0108 C3 RET |
a mov AX,10 push AX mov AX,0 pop AX RET n poppush.com r cx 8 w q |
Legenda; Il registro IP contiene l'indirizzo dell'istruzione corrente |
Il comando
POPA
(aggiunto negli 80186) ripristina i
valori nei registri ax, cx, dx, bx, sp, bp, si, di prelevandoli dalla cima dello stack ed incrementa di 16 il valore nel registro SP
LEA <registrodestinazione>, <operando>
L' istruzione
MOV <registrodestinazione>, OFFSET <operando>.
Questa ha il vantaggio di pesare di meno in
termini di codifica in byte rispetto a LEA ma non consente scritture del tipo (poichè
OFFSET effettua i suoi calcoli durante l'assemblaggio e SI non è noto se non
durante l'esecuzione [runtime] ):
MOV BX, OFFSET FRASE[SI] - NON VALIDA
mentre è corretto
LEA BX, FRASE[SI] - VALIDA
In altre parole LEA è più potente poichè permette
di acquisire l'indirizzo anche di variabili inidicizzate. Nessun flag viene influenzato dall'esecuzione di
questa istruzione
L'istruzione non ha operandi (la dimensione del
dato da copiare è prefissata per default: un byte) e i dati sono comunque letti
dalla locazione puntata da DS:SI e copiati in ES:DI. L'uso di questa istruzione
ha quindi senso solo se, in precedenza, entrambi i registri puntatori, SI e DI,
sono stati inizializzati con le rispettive etichette.
Questa istruzione può fruire dell'operatore di autoripetizione
REP:
anteponendolo al suo mnemonico il processo di spostamento dati viene ripetuto
automaticamente CX volte, con conseguente copia automatica di tutta
l'area sorgente di memoria; naturalmente il numero bytes da copiare va caricato
preventivamente in CX. I segmenti dati usati sono impliciti ovvero
ES e DS
.DATA
; segmento dati
STR_SORGENTE DB 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
STR_DESTINAZ DB 26 DUP (?)
.CODE
; segmento codice
CLD
; Imposto l'incremento
di DI e SI
LEA SI, STR_SORGENTE
; Indirizzo sorgente
LEA DI, STR_DESTINAZ
; Indirizzo di destinazione
MOV CX,100
; Carico in CX il
numero di byte da copiare
REP MOVSB
; ripeto CX volte la
copia ...
Questa potente istruzione esegue con un solo codice operativo il compito di 4 istruzioni, cioè equivale all'esecuzione del seguente codice:
MOV AL,DS:[SI] ; carico in AL il byte puntato da DS:SI
MOV ES:[DI],AL ; copio nell'area puntata da ES:DI il contenuto di AL
INC SI
INC DI
La lettera finale B dopo MOVS indica che deve trattare i dati come byte.
NOP
MOV DX,0379H
IN AL,DX
La sua esecuzione non determina cambiamenti nel registro flag.
OUT DX,AL ; spedisco il dato in AL alla porta puntata da DX
- il contenuto del registro DX, cioè un numero intero da 0 a 65535 (da 0000H a
FFFFH): in questo caso il registro DX va caricato prima di effettuare l'input e,
esclusi i primi 256 indirizzi, si riferisce a dispositivi esterni alla scheda
madre, come la porta parallela o la porta seriale.
MOV DX,0378H ; specifico
l'indirizzo della porta da utilizzare
MOV AL,0FFH ; specifico il dato da inviare alla porta
OUT DX,AL ; spedisco il dato in AL alla porta puntata da DX
La sua esecuzione non determina cambiamenti nel
registro flag.
CALL <Destinazione>
Questa istruzione organizza la chiamata e l'esecuzione di una procedura, cioè un
sottoprogramma terminato con
RET.
La procedura chiamata può essere di tipo
NEAR, cioè posta dentro lo stesso
segmento di codice in cui abbiamo la chiamata
CALL,
o
RET [nr byte di stack]
L' istruzione
RET
Quindi se la procedura da cui si torna è
NEAR l'istruzione
RET
(o specificatamente
RETN)
provvede ai seguenti compiti:
1) preleva il byte contenuto nella locazione attualmente puntata da SP,
lo trasferisce nella parte bassa di IP;
2) incrementa il valore di SP e lo utilizza per puntare la locazione da
cui prelevare il byte da utilizzare come parte alta di IP.
3) incrementa ancora SP.
4) salta alla locazione di programma indicata dal nuovo valore di IP,
praticamente l'indirizzo di offset dell'istruzione successiva a quella con la
CALL
che aveva richiamato la procedura che si è appena terminata.
Quando la procedura da cui si torna è
FAR
l'istruzione
RET
(o specificatamente
RETF)
provvede ai seguenti compiti:
a) esegue i punti 1, 2 e 3 di
RETN.
b) usa SP per puntare la locazione nello stack da cui prelevare la parte
bassa di CS.
c) incrementa ancora SP e lo utilizza per puntare la locazione da cui
prelevare la parte alta di CS.
d) incrementa ancora il valore di SP.
e) salta alla locazione di programma indicata dal nuovo valore di CS:IP,
praticamente l'indirizzo logico completo della locazione del programma
principale successiva a quella con la
CALL
che aveva richiamato la procedura che si è appena terminata.
Se insieme all'istruzione
RET
viene fornito un operando numerico, dopo le consuete operazioni illustrate qui
sopra, il numero viene semplicemente sommato a SP; questo significa
effettivamente "scaricare" i bytes dallo stack, anche se ovviamente di essi
rimarrà traccia in memoria, almeno fino al prossimo riutilizzo di questa
preziosa area. Questa opportunità è frequentemente utilizzata dai linguaggi di
programmazione ad alto livello per liberare l'area stack dai parametri passati
in ingresso alla procedura da cui si torna.