Appunti per la programmazione in assembler

Materie:Appunti
Categoria:Informatica

Voto:

2.5 (2)
Download:199
Data:26.06.2000
Numero di pagine:11
Formato di file:.doc (Microsoft Word)
Download   Anteprima
appunti-programmazione-assembler_1.zip (Dimensione: 13.99 Kb)
trucheck.it_appunti-per-la-programmazione-in-assembler.doc     57.5 Kb
readme.txt     59 Bytes


Testo

(In questi appunti vi parlo un po’ di assembler)
By Leons

Le istruzioni per lo spostamento dati
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

Esempi di spostamenti memoria , NON AMMESSI, sono (in linguaggio macchina):
mov byte/word [100],[200]
mov byte/word [bx],[50]
Esercizio (facile!): espandere questi spostamenti usando ad es. al/ax come registro di supporto.
> MOV
Il metodo più comune per spostare i dati è quello di utilizzare l'istruzione MOV. La sintassi completa dell'istruzione è la 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;

Attenzione che non è possibile fare lo scambio di due dati entrambi in memoria.
> LAHF e SAHF
Per esaminare il registro dei flag esistono queste due istruzioni: 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 queste due istruzioni lavorano solo sugli 8 bit meno significativi del registro.
Esistono comunque altre due istruzioni per salvare e ripristinare l'intero 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)(FFFBh-->FFFF:FFFBh) in DX: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) in tal caso è più veloce essendo l'OFFSET una costante nota all'assembler che quindi non deve essere calcolata.
Nota: più veloce in tal caso significa risparmiare qualche ciclo di clock (!!!), ma ciò avviene solo sugli 8088 (!!!) Chi più usa un 8088? (io ce l'ho uno, per ricordo)
E comunque in tal caso TASM è in grado di tradurre una
LEA dx,stringa
con
MOV dx,OFFSET stringa.
L'istruzione LEA è più utile per calcolare l'indirizzo indiretto:
LEA dx,stringa[si] ;mette in dx l'indirizzo di stringa[si]
equivale in linguaggio macchina a
LEA dx,[si+OFFSET stringa]
>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 in ES.
Queste istruzioni sono spesso usate con le stringhe come vedremo più 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 importanza nei processori 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.
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 ---------
^ | ??? | ,< registro|memoria|valore >
SBB < registro|memoria >,< registro|memoria|valore >
DEC < registro|memoria >
NEG < registro|memoria >
Cominciamo dalla SUB che funziona esattamente come la ADD: sottrae dal primo operando il secondo e mette il risultato nel primo. Valgono le stesse osservazioni fatte per la somma quindi se non ve le ricordate andate a rileggerle.
Idem per quanto riguarda la DEC che altro non fa che sottrarre 1 all'operando, anche qui il numero è trattato come senza segno.
Vi riporto anche qui qualche esempio per capire meglio:
.DATA
dato DB 122
.CODE
...
...
mov al,95 ;al=95
dec al ;al=94
sub al,23 ;al=71

sub al,dato ;al=-51 se con segno oppure
; al=205 senza segno

mov ah,119
sub al,ah ;al=86 con OF=1
Quando il risultato va sotto lo zero viene settato il SF (sign flag).
Per poter effettuare le sottrazioni vere e proprie con tanto di riporto si usa la SBB (subtract with borrow) che funziona esattamente come la SUB solo che in caso di riporto viene sottratto 1 al risultato.
Esempio:
.DATA
datoA DD 316423
datoB DD 156739
.CODE
...
...
mov ax,WORD PTR datoA[0]
mov dx,WORD PTR datoA[2] ;ora ho il primo numero in DX:AX
sub ax,WORD PTR datoB[0]
sbb dx,WORD PTR datoB[2] ;ho il risultato in DX:AX
...
...
L'ultima istruzione che rientra nella categoria delle sottrazioni è la NEG che cambia il segno dell'operando e che quindi deve essere usata solo per i numeri con segno.
ES:
mov ax,143 ;ax=143 --> 8Fh
neg ax ;ax=-143 --> FF71h
NOTA: Vi ricordo che i numeri negativi sono rappresentati in complemento a 2.
>MUL e IMUL
La terza operazione che vediamo è la moltiplicazione, senza segno (MUL) e con segno (IMUL).
La loro sintassi è la seguente:
MUL < registro|memoria >
IMUL < registro|memoria >
Vi chiederete dove sta il secondo fattore. Bene l'operando specificato nella MUL (o nella IMUL) verrà moltiplicato per il numero che è contenuto nell'accumulatore (AX per i 16bit, AL per gli 8bit o EAX per i 32bit).
E il risultato?
Quello dipende dagli operandi:
1byte * AL --> risultato in AX (CF=0 e OF=0 se e solo se AH=0)
2byte * AX --> risultato in DX:AX (CF=0 e OF=0 se e solo se DX=0)
4byte * EAX -> risultato in EDX:EAX (CF=0 e OF=0 se e solo se EDX=0)
Anche qui vediamo alcuni esempi:
.DATA
dato DW -30000
.CODE
...
...
mov al,23 ;al=23
mov bl,24 ;bl=24
mul bl ;ax=552 (CF=1 e OF=1)

mov ax,50
imul dato ;DX:AX=-1500000 (CF=1 e OF=1)
Da questo esempio si capisce anche come funziona la IMUL.
Per processori 80186 e superiori la IMUL prevede anche le seguenti sintassi:
IMUL < registro >,< valore >
IMUL < registro >,< memoria >,< valore >
In questo caso il risultato viene messo nel registro specificato e nel caso non ci stia viene settato CF e OF.
Quando specifico 3 parametri il primo è la destinazione e gli altri due sono i fattori del prodotto.
Esempi:
imul dx,455 ;moltiplica dx*455
imul ax,[bx],6 ;moltiplica il valore puntato da bx*6 e
; mette il risultato in ax
>DIV e IDIV
Anche per la divisione esistono 2 istruzioni una per i numeri senza segno (DIV) e una per quelli col segno (IDIV).
Entrambe ritornano il quoziente e il resto, e la loro sintassi è:
DIV < registro|memoria >
IDIV < registro|memoria >
Per effettuare una divisione di un numero a 16bit per uno di 8bit devo mettere il numero da dividere in AX e specificare il divisore (cioè l'altro numero) in un altro registro. Il risultato della divisione sarà in AL e il resto in AH ; quindi fate attenzione il dividendo viene perso.
Se invece voglio dividere un numero a 32 bit per uno di 16 si deve mettere il dividendo in DX:AX, specificare il divisore in un registro a 16 bit (che non sia AX o DX) ed effettuare la divisione: avremo il risultato in AX e il resto in DX.
Per dividere tra loro due numeri a 16 bit si deve prima trasformare il dividendo in un numero a 32 bit tramite l'istruzione CWD (andate a rivederla) se ci interessa il segno oppure nel modo visto nel tutorial precedente se stiamo lavorando con numeri senza segno.
Se si cerca di effettuare una divisione per 0 o se il quoziente non sta nei registri viene generata una chiamata all'int 0 ,il programma termina e il controllo torna al DOS.
Per evitare questo problema ci sono due modi: o si controlla il valore del divisore prima di effettuare l'istruzione DIV, oppure intercettare l'int 0 e riscriversi una routine di interrupt per gestire l'evento ma questo lo vedremo più avanti!!
Vi riporto quelche esempio:
.DATA
data16 DW -2000
data32 DD 500000
.CODE
...
...
mov ax,700 ;ax=700
mov bl,36 ;bl=36
div bl ;al=19 (quoziente), ah=16 (resto)

mov ax,WORD PTR dato32[0]
mov dx,WORD PTR dato32[2] ;carico il dividendo in DX:AX
idiv dato16 ;500000/-2000
;AX=-250 , DX=0
mov ax,WORD PTR dato16
cwd ;lo estendo a 32bit in DX:AX
mov bx,-421
idiv bx ;ax=4 , dx=-316

Ora che abbiamo visto le 4 operazioni fondamentali analizzeremo alcune istruzioni che lavorano con i numeri binari in notazione BCD.
Vi chiederete perchè; beh sappiate che lavorare con in numeri in BCD spesso è più facile per fare calcoli.
Queste istruzioni non hanno operandi perche lavorano sempre con al:
>AAA (Adjust after an Addition)
Per capire cosa fa vediamo un esempio:
mov ax,9 ;ax=9
mov bx,3 ;bx=3
add al,bl ;al=C
aaa ;al=2 ah=1 CF=1
Come si vede dall'esempio il risulato (in "unpacked" BCD) l'ho in ax: in ah ci sono le decine e in al le unità. Notate che setta anche il Carry a 1 (questo indica che il risultato in decimale non sta in una cifra, ma è composto da 2 cifre).
Ricordatevi che una cifra in decimale viene qui codificata con 1byte.
>AAS (Adjust After a Subtraction)
mov ax,103h ;ax=13h (in BCD : ah=1 al=3 !!!)
mov bx,4 ;bx=4
sub al,bl ;al=FFh (-1)
aas ;al=9 ah=0 CF=1
Dopo la prima istruzione mov, ax è ovviamente uguale a 103h; tuttavia un'altra interpretazione è: contiene la versione (non compattata) del valore BCD 13.
>AAM (Adjust After a Multiplication)
mov ax,903h
mul ah ;moltiplica ah*al = 1Bh
aam ;ah=2 al=7;
Attenzione : questa istruzione si può usare solo per le moltiplicazioni senza segno !!!
>AAD (Adjust BEFORE a division)
A differenza delle altre 3 questa va usata prima di una divisione infatti trasforma il numero in BCD in codice binario prima dell'operazione. Dopo la divisione il risultato va risistemato tramite un AAM. Vediamo ad esempio (25/2):
mov ax,205h ;carico il dividendo (25)
mov bl,2 ;carico il divisore (2)
aad ;trasforma il 25 in BCD in 19h binario (ax=19h)
div bl ;quoziente in al=0Ch resto in ah=1
aam ;sistema al in ax=102h (12 in BCD)
Notate che il resto viene perso: se vi serve ricordatevi di salvarlo prima dell'istruzione AAM.
Abbiamo visto come si lavora con numeri a 2 cifre ma se devo usare numeri più grossi?
Semplice devo iterare il metodo per ogni cifra del numero !!
Esistono poi altre due istruzioni per lavorare con un altro tipo di numeri BCD cosiddetti "packed" in cui ogni cifra occupa solo 4 bit.
A differenza delle altre non cambiano il valore di ah ma lavorano sul registro di flag ogni volta che c'è un riporto.
>DAA (Adjust After an Addition)
mov ax,8833h ;carica 88 e 33 in un unico registro
add al,ah ;0BBh
daa ;CF=1 e AL=21
>DAS (Adjust After an Subtraction)
mov ax,3883h ;carica 38 e 83 in BCD
sub al,ah ;04Bh
das ;CF=0 AL=45
Vediamo ora le istruzioni che il processore ci mette a disposizione per effettuare operazioni logiche.
Qui andrò abbastanza veloce, il significato degli operatori l'ho già spiegato perciò mi limiterò a elencare la sintassi delle istruzioni e a fare qualche breve commento.
>AND < registro|memoria >,< registro|memoria|valore >
Effettua un AND bit a bit degli operandi.
L'AND può essere utile per azzerare alcuni bit di un registro tramite una "maschera" in cui i bit da azzerare sono 0 e gli altri sono 1:
mov ax,035h ;00110101
mov bx,0FBh ;11111011
and ax,bx ;00110001 ho azzerato il secondo bit !!
I bit di solito vengono convenzionalmente contati a partire da destra e da 0.
>OR < registro|memoria >,< registro|memoria|valore >
Effettua l'OR bit a bit.
Può essere utile per settare solo alcuni bit di un registro con un'opportuna "maschera" di bit.
>XOR < registro|memoria >,< registro|memoria|valore >
Effettua l'OR esclusivo bit a bit.
E' molto usata per azzerare il valore di un registro facendo XOR con se stesso: è molto veloce!!
>NOT < registro|memoria >
Effettua il NOT logico sui bit dell'operando.
Vediamo infine alcune istruzioni non prettamente matematiche ma che vengono spesso utilizzate per questo scopo.
>SHL e SHR
Queste due istruzioni shiftano a sinistra e destra (risp.) l'argomento. Innanzi tutto la sintassi è la seguente:
SHL < registro|memoria >,< CL|1 >
SHR < registro|memoria >,< CL|1 >
Vediamo cosa vuol dire con un esempio:
mov al,01001011 ;al=01001011
shl al,1 ;al=10010110 CF=0
In pratica è successo che tutti i bit sono stati spostati di una posizione verso sx, il bit più significativo è andato nel CF e quello meno significativo è stato messo a zero.
Dal punto di vista matematico ho effettuato una moltiplicazione per 2!! Lo stesso discorso simmetrico vale per lo Shift a dx e in questo caso il numero viene diviso per 2.
Se sposto di due posizioni il numero verra moltiplicato/diviso per 4, se sposto di 3 per 8,di 4....
>ROR e ROL
Queste servono per far ruotare i bit a destra e sinistra (risp.) La loro sintassi è uguale a shl e shr . Vediamo subito un esempio:
mov al,01001011 ;al=01001011
ror al,1 ;al=10100101
mov cl,3
rol al,cl ;al=00101101

Esempio