Breve panoramica sulla famiglia 80x86 Rappresentazione dei

annuncio pubblicitario
Breve panoramica sulla famiglia 80x86
La famiglia 80x06 è nata nel 1981 con il processore 8086 ed è proseguita con la linea del Pentium.
Ciò che accomuna tutti questi processori è che sono tutti compatibili all'indietro, ma ciascuna nuova
generazione ha aggiunto nel tempo nuove caratteristiche e maggior potenza di calcolo rispetto ai
suoi predecessori. Oggi i computer dotati di processori 8088, 8086, 286, 386 o 486 sono storia
poiché si tratta di computer lenti e superati. Anche i computer dotati di processore Pentium della
ultima generazione sono ormai scomparsi per lasciare il posto agli attuali multi-core a 32 o 64 bit
sui quali gira un software sempre più esigente in termini di risorse e velocità.
Il processore a 16 bit rimane perciò una palestra dove si possono affrontare senza troppe
complicazioni tecnologiche i concetti basilari della programmazione Assembly.
Rappresentazione dei numeri in notazione binaria
Prima di cominciare a capire come programmare in Assembly, è opportuno cercare di comprendere
come i numeri sono rappresentati all'interno della CPU. I numero sono rappresentati in notazione
binaria, ovvero in base 2. Ci sono diversi termini che sono utilizzati per descrivere numeri di
differenti dimensioni, e sono descritti qui di seguito.
1 BIT: 0
Un bit è il tipo di dato più semplice che esiste. Può assumere valore zero oppure uno.
1 NIBBLE: 0000
4 BITS
Un nibble è quattro bit, o mezzo byte. Notate che può assumere il valore massimo di 15 (1111 =
15). Esso è la base della notazione esadecimale (base 16), che viene utilizzata per rappresentare i
numeri nei programmi Assembly. I numeri esadecimali vanno da 1 a F e sono seguiti da un h per
indicare che il numero è espresso in questa notazione. Ad esempio: Fh = 15 decimale. I numeri
esadecimali che cominciano con una lettera sono preceduti da uno 0 (zero).
1 BYTE 00000000
2 NIBBLES
8 BITS
Un byte è 8 bit o 2 nibble. Un byte ha un valore massimo di FFh (255 decimale). Poiché un byte è 2
byte, la rappresentazione esadecimale è formata da due cifre consecutive seguite da h. Ad esempio:
3Dh. Un byte è anche la dimensione dei registri a 8-bit, che vedremo in seguito.
1 WORD 0000000000000000
2 BYTES
4 NIBBLES
16 BITS
Una word è costituita da due byte affiancati. Una word può assumere un valore massimo di FFFFh
(65536). Poiché una word è composta da quattro nibble, è rappresentata da quattro cifre
esadecimali. Una word è anche la dimensione dei registri a 16-bit.
Registri
I registri sono locazioni di memoria all'interno della CPU dove un
numero può essere memorizzato e manipolato. Vi sono tre
dimensioni di registro: 8-bit, 16-bit, e (dal 386 in poi) 32-bit. Vi sono
quattro differenti tipi di registri: registri general-purpose, registri di
segmento, registri indice e registri di stack. Prima descriveremo i
registri principali. I registri di stack e segmento saranno affrontati in seguito.
Registri General-Purpose
Registri General-Purpose
Sono registri a 16-bit. Vi sono quattro registri general-purpose: AX, BX, CX, DX. Sono suddivisi in
registri a 8-bit. AX è suddiviso in AH, che contiene il byte alto e AL che contiene il byte basso. Con
il processore 386 e superiori vi sono anche registri a 32-bit. Questi hanno gli stessi nomi dei registri
a 16-bit ma con una E come prefisso (es.: EAX). Si possono utilizzare AL, AH, AX ed EAX
separatamente e considerare come registri separati per alcune oprazioni.
Se AX contenesse 24689 decimal:
AH
AL
01100000 01110001
AH varrebbe 96 e AL varrebbe 113. Se aggiungessimo 1 ad AL, questo varrebbe 114 e AH
rimarrebbe invariato. SI, DI, SP e BP possono essere utilizzati come registri general-purpose, ma
hanno usi più specifici. Questi non sono suddivisi in due metà.
Registri Indice
Questi sono a volte chiamati registri puntatore e sono registri a 16-bit. Sono utilizzati
principalmente con le istruzioni che operano con le stringhe di caratteri. Vi sono tre registri indice:
SI (source index), DI (destination index) e IP (instruction pointer). Con il processore 386 e superiori
vi sono anche registri indice a 32-bit: EDI e ESI. Nota: Come registro indice per le stringhe si può
usare anche il registro BX.
IP è un registro indice ma non può essere manipolato direttamente poiché contiene l'indirizzo
dell'istruzione successiva durante l'esecuzione.
Registri di Stack
BP e SP sono registri di stack e sono utilizzati quando si ha a che fare con lo stack. Li vedremo in
seguito.
Segmenti e Offset
I primi progettisti del processore 8008 decisero che nessuno avrebbe mai avuto bisogno di utilizzare
più di un megabyte di memoria, così costruirono il chip in modo che non potesse indirizzare
memoria oltre il megabyte. Il problema è che per indirizzare un intero megabyte sono necessari
almeno 20 bit. I registri hanno solo 16 bit, ma i progettisti non ritennero di utilizzare due registri
perché credevano che lo spazio di indirizzamento che si otteneva con 32 bit fosse esagerato per
chiunque. Pertanto idearono quello che sembrava una soluzione intelligente per risolvere il
problema: segmento e offset. Ecco come viene ottenuto l'indirizzamento con due registri, ma senza
utilizzare 32 bit.
OFFSET = SEGMENT * 16
SEGMENT = OFFSET / 16 (the lower 4 bits are lost)
Un registro contiene il segmento e un altro registro contiene l'offset. Unendo insieme i due registri
si ottiene un indirizzo a 20-bit.
SEGMENT
0010010000010000----
OFFSET
----0100100000100010
20-bit Address 00101000100100100010
DS contiene il segmento e SI contiene l'offset. Poiché entrambi hanno dimensione 16-bit gli
indirizzi si accavallano. Nello schema è mostrato come la coppia di indirizzi DS:SI è utilizzata per
ottenere l'indirizzo di 20 bit. La notazione standard per una coppia Segmento/Offset è
SEGMENT:OFFSET.
I registri di segmento sono CS, DS, ES, SS. Con il 386 e processori successivi esistono anche FS e
GS.
I registri di offset sono BX, DI, SI, BP, SP, IP. Con il processore 386 che opera in modalità protetta,
QUALSIASI registro generale può essere utilizzato come registro di offset. (Eccetto IP, che non
può essere manipolato direttamente).
Lo Stack
Poiché esistono soltanto sei registri che
possono essere utilizzati per la maggior
parte delle operazioni, è naturale
chiedersi
come
superare
questa
apparente limitazione. E' semplice.
Esiste una speciale area di memoria,
denominata stack, in cui possono essere
salvati e recuperati i valori desiderati.
Quest'area di memoria è organizzata
come una pila di piatti. L'ultimo che
viene messo è il primo ad essere
prelevato. Questo tipo di organizzazione
è denominata Last On First Off (LOFO)
oppure Last In First Out (LIFO).
Come lo stack è organizzato
Via via che nuovi dati sono posti nello stack, questo cresce verso il basso. Come si vede dalla
figura, lo stack ha inizio ad un indirizzo alto e cresce verso il basso. E' opportuno accertarsi di non
mettere troppi dati nello stack, se non si vuole rischiare l'errore di stack overflow.
Introduzione alle istruzioni Assembly
In Assembly sono disponibili moltissime istruzioni, ma sono soltanto una ventina quelle che
bisogna assolutamente conoscere e che vengono utilizzate più spesso. La maggior parte delle
istruzioni sono identificate da un codice mnemonico di tre caratteri e sono seguite da uno o più
operandi separati da virgole. Per esempio per mettere un dato in un registro, si può utilizzare
l'istruzione MOV.
mov
mov
mov
mov
ax,10
bx,20
cx,30
dx,40
;
;
;
;
put
put
put
put
10
20
30
40
into
into
into
into
ax
bx
cx
dx
Notate che in Assembly tutto quello che viene scritto dopo il ; (punto e virgola) viene ignorato.
Questo è utile per poter commentare il codice.
Push e Pop: Due istruzioni per utilizzare lo Stack
Abbiamo già parlato dello stack, ma non di come fare ad inserire e prelevare i dati. Ci sono due
semplici istruzioni che è necessario conoscere: PUSH e POP. Ecco la sintassi per utilizzarli:
PUSH: Inserisce un dato in cima allo stack
Sintassi:
push dato
POP: Preleva un dato dallo stack e lo memorizza in uno specifico registro o variabile.
Sintassi:
pop registro (o variabile)
Questo esempio di codice mostra come utilizzare le istruzioni PUSH e POP.
push cx
push ax
pop cx
pop ax
;
;
;
;
put
put
put
put
cx on
ax on
value
value
the stack
the stack
from stack into cx
from stack into ax
Notate che i valori di CX e AX vengono scambiati. C'è un istruzione specifica per scambiare i
valori contenuti in due registri: XCHG, che può essere usato per rimpiazzare il precedente
frammento di codice con la sola istruzione "xchg ax,cx".
Tipi di operando
Ci sono tre tipi di operando in Assembly: immediati, registro, e memoria. Un operando immediato è
un valore numerico conosciuto al momento della comilazione ed è sempre costante. Un operando di
tipo registro è un valore contenuto in un registro come ad esempio AX o SI. Un operando in
memoria è un valore che viene mantenuto e acceduto in memoria.
Alcune Istruzioni da conoscere
Questo è un elenco di alcune importanti istruzioni che è necessario conoscere prima di poter
comprendere e scrivere programmi in Assembly.
MOV: copia un valore da una locazione ad un'altra.
Sintassi:
MOV destinazione, sorgente
Ad esempio:
mov ax,10
mov bx,cx
mov dx,Number
; moves an immediate value into ax
; moves value from cx into bx
; moves the value of Number into dx
INT: richiama una funzione DOS o del BIOS.
Sintassi:
INT interrupt number
Ad esempio:
int 21h
int 10h
; Calls DOS service
; Calls the Video BIOS interrupt
La maggior parte degli interrupt hanno più di una funzione. Questo significa che è necessario
fornire un numero specificatore alla funzione che si desidera richiamare. In genere questo numero
viene specificato in AH. Ad esempio per stampare un messaggio sullo schermo bisogna fare così:
mov ah,9
int 21h
; subroutine number 9
; call the interrupt
Ma prima bisogna specificare cosa stampare. Questa funzione richiede che: la coppia DS:DX
contenga il puntatore a dove si trova il contenuto della stringa. La stringa deve essere terminata con
il simbolo di dollaro ($). Questo sarebbe semplice da ottenere se il registro DS potesse essere
manipolato direttamente; bisogna invece utilizzare il registro AX nel modo seguente.
L'esempio mostra la tecnica da utilizzare:
mov
mov
mov
mov
int
dx,OFFSET Message
ax,SEG Message
ds,ax
ah,9
21h
;
;
;
;
;
DX contains offset of message
AX contains segment of message
DS:DX points to message
function 9 - display string
call dos service
Le direttive OFFSET e SEG servono per ottenere il segmento e l'offset dove si trova il messaggio, e
non il suo contenuto. Ora che sappiamo come scrivere il codice per visualizzare il messaggio, non
resta che dichiarare il messaggio. Nel segmento dati dichiariamo il messaggio nel modo seguente:
Message DB "Hello World!$"
Notate che la stringa è terminata con il simbolo di dollaro. Cosa significa 'DB'? DB è un codice
mnemonico che sta per "declare byte" e il messaggio è un array di byte (un carattere ASCII occupa
un byte). I dati possono essere dichiarati di varie dimensioni: byte (DB), word (DW) e double (DD).
Ecco alcuni esempi di dichiarazione di dati:
Number1 db ?
Number2 dw ?
Il simbolo di punto di domanda alla fine significa che il dato non è inizializzato, cioè non ha un
valore iniziale. Se si vuole specificare un valore di inizializzazione, è sufficiente scrivere:
Number1 db 0
Number2 dw 1
In questo caso Number1 assume valore 0 e Number2 è uguale a 1, quando il programma è mandato
in esecuzione.
Se si dichiara una variabile come word, non si può copiare il valore di questa variabile in un registro
a 8-bit, e allo stesso modo non si può dichiarare una variabile come byte e copiarne il contenuto in
un registro a 16-bit. Per esempio:
mov al,Number1
mov ax,Number1
; ok
; error
mov bx,Number2
mov bl,Number2
; ok
; error
Tutto quello che è necessario ricordarsi è che si può soltanto copiare byte in registri a 8-bit e word
in registri a 16-bit.
Il primo programma Assembly
Ora che abbiamo illustrato le istruzioni fondamentali e come dichiarare i dati, vediamo un
programma Assembly completo, che può essere compilato. Ci si riferisce qui al compilatore masm
tasm.
Listing 1: 1STPROG.ASM
; This is a simple program which displays "Hello World!"
; on the screen.
.model small
.stack
.data
Message db "Hello World!$"
; message to be display
.code
mov dx,OFFSET Message ; offset of Message is in DX
mov ax,SEG Message
; segment of Message is in AX
mov ds,ax
; DS:DX points to string
mov ah,9
; function 9 - display string
int 21h
; call dos service
mov ax,4c00h
; return to dos DOS
int 21h
END start
;end here
Qualche semplice esempio
Il modo in cui abbiamo specificato l'indirizzo del messaggio da stampare era un po' complicato.
Servivano ben tre linee di codice, non del tutto semplici da ricordare.
mov dx,OFFSET MyMessage
mov ax,SEG MyMessage
mov ds,ax
Possiamo rimpiazzare tutto questo con una singola linea. Questo rende il codice più semplice da
leggere e da ricordare.
mov dx,OFFSET MyMessage
Perché funzioni, bisogna anche aggiungere queste istruzioni all'inizio del programma.
mov ax,@data
mov ds,ax
Nota: per l'assemblatore tasm, la prima delle due linee è leggermente diversa:
mov ax,data
Questa tecnica si basa sul fatto che tutti i dati in un segmento hanno il medesimo SEG value.
Impostando questo valore in DS, evita di doverlo ricaricare ogni volta che desideriamo indirizzare
un altro dato nello stesso segmento.
Input da tastiera
Utilizzeremo l'interrupt 16h, funzione 00h per leggere la tastiera. Questa chiamata legge un dato dal
buffer della tastiera. Se nessun dato è presente, aspetta fino a quando non è disponibile. Restituisce
lo SCAN code in AH e la traduzione ASCII in AL.
xor ah,ah
int 16h
; function 00h - get character
; interrupt 16h
Tutto quello di cui dobbiamo preoccuparci per ora è il valore ASCII che si trova in AL.
Nota: XOR effettua un OR booleano esclusivo. E' comunemente utilizzato per azzerare un registro
o una variabile.
Stampare un carattere
Il problema è che abbiamo il tasto premuto in AH. Come facciamo a visualizzarlo? Non possiamo
usare la funzione 9h perché avremmo dovuto aver già definito una stringa terminandola con il
simbolo di dollaro. Questo è invece ciò che dobbiamo fare.
; after calling function 00h of interrupt 16h
mov dl,al
mov ah,02h
int 21h
; move al (ascii code) into dl
; function 02h of interrupt 21h
; call interrupt 21h
Se desiderate salvare il valore di AH, è necessario fare PUSH di AX prima della chiamata e
recuperarlo in seguito con POP.
Controllo del flusso
In Assembly c'è un insieme di comandi per il controllo del flusso, come in molti altri linguaggi.
Prima di tutto l'istruzione fondamentale:
jmp label
Tutto quello che fa è di saltare alla locazione specificata e continuare l'esecuzione del codice da
quel punto. Per esempio:
jmp ALabel
.
.
ALabel:
Cosa dobbiamo fare se vogliamo valutare delle condizioni? Abbiamo letto un valore dalla tastiera
ma ora vogliamo fare qualche operazione. Ad esempio stampiamo qualcosa se questo valore è
uguale ad una certa costante. Come facciamo? E' semplice. Utilizziamo comandi di salto
condizionato.
cmp ax,3
je correct
; is AX = 3?
; yes
Ecco un elenco di questi comandi:
JA
Salta se il primo operando è maggiore del secondo operando
JAE
Come sopra, ma salta anche se gli operandi hanno lo stesso valore
JB
Salta se il primo operando è minore del secondo operando
JBE
Come sopra, ma salta anche se gli operandi hanno lo stesso valore
JNA Salta se il primo operando non è maggiore del secondo operando (JBE)
JNAE Salta se il primo operando non è maggiore del o uguale al secondo operando (JNB)
JNB
Salta se il primo operando non è minore del secondo operando (JAE)
JNBE Salta se il primo operando non è minore del o uguale al secondo operando (JA)
JZ
Salta se i due operandi sono uguali
JE
Come JZ
JNZ
Salta se i due operandi NON sono uguali
JNE
Come JNZ
JC
Salta se il flag di carry è impostato
CMP: compara un registro o una variabile con un valore
Sintassi:
CMP registro o variabile, valore
jxx destination
Un esempio d'uso è il seguente:
cmp al,'Y'
je ItsYES
; compare the value in al with Y
; if it is equal then jump to ItsYES
Ogni istruzione richiede una certa quantità di spazio nel segmento codice. Si ottiene un avviso
dall'assemblatore se si cerca di saltare a locazioni lontane più di 127 byte in entrambe le direzioni.
Si può risolvere il problema, modificando una sequenza come questa:
cmp ax,10
je done
; is AX 10?
; yes, lets finish
in qualcosa come questa:
cmp ax,10
jne notdone
jmp done
notdone:
; is AX 10?
; no it is not
; we are now done
Questo risolve il problema, ma in genere è più opportuno organizzare il codice usando chiamate a
procedure.
Ora vedremo un programma che dimostra input, output e controllo del flusso.
Listing 2: PROGFLOW.ASM
; a program to demonstrate program flow and input/output
.model tiny
.code
org 100h
start:
mov dx,OFFSET Message
mov ah,9
int 21h
; display a message on the screen
; using function 09h
; of interrupt 21h
mov dx,OFFSET Prompt
mov ah,9
int 21h
; display a message on the screen
; using function 09h
; of interrupt 21h
jmp First_Time
Prompt_Again:
mov dx,OFFSET Another
mov ah,9
int 21h
; display a message on the screen
; using function 09h
; of interrupt 21h
First_Time:
mov
mov
int
xor
dx,OFFSET Again
ah,9
21h
ah,ah
;
;
;
;
display a message on the screen
using function 09h
of interrupt 21h
function 00h of
int
mov
mov
mov
int
16h
bl,al
dl,al
ah,02h
21h
;
;
;
;
;
interrupt 16h gets a character
save to bl
move al to dl
function 02h - display character
call DOS service
cmp bl,'Y'
je Prompt_Again
cmp bl,'y'
je Prompt_Again
;
;
;
;
is
if
is
if
al=Y?
yes then display it again
al=y?
yes then display it again
theEnd:
mov dx,OFFSET GoodBye
mov ah,9
int 21h
; print goodbye message
; using function 9
; of interrupt 21h
mov ah,4Ch
int 21h
; terminate program
.DATA
CR equ 13
LF equ 10
Message DB
Prompt DB
Again
DB
Another DB
GoodBye DB
end start
; enter
; line-feed
"A Simple Input/Output Program$"
CR,LF,"Here is your first prompt.$"
CR,LF,"Do you want to be prompted again? $"
CR,LF,"Here is another prompt!$"
CR,LF,"Goodbye then."
Altre istruzioni che si devono conoscere
Quello che segue è un elenco di alcuni istruzioni Assembly fondamentali che è molto importante
conoscere e che sono utilizzate molto spesso.
ADD: Aggiunge il contenuto del secondo operando al valore del primo operando
Sintassi:
ADD operando1,operando2
Questo esempio aggiunge il valore di operando2 a quello di operando1. Il risultato è memorizzato
nell'operando1. Un tipo di dato immediato non può essere utilizzato come operando1, ma può
essere utilizzato come operando2.
SUB: Sottrae il contenuto del secondo operando dal valore del primo operando
Sintassi:
SUB operand1,operand2
Questo esempio sottrae il valore di operando2 da quello di operando1. Il risultato è memorizzato
nell'operando1. Un tipo di dato immediato non può essere utilizzato come operando1, ma può
essere utilizzato come operando2.
MUL: Moltiplica due interi senza segno (sempre positivi)
IMUL: Moltiplica due interi con segno (positivi o negativi)
Sintassi:
MUL registro o variabile
IMUL registro o variabile
Questo esempio moltiplica AL o AX per il valore del registro o della variabile specificati. Se si
specifica un operando della dimensione di un byte, AL viene moltiplicato per l'operando e il
risultato è posto nel registro AX. Se invece si specifica un operando della dimensione di una word,
AX viene moltiplicato per l'operando e il risultato è posto in DX:AX.
Con i processori 386, 486 e Pentium può essere utilizzato anche il registro EAX e il risultato viene
posto in EDX:EAX.
DIV: Divide due interi senza segno (sempre positivi)
IDIV: Divide due interi con segno (positivi o negativi)
Sintassi:
DIV registro o variabile
IDIV registro o variabile
L'istruzione di divisione funziona in modo simile alla moltiplicazione, dividendo il valore contenuto
in AX per il registro o la variabile specificati. Il risultato è memorizzato in due diverse locazioni.
AL contiene il quoziente della divisione mentre il resto è posto in AH. Se l'operando è un registro a
16-bit, il numero alla locazione DX:AX viene diviso per l'operando e il quoziente e il resto sono
posti rispettivamente in AX e DX.
Introduzione alle procedure
In Assembly una procedura è l'equivalente di una funzione in C o in Pascal. Una procedura fornisce
un modo semplice per incapsulare un calcolo complesso in modo che possa essere utilizzato senza
preoccuparsi di come funziona internamente. Con procedure attentamente strutturate, è possibile
realizzare programmi di grosse dimensioni, nascondendo i dettagli implementativi e ottenendo
codice più leggibile.
Ecco come si definisce una procedura:
PROC AProcedure
.
.
; some code to do something
.
ret
; if this is not here then your computer will crash
ENDP AProcedure
Invocare una procedura è altrettanto semplice. Tutto quello che si deve fare è quanto segue:
call AProcedure
Il programma che segue è un esempio di come usare una procedura. E' simile al primo programma
visto, tutto quello che fa è stampare la frase "Hello World!" sullo schermo.
Listing 3: SIMPPROC.ASM
; This is a simple program to demonstrate procedures. It should
; print Hello World! on the screen when ran.
.model tiny
.code
org 100h
Start:
call Display_Hi
mov ax,4C00h
int 21h
; Call the procedure
; return to DOS
Display_Hi PROC
mov dx,OFFSET HI
mov ah,9
int 21h
ret
Display_Hi ENDP
HI DB "Hello World!"
; define a message
end Start
Procedure che passano parametri
Le procedure non sarebbero così utili se non si potessero passare parametri per modificarli o
utilizzarli all'interno della procedura. Vi sono tre diversi modi di passare un parametro: in un
registro, in memoria e sullo stack.
Di seguito ci sono tre esempi che svolgono tutti lo stesso compito. Stampano il carattere ASCII 254
in un punto specifico dello schermo.
Le dimensioni (in byte) dei tre programmi una volta compilati sono rispettivamente: 38 per il
parametro passato con registro, 69 per il parametro passato in memoria e 52 per il parametro
passato sullo stack.
Passare i parametri nei registri
Il vantaggio di passare i parametri nei registri è la semplicità. Basta copiare i parametri nei registri
prima di invocare la procedura.
Listing 4: PROC1.ASM
; this a procedure to print a block on the screen using
; registers to pass parameters (cursor position of where to
; print it and colour).
.model tiny
.code
org 100h
Start:
mov
mov
mov
mov
dh,4
dl,5
al,254
bl,4
;
;
;
;
row to print character on
column to print character on
ascii value of block to display
colour to display character
call PrintChar ; print our character
mov ax,4C00h
; terminate program
int 21h
PrintChar PROC NEAR
push bx
push cx
; save registers to be destroyed
xor bh,bh
mov ah,2
int 10h
; clear bh - video page 0
; function 2 - move cursor
; row and col are already in dx
pop bx
; restore bx
xor
mov
mov
int
;
;
;
;
bh,bh
ah,9
cx,1
10h
pop cx
display page - 0
function 09h write char & attrib
display it once
call bios service
; restore registers
ret
; return to where it was called
PrintChar ENDP
end Start
Passare i parametri in memoria
Il vantaggio di questo metodo è l'estrema semplicità, ma ha lo svantaggio di rendere il programma
più ingombrante e lento.
Per passare i parametri attraverso la memoria, è sufficiente copiare i parametri in variabili situate in
memoria. Si può usare una variabile nello stesso modo in cui si usa un registro, ma bisogna tenere
conto che le istruzioni che utilizzano i registri sono molto più veloci.
Listing 5: PROC2.ASM
; this a procedure to print a block on the screen using memory
; to pass parameters (cursor position of where to print it and
; colour).
.model tiny
.code
org 100h
Start:
mov
mov
mov
mov
Row,4
Col,5
Char,254
Colour,4
;
;
;
;
row to print character
column to print character on
ascii value of block to display
colour to display character
call PrintChar ; print our character
mov ax,4C00h
; terminate program
int 21h
PrintChar PROC NEAR
push ax cx bx
; save registers to be destroyed
xor
mov
mov
mov
int
bh,bh
ah,2
dh,Row
dl,Col
10h
mov
mov
xor
mov
mov
int
al,Char
bl,Colour
bh,bh
ah,9
cx,1
10h
pop bx cx ax
; clear bh - video page 0
; function 2 - move cursor
; call Bios service
;
;
;
;
display page - 0
function 09h write char & attrib
display it once
call bios service
; restore registers
ret
; return to where it was called
PrintChar ENDP
; variables to store data
Row db
Col db
Colour db
Char db
?
?
?
?
end Start
Passare i parametri sullo stack
Questo è il modo più potente e flessibile di passare i parametri. Il problema è che risulta un poco più
complicato.
Listing 6: PROC3.ASM
; this a procedure to print a block on the screen using the
; stack to pass parameters (cursor position of where to print
; it and colour).
.model tiny
.code
org 100h
Start:
mov dh,4
;
mov dl,5
;
mov al,254
;
mov bl,4
;
push dx ax bx
row to print string on
column to print string on
ascii value of block to display
colour to display character
; put parameters onto the stack
call PrintString ; print our string
pop bx ax dx
mov ax,4C00h
int 21h
;restore registers
;terminate program
PrintString PROC NEAR
push bp
mov bp,sp
push cx
; save bp
; put sp into bp
; save registers to be destroyed
xor
mov
mov
int
bh,bh
; clear bh - video page 0
ah,2
; function 2 - move cursor
dx,[bp+8]
; restore dx
10h
; call bios service
mov
mov
xor
mov
mov
int
ax,[bp+6]
bx,[bp+4]
bh,bh
;
ah,9
;
cx,1
;
10h
;
pop cx
pop bp
; character
; attribute
display page - 0
function 09h write char & attrib
display it once
call bios service
; restore registers
ret
; return to where it was called
PrintString ENDP
end Start
Stack di una procedura con due parametri
Per prelevare un parametro dallo stack, tutto quello che serve fare è conoscere l'indirizzo dove si
trova. L'ultimo parametro si trova all'indirizzo BP+2 e il precedente all'indirizzo BP+4
Cosa sono i "Memory Models"?
In molti esempi abbiamo utilizzato la direttiva .MODEL per specificare il modello di memoria da
utilizzare. Cosa significa?
Sintassi:
.MODEL MemoryModel
Dove MemoryModel può essere SMALL, COMPACT, MEDIUM, LARGE, HUGE, TINY o
FLAT.
Tiny
Significa che esiste un unico segmento per codice e dati. Questo tipo di programma può
essere un eseguibile .COM.
Small
Significa che per default tutto il codice si trova in un unico segmento e tutti i dati dichiarati
si trovano in un altro solo segmento. Questo implica che tutte le procedure e le variabili
sono indirizzate come NEAR utilizzando solo gli offset.
Compact
Significa che per default tutto il codice si trova in un unico segmento ma i dati possono
essere dichiarati in più segmenti diversi. Questo implica che i dati sono indirizzati
utilizzando sia il segmento sia l'offset. Gli elementi di codice (procedure) sono NEAR e le
variabili sono FAR.
Medium
E' l'opposto di compact. I dati sono NEAR e le procedure sono FAR.
Large
Questo significa che sia le procedure sia le variabili sono FAR. Bisogna indirizzarle
entrambe utilizzando sia il segmento, sia l'offset.
Flat
Questo modello di memoria si usa quando il processore opera in modalità protetta e la
memoria non è segmentata. E' utilizzabile solo con sistemi operativi a 32-bit, come
Windows 9x e Windows NT.
Macro (in Turbo Assembler)
Nota: Tutti gli esempi sono in Turbo Assembler.
Le macro sono molto utili per raggruppare sequenze di istruzioni usate frequentemente, per le quali
non è opportuno realizzare una procedura. Le macro sono sostituite al momento della compilazione
con il codice che contengono.
Questa è la sintassi per definire una macro:
Name_of_macro macro
;
;
a sequence of instructions
;
endm
Questi due esempi illustrano le macro per salvare e ripristinare di gruppi di registri:
SaveRegs macro
push
push
push
push
ax
bx
cx
dx
endm
RestoreRegs macro
pop
pop
pop
pop
dx
cx
bx
ax
endm
Notate che i registri sono prelevati in ordine inverso a quello con cui sono stati salvati nello stack.
Per usare una macro in un programma è sufficiente utilizzare il nome della macro come una
qualsiasi istruzione.
SaveRegs
; some other instructions
RestoreRegs
Questo esempio mostra come si può usare una macro per risparmiare righe di codice. Questa macro
stampa semplicemente un messaggio sullo schermo.
OutMsg macro SomeText
local PrintMe,SkipData
jmp SkipData
PrintMe db SomeText,'$'
SkipData:
push ax dx cs
mov dx,OFFSET cs:PrintMe
mov ah,9
int 21h
pop cs dx ax
endm
L'unico problema con le macro è che usandole troppo spesso, la dimensione del codice del
programma tende ad aumentare e si rischia di incontrare errori di duplicazione delle label e di nomi
delle variabili. Il modo corretto di risolvere questo problema è usare la direttiva LOCAL che
consente di dichiarare nomi che valgono solo all'inerno delle macro.
Sintassi:
LOCAL name
Dove name è il nome di una variabile locale o di una label.
Macro con parametri
Un'altra utile proprietà delle macro è che possono avere parametri. Il numero di parametri è limitato
solo dalla lunghezza della linea di codice.
Sintassi:
Name_of_Macro macro par1,par2,par3
;
; commands go here
;
endm
Questo esempio somma il primo e il secondo parametro e pone il risultato nel terzo:
AddMacro macro num1,num2,result
push ax
; save ax from being destroyed
mov ax,num1 ; put num1 into ax
add ax,num2 ; add num2 to it
mov result,ax
; move answer into result
pop ax
; restore ax
endm
Funzioni che operano sui file
I file possono essere aperti, letti e scritti. Il DOS ha diversi modi per fare queste operazioni, che
risparmiano la necessità di dover scrivere ogni volta routine ad hoc. Quella che segue è una lista di
utili funzioni dell'interrupt 21h che operano con i file.
Nota: I bit sono numerati da destra verso sinistra.
Function 3Dh: open file
Apre un file esistente in lettura, scrittura o in aggiunta, con il nome e drive specificati.
INPUT:
AH = 3Dh
AL = bits 0-2 Access mode
000 = read only
001 = write only
010 = read/write
bits 4-6 Sharing mode (DOS 3+)
000 = compatibility mode
001 = deny all
010 = deny write
011 = deny read
100 = deny none
DS:DX = segment:offset of ASCIIZ pathname
OUTPUT:
CF = 0 function is succesful
AX = handle
CF = 1 error has occured
AX = error code
01h missing file sharing software
02h file not found
03h path not found or file does not exist
04h no handle available
05h access denied
0Ch access mode not permitted
Cosa significa ASCIIZ? Una stringa ASCIIZ è una stringa di caratteri ASCII terminata con uno
zero anziché con il simbolo di dollaro.
Importante: Ricordatevi di salvare l'handle del file, poiché è necessario per le operazioni successive.
Come salvare gli handle dei file
E' importante salvare l'handle del file perché serve per fare qualsiasi operazione con il file. Come si
fa? Ci sono due metodi utilizzabili: copiare l'handle in un registro e rinunciare a utilizzarlo finché il
file è in uso, oppure copiarlo in una locazione di memoria.
Gli svantaggi con il primo metodo è che bisogna ricordarsi di non utilizzare il registro utilizzato per
contenere l'handle, e che viene sprecato un registro per qualcosa di più utile. Perciò utilizzeremo il
secondo metodo. Ecco come fare:
FileHandle DW 0
.
.
.
mov FileHandle,ax
; use this for saving the file handle
; save the file handle
Function 3Eh: close file
Chiude un file che è stato aperto.
INPUT:
AX = 3Eh
BX = file handle
OUTPUT:
CF = 0 function is successful
AX = destroyed
CF = 1 function not successful
AX = error code - 06h file not opened or unauthorised handle.
Importante: Non chiamate questa funzione con un handle di valore zero, poiché in questo caso
verrebbe chiuso lo standard input (la tastiera) e non sareste più in grado di immettere dati dalla
tastiera.
Function 3Fh: read file/device
Legge byte da un file o da un device in un buffer di memoria.
INPUT:
AH = 3Fh
BX = handle
CX = number of bytes to be read
DS:DX = segment:offset of a buffer
OUTPUT:
CF = 0 function is successful
AX = number of bytes read
CF = 1 an error has occurred
05h access denied
06h illegal handle or file not opened
Se CF = 0 e AX = 0, significa che il puntatore si trova già alla fine del file e non si possono leggere
altri dati. Se CF = 0 e AX è minore di CX, significa che soltanto una parte dei byte desiderati è stata
letta perché è stata raggiunta la fine del file o si è verificato un errore.
Questa funzione può essere anche utilizzata per leggere l'input dalla tastiera. Usando zero come
handle, la lettura ha termine dopo il primo carriage return incontrato o dopo che un numero
prefissato di byte è stato letto. Questo è un modo semplice ed efficace per permettere all'utente di
inserire soltanto un certo numero di caratteri.
Listing 7: READFILE.ASM
; a program to demonstrate creating a file and then reading from
; it
.model small
.stack
.code
mov ax,@data
mov ds,ax
mov
mov
mov
int
dx,OFFSET FileName
; put address of filename in dx
al,2
; access mode - read and write
ah,3Dh
; function 3Dh -open a file
21h
; call DOS service
mov Handle,ax
jc ErrorOpening
mov
mov
mov
mov
int
; base address of data segment
; put this in ds
; save file handle for later
; jump if carry flag set - error!
dx,offset Buffer
; address of buffer in dx
bx,Handle
; handle in bx
cx,100
; amount of bytes to be read
ah,3Fh
; function 3Fh - read from file
21h
; call dos service
jc ErrorReading
; jump if carry flag set - error!
mov bx,Handle
mov ah,3Eh
int 21h
; put file handle in bx
; function 3Eh - close a file
; call DOS service
mov
mov
xor
mov
cx,100
; length of string
si,OFFSET Buffer
; DS:SI - address of string
bh,bh
; video page - 0
ah,0Eh
; function 0Eh - write character
NextChar:
lodsb
int 10h
loop NextChar
mov ax,4C00h
int 21h
; AL = next character in string
; call BIOS service
; terminate program
ErrorOpening:
mov
mov
int
mov
int
dx,offset OpenError ; display an error
ah,09h
; using function 09h
21h
; call DOS service
ax,4C01h
; end program with an errorlevel =1
21h
ErrorReading:
mov dx,offset ReadError ; display an error
mov ah,09h
; using function 09h
int 21h
; call DOS service
mov ax,4C02h
; end program with an errorlevel =2
int 21h
.data
Handle DW ?
; to store file handle
FileName DB "C:\test.txt",0
; file to be opened
OpenError DB "An error has occured(opening)!$"
ReadError DB "An error has occured(reading)!$"
Buffer DB 100 dup (?)
; buffer to store data
END
Function 3Ch: Create file
Crea un file vuoto sul drive e con il nome specificati.
INPUT:
AH = 3Ch
CX = file attribute
bit 0 = 1 read-only file
bit 1 = 1 hidden file
bit 2 = 1 system file
bit 3 = 1 volume (ignored)
bit 4 = 1 reserved (0) - directory
bit 5 = 1 archive bit
bits 6-15 reserved (0)
DS:DX = segment:offset of ASCIIZ pathname
OUTPUT:
CF = 0 function is successful
AX = handle
CF = 1 an error has occurred
03h path not found
04h no available handle
05h access denied
Importante: Se esiste già un file con lo stesso nome, il file esistente viene perso. Perciò è opportuno
assicurarsi che non esista già un file con lo stesso nome. Ciò può essere fatto con la funzione che
segue.
Function 4Eh: find first matching file
Cerca il primo file con il nome specificato.
INPUT:
AH = 4Eh CX = file attribute mask (bits can be combined)
bit 0 = 1 read only
bit 1 = 1 hidden
bit 2 = 1 system
bit 3 = 1 volume label
bit 4 = 1 directory
bit 5 = 1 archive
bit 6-15 reserved
DS:DX = segment:offset of ASCIIZ pathname
OUTPUT:
CF = 0 function is successful
[DTA] Disk Transfer Area = FindFirst data block
The DTA block:
Offset
Size in bytes
Meaning
0
21
22
24
26
30
21
1
2
2
4
13
Reserved
File attributes
Time last modified
Date last modified
Size of file (in bytes)
File name (ASCIIZ)
Un esempio di come verificare l'esistenza di un file:
File DB "C:\file.txt",0
mov dx,OFFSET File
mov cx,3Fh
mov ah,4Eh
int 21h
;
;
;
;
; name of file that we want
address of filename
file mask 3Fh - any file
function 4Eh - find first file
call DOS service
jc NoFile
; print message saying file exists
NoFile:
; continue with creating file
Questo è un esempio per creare un file e scriverci dei dati.
Listing 8: CREATE.ASM
; This example program creates a file and then writes to it.
.model small
.stack
.code
mov
mov
mov
mov
int
ax,@data
; base address of data segment
ds,ax
; put it in ds
dx,offset StartMessage
ah,09h
21h
mov
xor
mov
int
dx,offset FileName
; put offset of filename in dx
cx,cx
; clear cx - make ordinary file
ah,3Ch
; function 3Ch - create a file
21h
; call DOS service
jc CreateError
mov dx,offset FileName
; jump if there is an error
; put offset of filename in dx
mov al,2
mov ah,3Dh
int 21h
; access mode -read and write
; function 3Dh - open the file
; call dos service
jc OpenError
mov Handle,ax
; jump if there is an error
; save value of handle
mov
mov
mov
mov
int
;
;
;
;
;
dx,offset WriteMe
bx,Handle
cx,38
ah,40h
21h
address of information to write
file handle for file
38 bytes to be written
function 40h - write to file
call dos service
jc WriteError
cmp ax,cx
jne WriteError
; jump if there is an error
; was all the data written?
; no it wasn't - error!
mov bx,Handle
mov ah,3Eh
int 21h
; put file handle in bx
; function 3Eh - close a file
; call dos service
mov dx,offset EndMessage
mov ah,09h
int 21h
ReturnToDOS:
mov ax,4C00h
int 21h
; terminate program
WriteError:
mov dx,offset WriteMessage
jmp EndError
OpenError:
mov dx,offset OpenMessage
jmp EndError
CreateError:
mov dx,offset CreateMessage
EndError:
mov ah,09h
int 21h
mov ax,4C01h
int 21h
.data
CR equ 13
LF equ 10
StartMessage DB "This program creates a file called NEW.TXT"
DB ,"on the C drive.$"
EndMessage DB CR,LF,"File create OK, look at file to"
DB ,"be sure.$"
WriteMessage DB "An error has occurred (WRITING)$"
OpenMessage
DB "An error has occurred (OPENING)$"
CreateMessage DB "An error has occurred (CREATING)$"
WriteMe DB "HELLO, THIS IS A TEST, HAS IT WORKED?",0
FileName DB "C:\new.txt",0 ; name of file to open
Handle
DW ?
; to store file handle
END
Questo è un esempio di come cancellare un file dopo aver verificato che esiste:
Listing 9: DELFILE.ASM
; a demonstration of how to delete a file. The file new.txt on
; c: is deleted (this file is created by create.exe). We also
; check if the file exits before trying to delete it
.model small
.stack
.data
CR equ 13
LF equ 10
File
Deleted
NoFile
ErrDel
db
db
db
db
"C:\new.txt",0
"Deleted file c:\new.txt$"
"c:\new.txt doesn't exits - exiting$"
"Can't delete file - probably write protected$"
.code
mov ax,@data
mov ds,ax
mov dx,OFFSET File
mov cx,3Fh
mov ah,4Eh
int 21h
jc FileDontExist
;
;
;
;
address of filename to look for
file mask 3Fh - any file
function 4Eh - find first file
call dos service
mov dx,OFFSET File
mov ah,41h
int 21h
jc ErrorDeleting
;
;
;
;
DS:DX points to file to be killed
function 41h - delete file
call DOS service
jump if there was an error
mov dx,OFFSET Deleted
jmp Endit
; display message
ErrorDeleting:
mov dx,OFFSET ErrDel
jmp Endit
FileDontExist:
mov dx,OFFSET NoFile
EndIt:
mov ah,9
int 21h
mov ax,4C00h
int 21h
end
; terminate program and exit to DOS
; call DOS service
Usare le funzioni FindFirst e FindNext
Listing 10: DIRC.ASM
; this program demonstrates how to look for files. It prints
; out the names of all the files in the c:\drive and names of
; the sub-directories
.model small
.stack
.data
FileName db "c:\*.*",0 ;file name
DTA
db 128 dup(?) ;buffer to store the DTA
ErrorMsg db "An Error has occurred - exiting.$"
.code
mov ax,@data
mov ds,a
mov es,ax
; set up ds to be equal to the
; data segment
; also es
mov dx,OFFSET DTA
mov ah,1AH
int 21h
mov
mov
mov
int
; DS:DX points to DTA
; function 1Ah - set DTA
; call DOS service
cx,3Fh
; attribute mask - all files
dx,OFFSET FileName
; DS:DX points ASCIZ filename
ah,4Eh
; function 4Eh - find first
21h
; call DOS service
jc error
; jump if carry flag is set
LoopCycle:
mov dx,OFFSET FileName
; DS:DX points to file name
mov ah,4Fh
; function 4fh - find next
int 21h
; call DOS service
jc exit
mov
mov
xor
mov
; exit if carry flag is set
cx,13
si,OFFSET DTA+30
bh,bh
ah,0Eh
;
;
;
;
length of filename
DS:SI points to filename in DTA
video page - 0
function 0Eh - write character
NextChar:
lodsb
int 10h
; AL = next character in string
; call BIOS service
loop NextChar
mov
mov
xor
rep
di,OFFSET DTA+30
cx,13
al,al
stosb
jmp LoopCycle
;
;
;
;
ES:DI points to DTA
length of filename
fill with zeros
erase DTA
; continue searching
error:
mov dx,OFFSET ErrorMsg
mov ah,9
int 21h
exit:
mov ax,4C00h
int 21h
end
; exit to DOS
; display error message
Istruzioni per le stringhe
In Assembly vi sono diverse utili istruzioni per operare con le stringhe. Segue l'elenco delle
istruzioni e la sintassi per utilizzarle:
MOV* Move String: copia un byte, una word o una double word dall'indirizzo DS:SI all'indirizzo
ES:DI
Sintassi:
movsb
movsw
movsd
; move byte
; move word
; move double word
CMPS* Compare string: confronta un byte, una word o una double word all'indirizzo DS:SI con un
byte, una word o una double word all'indirizzo ES:DI
Sintassi:
cmpsb
cmpsw
cmpsd
; compare byte
; compare word
; compare double word
Nota: Questa istruzione è normalmente utilizzata con il prefisso REP.
SCAS* Search string: cerca il valore in AL, AX o EAX nella stringa all'indirizzo ES:DI
Sintassi:
scasb
scasw
scasd
; search for AL
; search for AX
; search for EAX
Nota: Questa istruzione è normalmente utilizzata con il prefisso REPZ o REPNZ.
REP Prefisso utilizzabile davanti ad un'istruzione per le stringhe per ripetere l'istruzione tante volte
quanto specificato nel registro CX.
Sintassi:
rep StringInstruction
STOS* Copia un byte, una word o una double word da AL, AX o EAX all'indirizzo ES:DI
Sintassi:
stosb
stosw
stosd
; move AL into ES:DI
; move AX into ES:DI
; move EAX into ES:DI
LODS* Copia un byte, una word o una double word dall'indirizzo ES:DI in AL, AX o EAX
Sintassi:
lodsb
lodsw
lodsd
; move ES:DI into AL
; move ES:DI into AX
; move ES:DI into EAX
L'esempio successivo mostra come usare queste istruzioni.
Listing 11: STRINGS.ASM
.model small
.stack
.code
mov
mov
mov
mov
mov
int
ax,@data
; ax points to of data segment
ds,ax
; put it into ds
es,ax
; put it in es too
ah,9
; function 9 - display string
dx,OFFSET Message1
; ds:dx points to message
21h
; call dos function
cld
mov si,OFFSET String1
; clear direction flag
; make ds:si point to String1
mov di,OFFSET String2
; make es:di point to String2
mov cx,18
; length of strings
rep movsb
; copy string1 into string2
mov ah,9
; function 9 - display string
mov dx,OFFSET Message2
; ds:dx points to message
int 21h
; call dos function
mov dx,OFFSET String1
; display String1
int 21h
; call DOS service
mov dx,OFFSET Message3
; ds:dx points to message
int 21h
; call dos function
mov dx,OFFSET String2
; display String2
int 21h
; call DOS service
mov si,OFFSET Diff1
; make ds:si point to Diff1
mov di,OFFSET Diff2
; make es:di point to Diff2
mov cx,39
; length of strings
repz cmpsb
; compare strings
jnz Not_Equal
; jump if they are not the same
mov ah,9
; function 9 - display string
mov dx,OFFSET Message4
; ds:dx points to message
int 21h
; call dos function
jmp Next_Operation
Not_Equal:
mov ah,9
; function 9 - display string
mov dx,OFFSET Message5 ; ds:dx points to message
int 21h
; call dos function
Next_Operation:
mov di,OFFSET SearchString
; make es:di point to string
mov cx,36
; length of string
mov al,'H'
; character to search for
repne scasb
; find first match
jnz Not_Found
mov
mov
int
jmp
ah,9
; function 9 - display string
dx,OFFSET Message6
; ds:dx points to message
21h
; call dos function
Lodsb_Example
Not_Found:
mov ah,9
; function 9 - display string
mov dx,OFFSET Message7
; ds:dx points to message
int 21h
; call dos function
Lodsb_Example:
mov ah,9
; function 9 - display string
mov dx,OFFSET NewLine
; ds:dx points to message
int 21h
; call dos function
mov
mov
xor
mov
cx,17
; length of string
si,OFFSET Message
; DS:SI - address of string
bh,bh
; video page - 0
ah,0Eh
; function 0Eh - write character
NextChar:
lodsb
; AL = next character in string
int 10h
loop NextChar
; call BIOS service
mov ax,4C00h
int 21h
; return to DOS
.data
CR equ 13
LF equ 10
NewLine db CR,LF,"$"
String1 db "This is a string!$"
String2 db 18 dup(0)
Diff1
db "This string is nearly the same as Diff2$"
Diff2
db "This string is nearly the same as Diff1$"
Equal1
db "The strings are equal$"
Equal2
db "The strings are not equal$"
Message db "This is a message"
SearchString db "1293ijdkfjiu938uHello983fjkfjsi98934$"
Message1
Message2
Message3
Message4
Message5
Message6
Message7
db
db
db
db
db
db
db
"Using String instructions example program.$"
CR,LF,"String1 is now: $"
CR,LF,"String2 is now: $"
CR,LF,"Strings are equal!$"
CR,LF,"Strings are not equal!$"
CR,LF,"Character was found.$"
CR,LF,"Character was not found.$"
end
Scoprire la versione del DOS
In molti programmi è necessario scoprire la versione del DOS. Questo perché si sta utilizzando una
funzione DOS che è disponibile soltanto da una certa versione in poi.
Per conoscere la versione è sufficiente fare come segue:
mov ah,30h ; function 30h - get MS-DOS version
int 21h
; call DOS function
Questa funzione restituisce il major number di versione in AL e il minor number in AH. Per
esempio se la versione fosse 4.01, AL varrebbe 4 e AH varrebbe 01. Il problema è che dal DOS 5 in
poi, il comando SETVER può modificare la versione che deve essere restituita. Il modo per aggirare
il problema è il seguente.
mov ah,33h ; function 33h - actual DOS version
mov al,06h ; subfunction 06h
int 21h
; call interrupt 21h
Questo frammento di codice restituisce la versione corrente del DOS, indipendentemente dal fatto
che sia stato utilizzato SETVER per modificare il numero di versione. La major version viene posta
in BL e la minor version in BH. Ovviamente funziona solo con le versioni del DOS dalla 5 in poi.
Push e Pop multipli
Si possono inserire e prelevare dallo stack più di un registro con una sola linea di codice, sia con
TASM, sia con MASM. Questo rende il codice più semplice da comprendere.
push ax bx cx dx
pop dx cx bx ax
; save registers
; restore registers
Quando TASM e A86 compilano queste linee, le traducono in istruzioni di PUSH e POP separate.
Questa notazione server soltanto per maggior concisione e comprensibilità.
Nota: Perché le istruzioni mostrate sopra siano compilate correttamente da A86, bisogna separare i
registri con una virgola (,).
Le istruzioni PUSHA/PUSHAD e POPA/POPAD
PUSHA è un'utile istruzione che inserisce nello stack tutti i registri general-purpose. In pratica
PUSHA corrisponde alle seguenti istruzioni:
temp
push
push
push
push
push
push
push
push
= SP
ax
cx
dx
bx
temp
bp
si
di
Il vantaggio principale è di essere un comando molto conciso. Inoltre viene eseguito molto più
velocemente delle corrispettive istruzioni. POPA esegue la funzione inversa, prelevando tutti questi
registri dallo stack. PUSHAD e POPAD svolgono le stesse funzioni di PUSHA e POPA, ma
utilizzando i registri a 32-bit: ESP, EAX, ECX, EDX, EBX, EBP, ESI e EDI.
Usare lo Shift per moltiplicazione e divisione
Le istruzioni MUL e DIV sono molto lente e dovrebbero essere utilizzate solo quando la velocità
non è una richiesta. Per effettuare moltiplicazioni e divisioni più velocemente si possono utilizzare
gli operatori di shift a destra o a sinistra. Ogni operazione di shift moltiplica o divide per una
potenza di 2. Funziona allo stesso modo degli operatori << e >> del linguaggio C.
Ci sono quattro differenti modi di utilizzare l'istruzione di shift.
SHL Moltiplica per due un operando senza segno
SHR Divide per due un operando senza segno
SAR Divide per due un operando con segno
SAL Moltiplica per due un operando con segno
La sintassi è identica per tutti e quattro le modalità:
SHL operando1,operando2
Nota: Con il processore 8086 operando2 può assumere solo il valore 1. Con il processore 286/386 il
valore di operando2 deve essere minore o uguale di 31.
Cicli
Usare l'istruzione Loop è preferibile rispetto ad utilizzare un JMP con label. Si imposta il numero di
ripetizioni da effettuare nel registro CX ed ogni volta che il programma incontra l'istruzione Loop,
CX viene automaticamente decrementato e viene fatto un salto alla label specificata come
argomento. Il salto deve essere limitato a 127 byte in avanti o 128 byte all'indietro.
Sintassi:
mov cx,100 ; 100 times to loop
Label:
.
Loop Label:
; decrement CX and loop to Label
Questo produce esattamente lo stesso risultato del codice seguente:
mov cx,100 ; 100 times to loop
Label:
dec cx
; CX = CX-1
jnz Label ; continue until done
Quale ritenete più comprensibile? Utilizzare DEC/JNZ risulta essere più veloce dal 486 in poi ed è
utile quando non è possibile utilizzare il registro CX.
L'istruzione JNZ salta se il flag zero non è impostato. Azzerando il registro CX, il flag zero viene
impostato.
Come usare un debugger
E' tempo di utilizzare un debugger per capire cosa un programma sta effettivamente facendo. A
questo scopo si può usare il Turbo Debugger. Ma prima abbiamo bisogno di un programma su cui
lavorare.
Listing 12: DEBUG.ASM
; example program to demonstrate how to use a debugger
.model tiny
.code
org 100h
start:
push ax
push bx
push cx
; save value of ax
; save value of bx
; save value of cx
mov ax,10
mov bx,20
mov cx,3
; first parameter is 10
; second parameter is 20
; third parameter is 3
Call ChangeNumbers
pop cx
pop bx
pop ax
; restore cx
; restore bx
; restore dx
mov ax,4C00h
int 21h
; exit to dos
ChangeNumbers PROC
add ax,bx
; adds number in bx to ax
mul cx
; multiply ax by cx
mov dx,ax
; return answer in dx
ret
ChangeNumbers ENDP
end start
Ora compiliamolo come eseguibile .COM e poi digitiamo:
td name of file
Il Turbo Debugger viene eseguito, e vengono mostrate le istruzioni che compongono il nostro
programma.
Per esempio le prime linee del programma vengono mostrate come:
cs:0000 50
cs:0001 53
cs:0002 51
push ax
push bx
push cx
Alcune combinazioni di tasti per il Turbo Debugger:
F5
F7
F9
ALT F4
Size Window
Next Instruction
Run
Step Backwards
I numeri che vengono copiato nei registri sono differenti da quelli del codice sorgente perché sono
rappresentati in forma esadecimale (base 16) poiché questa notazione è semplice da convertire in
forma binaria, ed è anche più concisa e comprensibile.
Sulla destra dello schermo c'è una finestra che mostra i valori contenuti dei registri. In questo
momento tutti i registri principali sono vuoti. Premendo F7 per eseguire la prima linea del
programma. Poiché la prima linea inserisce il valore del registro AX nello stack, si vede che lo
stack pointer (SP) viene modificato. Continuate a premere F7 fino ad arrivare alla linea mov
ax,000A, e oltrepassatela.
Ora, guardando nella finestra che mostra il contenuto dei registri, si vede che AX contiene A.
Procedendo passo passo, BX assume il valore 14 e CX assume il valore 3. Poi AX assume il valore
1E che è A+14, e così via istruzione dopo istruzione. Infine CX, BX e AX sono impostati al loro
valore iniziale di zero.
Altre operazioni di output in modalità testo
Vi sono altri modi per ottenere l'output in modalità testo. Questo primo programma è un esempio di
come muovere il cursore per visualizzare una stringa di testo in una posizione a piacere.
Listing 12: TEXT1.ASM
.model tiny
.code
org 100h
start:
mov
mov
mov
xor
int
dh,12
dl,32
ah,02h
bh,bh
10h
;
;
;
;
;
cursor col
cursor row
move cursor to the right place
video page 0
call bios service
mov dx,OFFSET Text
mov ah,9
int 21h
; DS:DX points to message
; function 9 - display string
; all dos service
mov ax,4C00h
int 21h
; exit to dos
Text DB "This is some text$"
end start
L'esempio successivo mostra come scrivere sullo schermo utilizzando la funzione 40h dell'interrupt
21h.
Listing 13: TEXT2.ASM
.model small
.stack
.code
mov ax,@data
mov ds,ax
; set up ds as the segment for data
mov
mov
mov
mov
int
;
;
;
;
;
ah,40h
bx,1
cx,17
dx,OFFSET Text
21h
mov ax,4C00h
int 21h
function 40h - write file
handle = 1 (screen)
length of string
DS:DX points to string
call DOS service
; terminate program
.data
Text DB "This is some text"
end
Il programma che segue mostra come impostare e richiamare la funzione 13h dell'interrupt 10h "write string". Questa funzione ha la caratteristica di poter scrivere una stringa in un posto a piacere
sullo schermo con uno specifico colore. Tuttavia è complessa da impostare.
Listing 14: TEXT3.ASM
.model small
.stack
.code
mov ax,@data
mov es,ax
; set up ds as the segment for data
; put this in es
mov
mov
mov
xor
mov
;
;
;
;
;
bp,OFFSET Text
ah,13h
al,01h
bh,bh
bl,5
ES:BP points to message
function 13 - write string
attrib in bl,move cursor
video page 0
attribute - magenta
mov
mov
mov
int
cx,17
dh,5
dl,5
10h
;
;
;
;
mov ax,4C00h
int 21h
length of string
row to put string
column to put string
call BIOS service
; return to DOS
.data
Text DB "This is some text"
end
Il programma successivo mostra come scrivere sullo schermo usando rep stosw per scrivere nella
memoria video.
Listing 15: TEXT4.ASM
.model small
.stack
.code
mov
mov
xor
mov
mov
mov
cld
rep
ax,0B800h
es,ax
di,di
ah,4
al,"G"
cx,4000
stosw
mov ax,4C00h
int 21h
;
;
;
;
;
;
;
;
segment of video buffer
put this into es
clear di, ES:DI points to video memory
attribute - red
character to put there
amount of times to put it there
direction - forwards
output character at ES:[DI]
; return to DOS
end
Il programma successivo mostra come scrivere una stringa nella memoria video.
Listing 15: DIRECTWR.ASM
; write a string direct to video memory
.model small
.stack
.code
mov ax,@data
mov ds,ax
mov
mov
mov
mov
mov
xor
ax,0B800h
es,ax
ah,3
cx,17
si,OFFSET Text
di,di
;
;
;
;
;
segment of video buffer
put this into es
attribute - cyan
length of string to print
DX:SI points to string
lodsb
mov es:[di],al
inc di
mov es:[di],ah
inc di
;
;
;
;
put next character into al
output character to video memory
move along to next column
output attribute to video memory
loop Wr_Char
; loop until done
Wr_Char:
mov ax,4C00h
int 21h
; return to DOS
.data
Text DB "This is some text"
end
Modalità 13h
La modalità 13h è disponibile soltanto sulle schede VGA, MCGA e superiori.
Verificare che la modalità 13h sia consentita
E' opportuno comunicare all'utente se il suo computer non è in grado di supportare la modalità 13h,
invece di lasciare che il programma vada in crash. Ecco come fare.
Listing 16: CHECK13.ASM
.model small
.stack
.data
NoSupport db "Mode 13h is not supported on this computer."
db ,"You need either a MCGA or VGA video"
db ,"card/monitor.$"
Supported db "Mode 13h is supported on this computer.$"
.code
mov ax,@data
mov ds,ax
; set up DS to point to data segment
; use ax
call Check_Mode_13h
; check if mode 13h is possible
jc Error
; if cf=1 there is an error
mov ah,9
; function 9 - display string
mov dx,OFFSET Supported ; DS:DX points to message
int 21h
; call DOS service
jmp To_DOS
; exit to DOS
Error:
mov ah,9
; function 9 - display string
mov dx,OFFSET NoSupport ; DS:DX points to message
int 21h
; call DOS service
To_DOS:
mov ax,4C00h
int 21h
; exit to DOS
Check_Mode_13h PROC
; Returns: CF = 1 Mode 13h not possible
mov ax,1A00h
int 10h
cmp al,1Ah
je Mode_13h_OK
stc
;
;
;
;
;
Request video info for VGA
Get Display Combination Code
Is VGA or MCGA present?
mode 13h is supported
mode 13h isn't supported CF=1
Mode_13h_OK:
ret
Check_Mode_13h ENDP
end
E' sufficiente includere questo codice per verificare se la modalità 13h è supportata, per essere certi
di poterla utilizzare.
Impostare la modalità video
Impostare la modalità è molto semplice. Ecco come fare.
mov ax,13h
int 10h
; set mode 13h
; call BIOS service
Una volta che ci troviamo in modalità 13h e abbiamo completato le operazioni, è necessario reimpostare la modalità precedente.
Questo viene fatto in due fasi. Prima dobbiamo salvare la modalità video e poi re-impostarla al
valore originario.
VideoMode db ?
.
.
mov ah,0Fh
; function 0Fh - get current mode
int 10h
; Bios video service call
mov VideoMode,al ; save current mode
; program code here
mov al,VideoMode ; set previous video mode
xor ah,ah
; clear ah - set mode
int 10h
; call bios service
mov ax,4C00h
int 21h
; exit to dos
; call dos function
Ora che sappiamo come entrare in modalità 13h, facciamo qualche operazione. Per prima cosa
scriviamo alcuni pixel sullo schermo.
Funzione 0Ch: Write Graphics Pixel
Questa funzione imposta un punto colorato sullo schermo alle coordinate grafiche specificate.
INPUT:
AH = 0Ch
AL = Colore del punto
CX = Colonna dello schermo (coordinata x)
DX = Riga dello schermo (coordinata y)
OUTPUT:
Nulla eccetto il pixel sullo schermo.
Questo programma dimostra come disegnare dei punti. Disegna quattro pixel rossi nel mezzo dello
schermo.
Listing 17: PIXINT.ASM
; example of plotting pixels in mode 13 using bios services ; INT 10h
.model tiny
.code
org 100h
start:
mov ax,13
int 10h
; mode = 13h
; call bios service
mov
mov
mov
mov
int
ah,0Ch
al,4
cx,160
dx,100
10h
;
;
;
;
;
function 0Ch
color 4 - red
x position = 160
y position = 100
call BIOS service
inc
int
inc
int
dec
int
dx
10h
cx
10h
dx
10h
;
;
;
;
;
;
plot
call
plot
call
plot
call
pixel downwards
BIOS service
pixel to right
BIOS service
pixel up
BIOS service
xor ax,ax
int 16h
; function 00h - get a key
; call BIOS service
mov ax,3
int 10h
; mode = 3
; call BIOS service
mov ax,4C00h
int 21h
; exit to DOS
end start
Alcune ottimizzazioni
Questo metodo non è troppo veloce e potremmo renderlo molto più veloce. Come? Scrivendo
direttamente nella memoria video. Ciò si ottiene abbastanza facilmente.
Il segmento di memoria VGA si trova all'indirizzo 0A000h. Per conoscere l'indirizzo di ciascun
pixel, utilizzate questa semplice formula per ottenere l'offset.
Offset = X + ( Y * 320 )
Tutto quello che dobbiamo fare è scrivere un valore a questa locazione, ottenendo così un pixel. Il
numero rappresenta il colore desiderato. Ci sono due istruzioni che possiamo usare per scrivere un
pixel sullo schermo. La prima possibilità è quella di utilizzare stosb per impostare il valore nel
registro AL all'indirizzo ES:DI; la seconda possibilità è quella di sfruttare una nuova forma
dell'istruzione MOV, come segue:
mov es:[di], color
Quale sarebbe meglio utilizzare? La scelta deve essere guidata dalla velocità di esecuzione, ovvero
dal numero di cicli necessari per svolgere l'operazione. Minore è il numero di cicli, più veloce è
l'operazione.
Instruction
Pentium
486
386
286
86
STOSB
MOV AL to SEG:OFF
3
1
5
1
4
4
3
3
11
10
Se si utilizza l'istruzione MOV, bisogna ricordarsi di incrementare DI (operazione che STOSB
effettua automaticamente).
Esempi
Qui di seguito sono riportati gli esempi illustrati durante le lezioni.
Indirizzamento immediato
// INDIRIZZAMENTO IMMEDIATO
// L'operando sorgente è un valore numerico.
asm
{
// Il registro AX viene impostato a zero
XOR EAX,EAX
// Il registro AX viene caricato impostando separatamente
// la parte alta (AH) e la parte bassa (AL) con due operandi
// a 8 bit
MOV AH,00
MOV AL,04
// Il regsitro AX viene caricato con una singola istruzione
// utilizzando un operando a 16 bit
// (ogni numero esadecimale che inizia con una lettera deve
// essere preceduto dallo 0)
MOV AX,0FFFFH
// La dimensione in bit dell'operando sorgente viene adattata
// a quello dell'operando destinazione
MOV AX,302
// Lo stesso vale per operandi a 8 bit
MOV AL,-40
}
Indirizzamento a registro
// INDIRIZZAMENTO A REGISTRO
// L'operando sorgente è contenuto in uno dei registri.
asm
{
// Il contenuto di AX viene trasferito nel registro DX
XOR EAX,EAX
MOV AX,01234H
MOV DX,AX
// Si possono usare registri sorgente e destinazione a 8 bit
XOR EAX,EAX
MOV AL,056H
MOV DL,AL
}
Indirizzamento diretto
// INDIRIZZAMENTO DIRETTO
// L'operando sorgente viene prelevato all'indirizzo di memoria
// specificato. In genere l'operando è una label.
DWORD dwDato = 0x12345678; // dwDato DD 012345678H
WORD wDato = 0x1234;
// wDato DW 01234H
BYTE bDato = 0x12;
// bDato DB 012H
asm
{
// EAX viene caricato con il contenuto della locazione
// di memoria dwDato (32 bit)
XOR EAX,EAX
MOV EAX,dwDato
// AX viene caricato con il contenuto della locazione
// di memoria wDato (16 bit)
XOR EAX,EAX
MOV AX,wDato
// AL viene caricato con il contenuto della locazione
// di memoria bDato (8 bit)
XOR EAX,EAX
MOV AL,bDato
}
Indirizzamento indiretto con registro
// INDIRIZZAMENTO INDIRETTO CON REGISTRO
// L'operando sorgente viene prelevato all'indirizzo di memoria
// contenuto in un registro.
DWORD dwDato = 0x12345678; // dwDato DD 012345678H
WORD wDato = 0x1234;
// wDato DW 01234H
BYTE bDato = 0x12;
// bDato DB 012H
asm
{
// EAX viene caricato con il contenuto della locazione
// di memoria dwDato (32 bit)
XOR EAX,EAX
LEA EBX,dwDato
MOV EAX,[EBX]
// AX viene caricato con il contenuto della locazione
// di memoria wDato (16 bit)
XOR EAX,EAX
LEA EBX,wDato
MOV AX,[EBX]
// AL viene caricato con il contenuto della locazione
// di memoria bDato (8 bit)
XOR EAX,EAX
LEA BX,bDato
MOV AL,[EBX]
}
Indirizzamento relativo con registro base
// INDIRIZZAMENTO RELATIVO CON REGISTRO BASE
// L'indirizzo effettivo dell'operando sorgente viene determinato
// sommando uno spiazzamento al contenuto di un registro base.
char msg[] = "Ibis redibis non morieris";
// msg DB 'Ibis redibis non morieris','$'
asm
{
// Imposta AL con il secondo carattere del messaggio
XOR EAX,EAX
LEA EBX,msg
MOV AL,[EBX]+1
// Imposta AL con il terzo carattere del messaggio
XOR EAX,EAX
LEA EBX,msg
MOV AL,2[EBX]
// Imposta AL con il quarto carattere del messaggio
XOR EAX,EAX
LEA EBX,msg
MOV AL,[EBX+3]
}
Indirizzamento diretto con registro indice
// INDIRIZZAMENTO DIRETTO CON REGISTRO INDICE
// L'indirizzo dell'operando viene calcolato sommando un indirizzo
// di memoria al contenuto di un registro indice.
char msg[] = "Ibis redibis non morieris";
// msg DB 'Ibis redibis non morieris','$'
asm
{
// Imposta AL con il quarto carattere del messaggio
XOR EAX,EAX
MOV EBX,3
MOV AL,msg[EBX]
}
Indirizzamento con registri base e indice
// INDIRIZZAMENTO CON REGISTRI BASE E INDICE
// L'indirizzo dell'operando viene calcolato sommando il contenuto
// del registro base con il contenuto del registro indice.
char msg[] = "Ibis redibis non morieris";
// msg DB 'Ibis redibis non morieris','$'
asm
{
// Imposta AL con il quarto carattere del messaggio
XOR EAX,EAX
LEA EBX,msg
MOV ECX,3
MOV AL,[EBX][ECX]
}
Somma esadecimale con indirizzamento immediato
// SOMMA ESADECIMALE CON INDIRIZZAMENTO IMMEDIATO
// Somma di tre numeri utilizzando l'indirizzamento immediato.
asm
{
// Azzera EAX
SUB EAX,EAX
// Carica AL con il valore 23H
MOV AL,23H
// Somma 0AH ad AL
ADD AL,0AH
// Somma 10H ad AL
ADD AL,10H
// Il risultato è in AL
}
Sottrazione esadecimale con indirizzamento diretto
//
//
//
//
SOTTRAZIONE ESADECIMALE CON INDIRIZZAMENTO DIRETTO
I numeri da sottrarre vengono referenziati tramite il loro
indirizzo e vengono sottratti utilizzando la tecnica di
indirizzamento diretto.
WORD
WORD
WORD
WORD
NUM1 = 0x1234;
NUM2 = 0x24;
NUM3 = 0xABC;
RISULTATO;
//
//
//
//
NUM1 DW 1234H
NUM2 DW 24H
NUM3 DW 0ABCH
RISULTATO DW ?
asm
{
// Azzera EAX
SUB EAX,EAX
// Carica NUM1 nel registro AX
MOV AX,NUM1
// Sottrae NUM2 a NUM1
SUB AX,NUM2
// Sottrae NUM3 al risultato precedente
SUB AX,NUM3
// Memorizza il risultato in RISULTATO
MOV RISULTATO,AX
}
Somma in precisione multipla con indirizzamento diretto
// SOMMA IN PRECISIONE MULTIPLA CON INDIRIZZAMENTO DIRETTO
WORD
WORD
WORD
WORD
WORD
WORD
NUM1 = 0x1234;
NUM2 = 0x5678;
NUM3 = 0x9ABC;
NUM4 = 0xDEF0;
RIS_LSW;
RIS_MSW;
//
//
//
//
//
//
NUM1 DW
NUM2 DW
NUM3 DW
NUM3 DW
RIS_LSW
RIS_MSW
asm
{
// Azzera EAX e EBX
1234H
5678H
9ABCH
0DEF0H
DW ?
DW ?
SUB EAX,EAX
SUB EBX,EBX
// Carica il primo numero nel registro AX
MOV AX,NUM1
// Somma il secondo numero al primo
ADD AX,NUM2
// Se il Carry è a 1, viene sommato a BX
ADC BX,00H
// Somma il terzo numero al registro AX
ADD AX,NUM3
// Se il Carry è a 1, viene sommato a BX
ADC BX,00H
// Somma il quarto numero al registro AX
ADD AX,NUM4
// Se il Carry è a 1, viene sommato a BX
ADC BX,00H
// La somma AX viene copiata in RIS_LSW
MOV RIS_LSW,AX
// La somma BX viene copiata in RIS_MSW
MOV RIS_MSW,BX
}
Somma in precisione multipla con indirizzamento con registro
indice
//
//
//
//
//
SOMMA IN PRECISIONE MULTIPLA CON INDIRIZZAMENTO CON REGISTRO INDICE
- Una tabella in memoria contiene gli addendi.
- Un ciclo di programma scandisce la tabella.
- L'indirizzamento con registro indice permette di accedere
alle righe della tabella.
WORD TABELLA[] = {0x1234,
0x9ABC,
0x1111,
0x3333,
WORD RIS_LSW;
WORD RIS_MSW;
0x5678,
0xDEF0,
0x2222,
0x4444};
asm
{
// Azzera EAX, EBX, ECX, EDX
SUB EAX,EAX
SUB EBX,EBX
SUB ECX,ECX
SUB EDX,EDX
// Indirizzo di TABELLA in EBX
LEA EBX,TABELLA
// Indice a 0
MOV ESI,00H
// TABELLA DW 1234H, 5678H
//
DW 9ABCH, 0DEF0H
//
DW 1111H, 2222H
//
DW 3333H, 4444H
// RIS_LSW DW ?
// RIS_MSW DW ?
// Primo numero in AX
MOV AX,TABELLA[ESI]
// Numero di righe della tabella
MOV CX,08H
// Incrementa l'indice di una WORD (2 BYTE)
ANCORA: ADD ESI,02H
// Somma l'i-esimo elemento della tabella
ADD AX,TABELLA[ESI]
// Se il Carry = 1, viene sommato a DX
ADC DX,00H
// se CX non è 0, continua
LOOP ANCORA
// Tasferisce AX in RIS_LSW
MOV RIS_LSW,AX
// Tasferisce DX in RIS_MSW
MOV RIS_MSW,DX
}
Somma di numeri decimali con indirizzamento indiretto con
registro
// SOMMA DI NUMERI DECIMALI CON INDIRIZZAMENTO INDIRETTO CON REGISTRO
BYTE ADDENDI[] = {1,2,3,4,5,6,7,8,9,10};
// ADDENDI DB 1,2,3,4,5,6,7,8,9,10
BYTE RISULTATO; // RISULTATO DB ?
asm
{
// Azzera EAX, EBX, ECX, EDX
SUB EAX,EAX
SUB EBX,EBX
SUB ECX,ECX
SUB EDX,EDX
// Indirizzo di ADDENDI in EBX
LEA EBX,ADDENDI
// Imposta il contatore
MOV CX,10
// Primo numero in AX
MOV AL,ADDENDI
// Incrementa l'indice di 1 BYTE
AGAIN: ADD EBX,01H
// Somma l'i-esimo addendo
ADD AL,[EBX]
// Conversione decimale della somma
DAA
// se CX non è 0, continua
LOOP AGAIN
// Copia il risultato in RISULATATO
MOV RISULTATO,AL
}
Moltiplicazione per somme ripetute
// MOLTIPLICAZIONE PER SOMME RIPETUTE
BYTE MOLTIPLICANDO = 0x2A; // MOLTIPLICANDO DB 2AH
BYTE MOLTIPLICATORE = 0x78; // MOLTIPLICATORE DB 78H
WORD PRODOTTO;
// PRODOTTO
DB ?
asm
{
// Azzera EAX, EBX, ECX, EDX
SUB EAX,EAX
SUB EBX,EBX
SUB ECX,ECX
SUB EDX,EDX
// Moltiplicatore in CL
MOV CL,MOLTIPLICATORE
SUB CL,01H
// Moltiplicando in AL
MOV AL,MOLTIPLICANDO
// Somma il moltiplicando al risultato parziale
CICLO1: ADD AL,MOLTIPLICANDO
ADC AH,00H
// Se CL non è 0, continua
LOOP CICLO1
// Risultato finale in PRODOTTO
MOV PRODOTTO,AX
}
Moltiplicazione, elevamento al quadrato e al cubo con
l'istruzione di moltiplicazione
// MOLTIPLICAZIONE, ELEVAMENTO AL QUADRATO E AL CUBO CON
// L'ISTRUZIONE DI MOLTIPLICAZIONE
WORD NUM1 = 0x1234; // NUM1 DW 1234H
WORD NUM2 = 0x00CD; // NUM2 DW 00CDH
DWORD RIS1;
// RIS1 DD ?
DWORD RIS2;
// RIS2 DD ?
DWORD RIS3;
// RIS3 DD ?
asm
{
// Azzera EAX, EBX, ECX, EDX
SUB EAX,EAX
SUB EBX,EBX
SUB ECX,ECX
SUB EDX,EDX
// MOLTIPLICAZIONE
// Azzera il registro di overflow
SUB DX,DX
// NUM1 in AX
MOV AX,NUM1
// Moltiplicazione di NUM1 con NUM2
MUL NUM2
// AX in LSB di RIS1
MOV WORD PTR RIS1,AX
// DX in MSB di RIS1
MOV WORD PTR RIS1+2,DX
// ELEVAMENTO AL QUADRATO
// Azzera il registro di overflow
SUB DX,DX
// NUM1 in AX
MOV AX,NUM1
// NUM1 moltiplicato per se stesso
MUL NUM1
// AX in LSB di RIS2
MOV WORD PTR RIS2,AX
// DX in MSB di RIS2
MOV WORD PTR RIS2+2,DX
// ELEVAMENTO AL CUBO
// Azzera il registro di overflow
SUB DX,DX
// NUM2 in AX
MOV AX,NUM2
// AX = AX * NUM2
MUL NUM2
// AX = AX * NUM2
MUL NUM2
// AX in LSB di RIS3
MOV WORD PTR RIS3,AX
// DX in MSB di RIS3
MOV WORD PTR RIS3+2,DX
}
Divisione con variabili di tipo doubleword
// DIVISIONE CON VARIABILI DI TIPO DOUBLEWORD
DWORD
WORD
WORD
WORD
DIVIDENDO = 0x2A8B7654; // DIVIDENDO DD
DIVISORE = 0X5ABC;
// DIVISORE DW
QUOZIENTE;
// QUOZIENTE DW
RESTO;
// RESTO
DW
02A8B7654H
5ABCH
?
?
asm
{
// Azzera EAX, EDX
SUB EAX,EAX
SUB EDX,EDX
// LSB di DIVIDENDO in AX
MOV AX,WORD PTR DIVIDENDO
// MSB di DIVIDENDO in DX
MOV DX,WORD PTR DIVIDENDO+2
// (DX AX) diviso per DIVISORE
DIV DIVISORE
// AX in QUOZIENTE
MOV QUOZIENTE,AX
// DX in RESTO
MOV RESTO,DX
}
Estrazione di radice quadrata
// ESTRAZIONE DI RADICE QUADRATA
WORD NUMERO = 0x1CE4; // NUMERO DW 1CE4H
WORD RADICE;
// RADICE DW ?
asm
{
// Azzera EAX, ECX, EDX
SUB EAX,EAX
SUB ECX,ECX
SUB EDX,EDX
// Numero da sottrarre
MOV CX,01H
// Sottrazione dell'i-esimo numero dispari
CICLO2: SUB NUMERO,CX
// Esce dal ciclo se NUMERO < 0
JL FINE
// Incrementa il valore della radice quadrata
INC DX
// Calcola il successivo numero dispari
ADD CX,02H
// Ripete il ciclo
JMP CICLO2
FINE:
}
// Il risultato è in RADICE
MOV RADICE,DX
Tabella di lookup per logaritmi
//
//
//
//
TABELLA DI LOOKUP PER LOGARITMI
Il programma determina il logaritmo di un numero, dopo aver
memorizzato in forma tabellare i valori della funzione logaritmo
per un predefinito insieme di numeri.
WORD TABELLA[] = {0,3010,4771,6021,6990,7782,8451,9031,9542,10000};
// TABELLA DW 0,3010,4771,6021,6990,7782,8451,9031,9542,10000
BYTE NUMERO = 7; // NUMERO
DB 7
WORD LOGARITMO; // LOGARITMO DW ?
asm
{
// Azzera EAX, EBX, EDX
SUB EAX,EAX
SUB EBX,EBX
SUB EDX,EDX
// Indice per la ricerca
MOV AL,NUMERO
// Corregge l'indice (non esiste il logaritmo di 0)
SUB AL,1
// Moltiplica l'indice per 2 (la tabella è fatta di WORD)
ROL AL,1
// Indirizzo di TABELLA in EBX
LEA EBX,TABELLA
// Converte da BYTE a WORD
CBW
// Somma indirizzo e indice
ADD BX,AX
// Estrae il dato in tabella
MOV DX,[EBX]
// Risultato in LOGARITMO
MOV LOGARITMO,DX
}
Conversione di codici mediante tabelle di lookup
// CONVERSIONE DI CODICI MEDIANTE TABELLE DI LOOKUP
// Il programma esegue la conversione di un numero esadecimale
// nella codifica Gray equivalente.
BYTE GRAY[] = {0x2,0x3,0x7,0x5,0x4,0xC,0xD,0xF,0xE,0xA};
// GRAY DB 0010B, 0110B, 0111B, 0101B, 0100B
//
DB 1100B, 1101B, 1111B, 1110B, 1010B
BYTE VALORE = 8; // VALORE
DB 8H
BYTE RISPOSTA;
// RISPOSTA DB ?
asm
{
// Azzera EAX, EBX
SUB EAX,EAX
SUB EBX,EBX
// Indirizzo della tabella di codifica in EBX
LEA EBX,GRAY
// Numero da convertire (indice tabella) in AL
MOV AL,VALORE
// Codifica in AL
XLAT GRAY
// Risultato in RISPOSTA
MOV RISPOSTA,AL
}
Conversione da numeri ASCII a numeri esadecimali
//
//
//
//
CONVERSIONE DA NUMERI ASCII A NUMERI ESADECIMALI
Il programma converte un numero espresso in formato ASCII
nel corrispondente numero in formato esadecimale.
L'input si suppone compreso tra 30H e 46H.
// ASCII
// 30H
// 31H
// 32H
// 33H
// 34H
// 35H
// 36H
// 37H
// 38H
// 39H
// 41H
// 42H
// 43H
// 44H
// 45H
// 46H
HEX
0H
1H
2H
3H
4H
5H
6H
7H
8H
9H
AH
BH
CH
DH
EH
FH
BYTE ASCII = 0x42; // ASCII DB 42H
BYTE HEX;
// HEX
DB ?
asm
{
// Azzera EAX
SUB EAX,EAX
// Numero ASCII da convertire in AL
MOV AL,ASCII
// Conversione
SUB AL,30H
// Il numero è compreso tra 0 e 9?
CMP AL,9
// Salta se il numero è maggiore di 9
JG LETTERA
// Vai alla fine
JMP FINEPRG
// Conversione per numeri maggiori di 9
LETTERA:SUB AL,07H
// Risultato in HEX
FINEPRG:MOV HEX,AL
}
Scarica