Sei sulla pagina 1di 38

(Incompleto) MINI CORSO DI ASSEMBLER 8086

(tratto dal sito:http://www.giobe2000.it/Tutorial/Cap01/Pag/cap01-01.asp


http://www.enricomilano.it/download/guide/c/11c.htm)

LINGUAGGIO MACCHINA
Rappresenta l'insieme delle istruzioni che un elaboratore in grado di eseguire
direttamente. E' strettamente correlato alla realizzazione fisica dell'elaboratore
stesso.
La struttura di massima di un'istruzione macchina la seguente:
OP-CODE

DEST

SOURCE

dove:
- Il campo Op-code specifica il tipo di operazione richiesta (es., copia)
- Il campo Dest specifica loperando di destinazione (es., con 3 bit si individua uno
degli 8 registri generali)
- Il campo Source specifica loperando sorgente es., lindirizzo di alfa relativo al
segmento dati)
Per scrivere un codice in linguaggio macchina necessario quindi:
- conoscere l'architettura della macchina
- ragionare in termini del comportamento dell'elaboratore
- conoscere i dettagli relativi alla scrittura delle singole istruzioni ovvero:
a) i codici numerici e il formato interno delle istruzioni macchina
b) le rappresentazione degli operandi
- gestire direttamente gli indirizzi in memoria per il riferimento ai dati e per i salti al
codice
Ad esempio, per scrivere nel linguaggio macchina dell'8086 l'istruzione:

MOV destinazione, sorgente


necessario scegliere tra:
- 14 diversi codici operativi, a seconda del tipo di operandi
- svariate possibili rappresentazioni degli operandi stessi
L'istruzione macchina che si ottiene ha una lunghezza variabile tra i due e i sei byte,
a seconda delle scelte effettuate.

LINGUAGGIO ASSEMBLER
Si tratta di un linguaggio a pi alto livello rispetto a quello macchina. I suoi vantaggi
sono:
- nasconde i dettagli realizzativi delle singole istruzioni (codici operativi, formati,
ecc.)
- associa ai dati e alle istruzioni nomi simbolici che identificano in modo univoco le
corrispondenti posizioni di memoria (eliminando, cos, la necessit di utilizzare
indirizzi espliciti).

Costringe ancora il programmatore a ragionare in termini della logica della macchina


a cui si
riferisce. L'architettura della macchina a cui il linguaggio si riferisce non viene
nascosta in
alcun modo. Ad esempio, con il linguaggio assembler possibile riferirsi
direttamente ai registri, azione preclusa ai linguaggi di alto livello a causa della loro
indipendenza dallhardware.
Distinguiamo nel linguaggio due tipi di istruzioni:
- Istruzioni eseguibili - a cui corrispondono le istruzioni del linguaggio macchina
- Direttive (o pseudoistruzioni) - controllano il comportamento dell'assemblatore in
fase di
traduzione - facilitano lo sviluppo dei programmi - permettono:
a) la suddivisione di un'applicazione in pi moduli
b) il controllo del formato dei listati
c) la definizione di macro, ecc.

STRUTTURA DI UN PROGRAMMA
Un programma in Macro Assembler ha generalmente la seguente struttura:
.386
.MODEL ......, tipo convenzione parametri
; indica al compilatore il modello di memoria da usare
.STACK ......
; dimensiona lo Stack (ad esempio 100h)
.DATA
; inizio del segmento dati
.DATA?
; inizio del segmento dati
.CONST
.CODE
Etichetta_di_Inizio:
; inizio del segmento di codice
........
END Etichetta_di_Inizio ; fine del programma

.386
Questa e' una direttiva per l'assembler, a cui diciamo di usare il
set di istruzioni 80386. Potreste anche usare .486, .586 ma la
scelta piu' sicura e' quella di utilizzare sempre .386 - Non va
messa se non necessaria
.MODEL
Definisce il tipo dei segmenti di memoria da utilizzare nel programma. Le principali
scelte possibili sono:

- TINY: tutto il codice e i dati sono in un unico segmento (stanno in 64Kb). Questo
modello il modello utilizzato per i file con estensione COM.
- SMALL: il modello pi comune, un segmento per il codice e uno solo per i dati
(statici, globali, heap) e lo stack, Tutti i segmenti non superano i 64Kb. Quindi il
valore iniziale di DS ed SS non coincide con quello di CS
- MEDIUM: il codice usa pi segmenti, pu quindi superare la barriera dei 64Kb. I
dati e lo stack sono gestiti come nel modello small.
- COMPACT: come lo small ma per accedere ai dati uso puntatori di tipo FAR
quest'ultimi possono infatti superare i 64Kb.
- LARGE: come il compact ma con il codice in pi segmenti; sia codice che dati
superano i 64Kb.
- HUGE: Il modello huge consente di gestire (in teoria) sino ad 1 Mb di dati statici e
globali, estendendo ad essi la modalit di indirizzamento implementata dai modelli
large e medium per il codice. E' l'unico modello che estende ad 1 Mb il limite teorico
sia per il codice che per tutti i tipi di dato
- FLAT : supporta la modalit a 32bit dei processori 386+; in questa modalit viene
abolita il metodo di indirizzamento SEGMENTO:OFFSET, l'indirizzo dato da un
unico numero a 32bit. Sotto Win32 l'unico modello ammesso
Se si vuole, sul sito esiste un approfondimento sui modelli di memoria.
Il memory model (modello di memoria) e' oggi drasticamente diverso dai vecchi
giorni del mondo a 16-bit. In Win32, non e' piu' necessario preoccuparsi del modello
di memoria o dei segmenti! C'e' solo un modello di memoria: il Flat memory
model. Non esistono piu' segmenti da 64K. La memoria e' un largo e continuo
spazio di 4 GB. Questo significa anche che non bisogner piu' giocare con i segment
registers. Potremo usare qualsiasi segment register per indirizzare qualsiasi punto
nello spazio di memoria. Questo e' un GRANDE aiuto ai programmatori, ed ci che
rende la programmazione in assembly per Win32 semplice quanto quella in C.
Come secondo argomento opzionale .MODEL ha la specifica della convenzione di
passaggio dei parametri. Questa convenzione specifica l'ordine con cui i parametri
verranno passati, da sinistra-verso-destra o da destra-verso-sinistra. In Win16, ci
sono due tipi di convenzioni di chiamata, C e PASCAL La convenzione di chiamata C
passa i parametri da destra a sinistra, cioe', il parametro all'estrema destra e'
PUSHato per primo. Il caller (chiamante) e' responsabile del bilanciamento dello
stack frame dopo la call. Ad esempio, volendo chiamare una funzione denominata
foo (int primo_param, int secondo_param, int terzo_param) con la convenzione C, il
codice assomiglierebbe a questo:
push [terzo_param]
push [secondo_param]
push [primo_param]
call foo
add sp, 12

// ; Push il terzo parametro ...


// ; Seguito dal secondo ...
// ; e dal primo
// ; Il caller bilancia lo stack frame

La convenzione PASCAL e' l'inverso di quella per il C. Essa passa i parametri da


sinistra a destra, e il callee e' responsabile per il bilanciamento della stack dopo la
call. Win16 adotta la convenzione PASCAL poich produce codici piu' piccoli. la
convenzione C e' utile quando non si conosce il numero dei parametri che verranno
passati alla funzione, come nel caso di wsprintf(). Nel caso di wsprintf(), la funzione
non ha modo di determinare aprioristicamente il numero di parametri che verrano
pushati sulla stack, percio' non puo' bilanciare lo stack frame. STDCALL e' un ibrido
tra le convenzioni C e PASCAL. Essa passa i parametri da destra a sinistra ma il
callee responsabile per il bilanciamento della stack dopo la call. La piattaforma
Win32 usa esclusivamente STDCALL. Eccetto in un caso: wsprintf() dove si usa la
convenzione C.

.STACK
Dice al compilatore quanto spazio deve riservare per lo stack. Se viene omesso il
compilatore usa per default 400h (1Kb)
La sezione dei dati divisa in 3 categorie: .DATA, .DATA? e .CONST. Non e'
necessario utilizzare tutte e tre le sezioni nel vostro programma. Dichiarate solo la/e
sezione/i che volete usare.

.DATA
Inizializza il segmento dati. Dopo questa direttiva si dichiarano le variabili da usare
nel programma e le si inizializzano.
Messaggio DB "Salve Mondo",13,10,'$'
Questa istruzione assegna alla variabile Messaggio il valore "Salve Mondo". DB
definisce dei Byte in memoria e in questo caso il numero dei byte 14:
- 11 per la stringa "Salve Mondo"
- 1 per il carattere 13 (CR)
- 1 per il 10 (LF)
- 1 per il terminatore '$', che deve essere sempre presente alla fine di una stringa.
In questo caso la variabile quindi inizializzata se avessimo voluto solo riservare
dello spazio (ad esempio 10 Byte) per riempirlo durante l'esecuzione del programma
avremmo dovuto scrivere:
Nome_Variabile DB 10 DUP(?)
Quindi abbiamo visto che la direttiva DB definisce byte , ne esistono altre:
DW - Define Word
DD - Define Double word
DQ - Define Quadword
DF - Define 48-bit (puntatore FAR 6 byte)
DT - Define TenByte

DATA?

Questa sezione contiene i dati NON inizializzati del vostro programma. A volte capita
di voler impegnare una parte di memoria (variabli) senza inizializzarla. Questa
sezione serve a tale scopo.

.CONST
Questa sezione contiene le costanti usate dal vostro programma. Le costanti in
questa sezione non potranno mai essere modificate dal vostro programma. Sono
semplicemente costanti.

.CODE
Indica l'inizio del segmento di codice del programma. E' qui dove vengono messe le
vostre istruzioni. <etichetta>: end <etichetta> dove <etichetta> e' una
qualsiasi etichetta arbitraria sono obbligatorie e sono usate per specificare
l'estensione del vostro programma. Entrambe le etichette devono essere identiche.
Tutto il vostro codice deve risiedere tra <etichetta>: e end < etichetta>

Dichiarazione Costanti
In assembler la dichiarazione delle costanti avviene mediante un'istruzione
dichiarativa composta dal nome che si intende utilizzare, dalla direttiva EQU e dal
valore che si vuole assegnare alla costante.
ESC_ EQU 27

Esempio per il codice del tasto <ESC>

CR EQU 0Dh

Esempio per il codice del tasto <CARRIAGE


RETURN>

LF EQU 0Ah

Esempio per il codice del tasto <LINE FEED>

Dichiarazione Variabili
La dichiarazione delle variabili avviene mediante un'istruzione dichiarativa composta
dal nome che si intende utilizzare, dalla direttiva che riserva il numero di byte e
dall'operando che pu essere:
- una costante (in questo caso si tratta dell'inizializzazione)
- un punto di domanda che indica che la variabile non stata inizializzata.
Il nome rappresenta l'indirizzo di offset rispetto all''inizio del segmento dati della
variabile appena dichiarata.
I tipi che una variabile pu assumere sono quattro e ne definiscono la massima
grandezza. Essi sono:
- DB (define byte): la variabile grande 1 byte;
- DW (define word): la variabile grande 2 byte;
- DD (define double): la variabile grande 4 byte;
- DQ (define quad): la variabile grande 8 byte
- DT (define ten): la variabile grande 10 byte
Dichiarazione di variabile numeriche intere senza segno:

n db 10
n dw 20
n dd 30
n dq 40
n dt 50

variabile n di un byte inizializzata a 10


(range da 0 a 255)
variabile n di due byte inizializzata a 20
(range da 0 a 65.535)
variabile

n di quattro byte inizializzata a


32

30 (range da 0 a 4.294.967.295=2 -1)


variabile n di otto byte inizializzata a 40
(disponibile dal 486) (range: da 0 a
18.446.744.073.709.551.615)
variabile

n di dieci byte inizializzata a 50


80

(range: da 0 a 2 -1)

Dichiarazione di variabile numeriche intere con segno: si usa la rappresentazione in


complemento a 2. - E' possibile usare le stesse direttive dei numeri senza segno
oppure sbyte, sword, sdword:
n
n
n
n

db -10
sbyte -10
dw -20
sword -20

n dd -30
n sdword -30

variabile n di un byte inizializzata a -10


(range da -128 a 127)
variabile n di due byte inizializzata a -20
(range da -32.768 +32.767)
variabile n di quattro byte inizializzata a
-30 (range da -2.147.483.648
+2.147.483.647 )

Le variabili numeriche con la virgola sono rappresentate in notazione Floating point


IEEE 754 - E' possibile usare le stesse direttive dei numeri senza segno oppure
real4, real8, real10:
n
n
n
n
n
n

dd -10
real4 -10
dq -20
real8 20
dt -30
real10 30

variabile

n di quattro byte inizializzata a

-10 (range da 1.18 x 10


variabile

a 3.40 x 10

38

n di otto byte inizializzata a -20

(range da 2.23 x 10
variabile

-38

-308

a 1.79 x 10

308

n di dieci byte inizializzata a -30

(range da 3.37 x 10

-4932

a 1.18 x 10

4932

La dichiarazione di stringhe
lettera db "a"
lettera db 'a'
s db "ciao"
s db "c","i","a","o"
invio db 0Dh, 0Ah

variabile lettera di un
byte inizializzata a "a"

s inizializzata a
"ciao". Il nome s fa riferimento
variabile

all'indirizzo del byte contenente


il primo carattere. Posso fornire
anche la codifica ascii

Gli array possono essere visti come una sequenza di variabili. Una stringa di testo
un esempio di array di byte dove ogni carattere rappresentato mediante il suo
codice ascii. Ecco degli esempi di definizione:
a DB 48h, 65h, 6Ch, 6Ch, 6Fh, 00h
b DB 'Hello', 0

E' possibile accedere al singolo elemento utilizzando le parentesi quadre


MOV AL, a[3]
Spesso a tale scopo vengono utilizzati i registri di memoria BX, SI, DI e BP
MOV SI, 3
MOV AL, a[SI]
Se si vuol dichiarare un array contenente gli stessi valori ripetuti oppure di grandi
dimensioni si pu utilizzare l'operatore DUP. La sintassi la seguente:
NUMERO DUP ( Valori )
dove numero indica quanti duplicati devono essere fatti mentre valori indica
l'espressione da duplicare
ad esempio:
X DB 5 DUP(9)
equivale a
X DB 9, 9, 9, 9, 9
oppure
X DB 3 DUP(1, 2)
equivale a
X DB 1, 2, 1, 2, 1, 2
Naturalmente si pu utilizzare DW al posto di DB se i valori contenuti nel singolo
elemento sono maggiori di 255 o inferiori a -128. DW non usabile per dichiarare
una stringa

LE OPERAZIONI FONDAMENTALI
Le operazioni fondamentali come la somma, la differenza, la moltiplica e la divisione
richiedono almeno il caricamento di uno degli operandi all'interno dei registri.
L'operazione che effettua tale caricamento il comando MOV.

- Istruzione MOV
L'istruzione MOV copia un dato da una posizione ad un'altra e la sua sintassi :
MOV

<destinazione>, <sorgente>

In pratica, questa istruzione copia il valore sorgente in destinazione. L'operando


<destinazione> pu essere un registro o una locazione di memoria, mentre

l'operando <sorgente> pu essere un registro, una locazione di memoria o un


valore immediato; i due operandi devono avere la stessa dimensione. Inoltre i due
operandi non possono essere entrambi locazioni di memoria.

- Istruzione ADD
L'istruzione ADD esegue la somma di due numeri con o senza segno e posiziona il
risultato della somma nel primo operando. Gli operandi possono essere byte, word e
doubleword (nei 386 in su). La sua sintassi :
ADD

<operando1>, <operando2>

Il primo operando pu essere un registro o una locazione di memoria, il secondo


pu essere un registro, una locazione di memoria o un valore immediato; i due
operandi devono avere la stessa dimensione. Inoltre i due operandi non possono
essere entrambi locazioni di memoria. In base al risultato vengono settati i seguenti
flag:
- ZF : ha il valore 1 se il risultato zero;
- SF : ha il valore 1 se il risultato negativo;
- CF : ha il valore 1 se si verificato un riporto (carry) sul bit pi significativo.
Corrisponde all'istruzione: < operando1> = <operando1> + <operando2>

- Istruzione ADC
L'istruzione ADC esegue esegue la somma di due numeri con o senza segno
considerando anche il valore del carry flag (CF) e inserendo il risultato della somma
nel primo operando. La sua sintassi :
ADC

<operando1>, <operando2>

Questa istruzione richiede le medesime condizione dell'istruzione ADD. Corrisponde


all'istruzione: < operando1> = <operando1> + <operando2> + CF
ASSEMBLER (OPER_SOMMA.ASM)

INCLUDE Libreria.inc ; files contenenti le macro generali


INCLUDE Output.inc
; files contenenti le macro di output
.MODEL tiny
; Modello di memoria
;-------------------------------------; segmento dati
;-------------------------------------.DATA
X db 120d ; Addendo iniziale
Y db 110d ; un addendo
Z db 140d ; altro addendo
S dw ?
; Somma
;-------------------------------------; segmento codice
;-------------------------------------.CODE
ORG 100h ; il prg inizia all'indirizzo 100h
INIZIO:
; ESEMPIO 1: somma con risultato inferiore al byte
MOV AH, 0
; Azzero la parte alta del registro AX

MOV AL, X
ADD AL, Y
MOV S, AX
MS_PRINT_INT S
MS_ACAPO

;
;
;
;

Carico il primo addendo dall'indirizzo X


Aggiungo il secondo addendo dall'indirizzo Y
Salvo la somma (inferiore a 256) nella variabile S
Stampo la variabile S (vedo 230)

; ESEMPIO 2: somma con risultato superiore a 256 (non mi basta un byte!)


;
non considero il riporto
MOV AH, 0
; Azzero la parte alta del registro AX
MOV AL, X
; Carico il primo addendo dall'indirizzo X
ADD AL, Z
; Aggiungo il secondo addendo dall'indirizzo Z
MOV S , AX
; Salvo la somma (superiore a 256) nella variabile S
MS_PRINT_INT S ; Stampo la variabile S (vedo 4: 00000001 00000100)
MS_ACAPO
; ESEMPIO 3: somma con risultato superiore a 256 (non mi basta un byte!)
;
considero il riporto
MOV AH, 0
; Azzero la parte alta del registro AX
MOV AL, X
; Carico il primo addendo dall'indirizzo X
ADD AL, Z
; Aggiungo il secondo addendo dall'indirizzo Z
ADC AH, 0
; Aggiungo il riporto (Carry)
MOV S , AX
; Salvo la somma (superiore a 256) nella variabile S
MS_PRINT_INT S ; Stampo la variabile S (vedo 260: 00000001 00000100)
MS_ACAPO
INT 20h
END INIZIO

; Termina il programma - equivalente all'int 21h AL=00h

- Istruzione SUB
L'istruzione SUB esegue la sottrazione tra due numeri con o senza segno e posiziona
il risultato della sottrazione nel primo operando. La sua sintassi :
SUB

<operando1>, <operando2>

Il primo operando pu essere un registro o una locazione di memoria, il secondo


pu essere un registro, una locazione di memoria o un valore immediato; i due
operandi devono avere la stessa dimensione. Inoltre i due operandi non possono
essere entrambi locazioni di memoria. In base al risultato vengono settati i seguenti
flag:
- ZF : ha il valore 1 se il risultato zero;
- SF : ha il valore 1 se il risultato negativo;
- CF : ha il valore 1 se si verificato un riporto sul bit pi significativo.
Corrisponde all'istruzione: < operando1> = <operando1> - <operando2>

- Istruzione SBB
L'istruzione SBB esegue la sottrazione di due numeri con o senza segno
considerando anche il valore del carry flag CF e inserendo il risultato della
sottrazione nel primo operando. La sua sintassi :
SBB

<operando1>, <operando2>

Questa istruzione richiede le medesime condizione dell'istruzione SUB.


Corrisponde all'istruzione: < operando1> = <operando1> - <operando2> +
CF
Lo scopo di questa istruzione da valutare nel contesto in cui ha avuto origine, cio
quello dell'8086: per via dei limiti della sua ALU (in grado di trattare solo dati a 16
bit). L'istruzione SBB ha reso possibile la sottrazione di numeri maggiori di 16 bit,
con la seguente tecnica (ovviamente superata con l'avvento dei nuovi processori):
- il primo numero a 32 bit viene caricato nei registri AX,BX:
- il sottraendo a 32 bit viene caricato nei registri CX,DX:
- la differenza viene eseguita in 2 tempi, prima sottraendo i 16 bit meno significativi
(DX da BX) e poi i rimanenti 16 bit alti (CX da AX) considerando l'eventuale prestito
(conservato appunto nella flag di carry) della sottrazione precedente.
ASSEMBLER (OPER_DIFFERENZA.ASM)

INCLUDE Libreria.inc ; files contenenti le macro generali


INCLUDE Output.inc
; files contenenti le macro di output
.MODEL tiny
; Modello di memoria
;-------------------------------------; segmento dati
;-------------------------------------.DATA
A dw 32512d
; Valore iniziale:
0111.1111.0000.0000
B dw 1d
; valore da sottrarre: 0000.0000.0000.0001
R dw ?
; Risultato - 32.511 : 0111.1110.1111.1111
;-------------------------------------; segmento codice
;-------------------------------------.CODE
ORG 100h ; il prg inizia all'indirizzo 100h
INIZIO:
; ESEMPIO 1: differenza senza considerare il riporto
MOV AX, A
; Carico il primo valore (a 32 bit) dall'indirizzo A
MOV BX, B
; Carico il secondo valore (a 32 bit) dall'indirizzo B
SUB AL, BL
; Sottraggo la parte bassa dei registri AX e BX
SUB AH, BH
; Ripeto la sottrazione ma non considero il riporto
MOV R, AX
; Salvo la differenza nella variabile R
MS_PRINT_INT R ; Stampo la variabile R (Errore: vedo 32.767!)
MS_ACAPO
; ESEMPIO 2: differenza considerando il riporto
MOV AX, A
; Carico il primo valore (a 32 bit) dall'indirizzo A
MOV BX, B
; Carico il secondo valore (a 32 bit) dall'indirizzo B
SUB AL, BL
; Sottraggo la parte bassa dei registri AX e BX
SBB AH, BH
; Ripeto la sottrazione ma questa volta considero il riporto
MOV R, AX
; Salvo la differenza nella variabile R
MS_PRINT_INT R ; Stampo la variabile R (vedo 32.511)
MS_ACAPO
; ESEMPIO 3: differenza senza distinguere parte bassa e alta (corretta!)
MOV AX, A
; Carico il primo valore (a 32 bit) dall'indirizzo A
SUB AX, B
; Sottraggo da AX il valore all'indirizzo B
MOV R, AX
; Salvo la differenza nella variabile R
MS_PRINT_INT R ; Stampo la variabile R (vedo 32.511)
MS_ACAPO
INT 20h
END INIZIO

; Termina il programma - equivalente all'int 21h AL=00h

- Istruzione INC e DEC

L'istruzione INC incrementa di 1 il valore del suo operando. La sua sintassi :


INC

<operando>

Il suo operando pu essere un registro o una locazione di memoria. L'istruzione DEC


decrementa di 1 il valore del suo operando. La sua sintassi :
DEC

<operando>

Il suo operando pu essere un registro o una locazione di memoria.

- Istruzione MUL e IMUL


L'istruzione MUL esegue la moltiplicazione tra due numeri senza segno. La sua
sintassi :
MUL

<operando>

Corrisponde all'istruzione:
AX = AL*<operando> se l'operando un byte
AX = DX:AX=AX*<operando> se l'operando un word
Quindi a seconda della dimensione dell'operando, l'istruzione si comporta in modo
diverso:
- Se l'operando di tipo byte, l'altro operando deve essere posto in AL e il risultato
viene posto in AX.
- Se l'operando di tipo word, l'altro operando deve essere posto in AX e il risultato
viene posto nei due registri DX:AX.
Da notare che l'operando non pu essere un valore immediato. L'istruzione MUL
imposta il CF per indicare se la parte alta del risultato usata o no: il flag a 1 se
viene usata anche la parte alta. Per eseguire moltiplicazioni tra numeri con segno si
utilizza l'istruzione IMUL, che prevede le stesse caratteristiche dell'istruzione MUL
con l'unica differenza che al posto del flag CF utilizza il flag di overflow OF. Inoltre
IMUL pu utilizzare anche due o tre argomenti.
ASSEMBLER (OPER_PRODOTTO.ASM)

INCLUDE Libreria.inc ; files contenenti le macro generali


INCLUDE Output.inc
; files contenenti le macro di output
.MODEL tiny
; Modello di memoria
;-------------------------------------; segmento dati
;-------------------------------------.DATA
A db 127d
; 1 Valore di un byte (da -128 a +127)
M1 db 127d
; 2 Valore di un byte (da -128 a +127)
M2 db -128d
; 3 Valore di un byte (da -128 a +127)
F dw ?
; Prodotto
;-------------------------------------; segmento codice
;-------------------------------------.CODE
ORG 100h
; il prg inizia all'indirizzo 100h

INIZIO:
; ESEMPIO 1: prodotto tra numeri positivi
MOV AL, A
; Carico il primo fattore (a 16 bit) dall'indirizzo A
MOV BL, M1
; Carico il secondo fattore (a 16 bit) dall'indirizzo M1
MUL BL
; Moltiplico BL*AL e scarico in AX
MOV F, AX
; Salvo la differenza nella variabile F
MS_PRINT_INT F ; Stampo la variabile F
MS_ACAPO
; ESEMPIO 2: prodotto con numeri con segno
MOV AL, A
; Carico il primo fattore (a 16 bit) dall'indirizzo A
MOV BL, M2
; Carico il secondo fattore (a 16 bit) dall'indirizzo M2
IMUL BL
; Moltiplico BL*AL e scarico in AX
MOV F, AX
; Salvo la differenza nella variabile F
MS_PRINT_INT F ; Stampo la variabile F
MS_ACAPO
INT 20h
END INIZIO

; Termina il programma - equivalente all'int 21h AL=00h

- Istruzione DIV e IDIV


L'istruzione DIV esegue la divisione intera tra due numeri senza segno. La sua
sintassi :
DIV

<operando>

Corrisponde all'istruzione:
- se l'operando un byte : AL = AX/<operando> e AH=resto
- se l'operando un word: AX = DX:AX/<operando> e DX=Resto
Da notare che il divisore non pu essere un valore immediato. L'istruzione DIV
pu causare una condizione di overflow se il risultato troppo grande per essere
contenuto nel registro relativo. Si deve porre particolare attenzione alla divisione per
0 e per 1. Per eseguire divisioni tra numeri con segno si utilizza l'istruzione IDIV,
che prevede le stesse caratteristiche dell'istruzione DIV.
ASSEMBLER (OPER_DIVISIONE.ASM)

INCLUDE Libreria.inc ; files contenenti le macro generali


INCLUDE Output.inc
; files contenenti le macro di output
.MODEL tiny
; Modello di memoria
;-------------------------------------; segmento dati
;-------------------------------------.DATA
A db 127d
; 1 Valore di un byte (da -128 a +127)
M1 db 127d
; 2 Valore di un byte (da -128 a +127)
M2 db -128d
; 3 Valore di un byte (da -128 a +127)
F dw ?
; Prodotto
;-------------------------------------; segmento codice
;-------------------------------------.CODE
ORG 100h
; il prg inizia all'indirizzo 100h
INIZIO:
; ESEMPIO 1: prodotto tra numeri positivi
MOV AL, A
; Carico il primo fattore (a 16 bit) dall'indirizzo A
MOV BL, M1
; Carico il secondo fattore (a 16 bit) dall'indirizzo M1

MUL BL
; Moltiplico BL*AL e scarico in AX
MOV F, AX
; Salvo la differenza nella variabile F
MS_PRINT_INT F ; Stampo la variabile F
MS_ACAPO
; ESEMPIO 2: prodotto con numeri con segno
MOV AL, A
; Carico il primo fattore (a 16 bit) dall'indirizzo A
MOV BL, M2
; Carico il secondo fattore (a 16 bit) dall'indirizzo M2
IMUL BL
; Moltiplico BL*AL e scarico in AX
MOV F, AX
; Salvo la differenza nella variabile F
MS_PRINT_INT F ; Stampo la variabile F
MS_ACAPO
INT 20h
END INIZIO

; Termina il programma - equivalente all'int 21h AL=00h

- Operatore PTR
Poich tutte le istruzioni aritmetiche e di trasferimento richiedono operandi aventi la
stessa dimensione, spesso risulta difficile eseguire spostamenti o operazioni. Per
permettere il normale svolgimento di tali istruzioni interviene l'operatore PTR che
permette di usare un solo byte di un dato definito come word o di indicare una word
con un riferimento solo al primo byte.
Loperatore PTR forza lassemblatore a modificare per l'istruzione corrente il tipo del
dato avente come identificatore nome.
Esempio 0):
.DATA
TOT DW ?
.CODE
MOV BH, BYTE PTR TOT
MOV CH, BYTE PTR TOT+1
Esempio 1)
X una variabile di tipo double (DD). Si vuole spostare nella parte bassa il
contenuto del registro AX e nella parte alta il contenuto del registro BX.
MOV Word Ptr X,AX
MOV Word Ptr X+2,BX
Esempio 2)
Si vuole poi spostare il contenuto della parte bassa della variabile double X nel
registro CX e il contenuto della parte alta rispettivamente nei registri DL e DH.
MOV CX,Word Ptr X
MOV DL,Byte Ptr X+2
MOV DH,Byte Ptr X+3
Esempio 3)

INC [BX]
La cella da incrementare corrisponde ad una word o ad un byte ? Lassemblatore
non pu saperlo e genera errore.
La soluzione :
INC BYTE PTR [BX]

IL CONTROLLO DI FLUSSO
Il controllo di flusso realizzato mediante istruzioni di salto. Le istruzioni di salto
permettono di specificare l'indirizzo dell'istruzione successiva da eseguire, in modo
da modificare la normale sequenza di esecuzione. L'istruzione di destinazione pu
essere specificata mediante un etichetta. Le istruzioni di salto possono essere
incondizionate o condizionate: quelle incondizionate vengono sempre eseguite,
mentre quelle condizionate vengono eseguite solo al verificarsi o meno di una data
condizione
Le istruzioni di salto possono avere un'etichetta come operando. Un'etichetta
all'interno del segmento di codice rappresenta l'indirizzo di un'istruzione. Le etichette
possono essere specificate con
Nome:
I nomi delle etichette possono essere scelti liberamente, ma naturalmente devono
essere unici.

- I salti incondizionati
I salti incondizionati vengono eseguiti con l'istruzione JMP, che ha sintassi:
JMP

<operando>

L'istruzione JMP permette di fare un salto a qualsiasi istruzione, e il suo operando


pu essere un etichetta.

- I salti condizionati
Per fare in modo che il salto venga eseguito solo se si verifica una particolare
condizione, si usano le istruzioni di salto condizionato. Le istruzioni di salto
condizionato si basano sul valore dei flag impostati dalle operazioni aritmeticologiche e dall'istruzione CMP
L'istruzione CMP permette di confrontare due dati e ha il formato:
CMP

<operando1>, <operando2>

Il primo operando pu essere un registro o una locazione di memoria, il secondo

pu essere un registro, una locazione di memoria o un valore immediato; i due


operandi devono avere la stessa dimensione. Inoltre i due operandi non possono
essere entrambi locazioni di memoria. L'istruzione CMP si comporta come una
sottrazione; sottrae dal primo operando il secondo, trascurando per il risultato.
Essa infatti modifica solo i flag affinch possano essere usati correttamente dalla
successiva istruzione di salto condizionato.
Ad ogni flag sono associate due istruzioni di salto condizionato: una che salta se il
suo valore a uno, mentre l'altra salta se il valore a zero (quest'ultima indicata
dalla lettera N che sta ad indicare Not).
Tali istruzioni sono:
-

JZ : salta se il flag Z a 1 (Zero Flag)


JNZ : salta se il flag Z a 0
JS : salta se il flag S a 1 (Sign Flag)
JNS : salta se il flag S a 0
JO : salta se il flag O a 1 (OverFlow Flag)
JNO : salta se il flag O a 0
JC : salta se il flag C a 1 (Carry Flag)
JNC : salta se il flag C a 0
JP : salta se il flag P a 1 (Parity Flag)
JNP : salta se il flag P a 0

Oltre alle istruzioni di salto condizionato che si riferiscono direttamente ai flag, ve ne


sono altre, basate sugli stessi flag ma pi facili da ricordare e utilizzare.
- JE : salta se Operatore1=Operatore2
- JNE : salta se Operatore1<>Operatore2
Confronto tra numeri con segno:
-

JL : salta se Operatore1<Operatore2
JG : salta se Operatore1>Operatore2
JLE : salta se Operatore1<=Operatore2
JGE : salta se Operatore1>=Operatore2

Confronto tra numeri senza segno:


- JB : salta se Operatore1<Operatore2
- JA : salta se Operatore1>Operatore2
- JBE : salta se Operatore1<=Operatore2
- JAE : salta se Operatore1>=Operatore2
Da notare che, nel processore 8086 le istruzioni di salto richiedono che l'indirizzo di

destinazione sia abbastanza vicino: tra -128 byte e +127 byte. Ecco alcuni dei flag
coinvolti nelle istruzioni di salto condizionato
CF

(Carry Flag, flag di Riporto): viene forzata a 1 se una somma/sottrazione ha prodotto


riporto/prestito; influenza i seguenti salti condizionati: JC (salta se CF=1), JNC (salta se
CF=0), JB (salta se CF=1), JAE (salta se CF=0), JA (salta se CF=0 e ZF=0), JBE
(salta se CF=1 o ZF=1).

PF

(Parity Flag, flag di Parit): viene forzata a 1 se il risultato di un'operazione contiene un


numero pari di 1.

AF

(Auxiliary Flag, flag di Riporto Ausiliario): viene forzata a 1 se un'operazione ha


prodotto riporto tra il primo (bit0bit3) e il secondo nibble (bit4bit7).

ZF

(Zero Flag, flag di Zero): viene forzata a 1 se un'operazione ha dato risultato nullo;
influenza i seguenti salti condizionati: JZ (salta se ZF=1), JNZ (salta se ZF=0), JE
(salta se ZF=1), JNE (salta se ZF=0), JG (salta se ZF=0 e SF=OF), JLE (salta se
ZF=1 o SF<>OF), JA (salta se ZF=0 e CF=0), JBE (salta se ZF=1 o CF=1).

(Sign Flag, flag di Segno): viene forzata al valore corrente del bit pi significativo del
risultato di un'operazione; come noto nei numeri con segno 0 significa positivo e 1
SF significa negativo; influenza i seguenti salti condizionati: JS (salta se SF=1), JNS (salta
se SF=0), JG (salta se SF=OF e ZF=0), JGE (salta se SF=OF), JL (salta se SF<>OF),
JLE (salta se SF<>OF o ZF=1).
OF

(Overflow Flag, flag di Overflow): viene forzata a 1 se un'operazione ha prodotto


trabocco, cio ha superato il numero massimo per il registro coinvolto (256=100H a 8bit,
65536=10000H a 16bit, ecc); influenza i seguenti salti condizionati: JO (salta se OF=1),
JNO (salta se OF=0).

- L'istruzione LOOP
Listruzione LOOP permette di realizzare un ciclo a contatore; il numero di ripetizioni
desiderato deve essere posto nel registro contatore CX; ad ogni ciclo il contatore
viene decrementato e quando raggiunge lo zero il ciclo viene fermato e lesecuzione
prosegue dallistruzione successiva al ciclo. Ogni ciclo LOOP presenta una simile
sintassi:
...
Mov CX,<N>
InizioCiclo:
...
; istruzioni
LOOP InizioCiclo
...

- IF THEN ELSE END IF


La struttura condizionale ha la seguente struttura:
if Condizione then
IstruzioniV
else
IstruzioniF
end if

Per codificare in assembly questa struttura le istruzioni vanno scritte in sequenza. Se


proviamo a deformare la struttura, senza per modificare i punti di giunzione
otteniamo questo diagramma equivalente che maggiormente si conf alla struttura
sintattica dell'assembler.

oppure
In entrambe i casi si pu notare che per implementare questa struttura di controllo
sono necessarie due istruzioni di salto: una condizionata e l'altra incondizionata.
Esempio:
si supponga di dover valutare il minimo tra due numeri a, b
MACRO LINGUAGGIO

SE (A <B) ALLORA
Minimo=A
ALTRIMENTI
Minimo=B
FINESE
Stampa Minimo

ASSEMBLER (IFTHEN.ASM)

INCLUDE Output.inc ; contenenti le macro


.MODEL tiny
.DATA
; segmento dati
A dw 4h
B dw 7h
Minimo dw ?
.CODE
; segmento codice
ORG 100h
; il prg inizia all'indirizzo 100h
INIZIO:
MOV AX, A
CMP AX, B
JNL _ELSE
MOV Minimo, AX
JMP _ENDIF
_ELSE:
MOV AX, B
MOV Minimo, AX
_ENDIF:
MS_PRINT_INT Minimo
INT 20h
END INIZIO

- CICLO WHILE
L'iterazione una struttura che permette di ripetere pi volte l'esecuzione di
un'istruzione strutturata, sotto il controllo di una condizione. La tipica struttura
iterativa a controllo in testa il seguente:
While Condizione
Istruzioni
Fine While

Questa struttura pu essere realizzazata con le istruzioni di salto secondo il


seguente schema
...
INIZIO_WHILE:
SE NON E' VERA LA CONDIZIONI SALTO ALLA FINE_WHILE
ISTRUZIONI
SALTA A INIZIO_WHILE
FINE_WHILE:
...
Che in assembler diventa:
...
INIZIO_WHILE:
CMP ...
JN Condizione FINE_WHILE
<ISTRUZIONI>
JMP INIZIO_WHILE
FINE_WHILE:
...

Esempio:
si supponga di dover valutare la somma dei primi N numeri interi
ASSEMBLER (WHILE.ASM)

INCLUDE Output.inc ;
.MODEL tiny
.DATA
;
Somma dw 0d
N
dw 10d
.CODE
;
ORG 100h
;
INIZIO:
MOV Somma, 0d
;
MOV CL, 1d
_INIZIO_WHILE:
CMP CX, N
JNBE _FINE_WHILE
ADD Somma, CX

contenenti le macro
segmento dati
segmento codice
il prg inizia all'indirizzo 100h
inizializzazione

INC CX
JMP _INIZIO_WHILE
_FINE_WHILE:
MS_PRINT_INT Somma
INT 20h
; Termina il programma - equivalente all'int 21h AL=00h
END INIZIO

LE MACRO ISTRUZIONI E LE PROCEDURE


La stesura di un sorgente software non pu fare a meno di ricorrere alla definizione
e all'uso di Procedure, qualunque sia l'ambiente di programmazione coinvolto.
Anche se l'obiettivo da raggiungere nello sviluppo di un programma sia limitato e
semplice sempre buona abitudine frazionare il problema in parti piccole, efficienti
e specializzate, organizzate da una procedura principale.
In questo ambito rientrano due preziosi strumenti: le Macro e le procedure.
Questa scheda si ripropone di analizzare le due strutture in modo comparativo; il
suo compito quello di aiutarti a distinguere le une dalle altre e a decidere quale
delle 2 sar in grado di assicurare la migliore efficienza.

Macro Istruzioni
Le Macro Istruzioni sono strutture importanti e utili nella programmazione in
Assembly
La MacroIstruzione definita in questo modo:
NomeMacro MACRO arg1, arg2 ....
... codice macro
ENDM
La definizione delle Macro deve essere fatta per tempo, di solito dopo quella delle
costanti, all'inizio del nostro programma sorgente; questo fatto da al programmatore
la possibilit di scrivere una sola riga di programma, al posto di tutte quelle
racchiuse nel corpo della macro.
Spetta poi all'assemblatore sostituire a sua volta questa nostra unica riga con quelle
effettive inserite nel corpo della macro associata compilando il codice ad esse
relativo. Quindi in realt, alla fine, il codice macchina sempre lo stesso!
Il codice macchina prodotto alla fine dall'assemblatore , dunque, lo stesso; lo scopo
della MACRO non quindi quello di ridurre il numero di bytes del programma finale
ma di rendere pi leggibile il sorgente, sostituendo le sequenze ripetitive di istruzioni
con una unica macro Istruzione. Vediamo il seguente esempio che stampa a video
due stringhe
ESEMPIO USO MACRO CON UN SOLO FILE MACRO1.ASM

CR EQU 13d
LF EQU 10d

CON DUE FILE: MACRO2.ASM

IF1
INCLUDE Lib_Macro.inc

; PULISCE IL VIDEO
MS_CLRSCR MACRO
PUSH AX
MOV AH,00H
MOV AL,03H
INT 10h
POP AX
ENDM
; STAMPA UNA STRINGA
MS_PRINT_STR MACRO stringa
PUSH AX
PUSH DX
LEA DX, stringa
MOV AH, 9
INT 21H
POP AX
POP DX
ENDM
.MODEL tiny
.DATA
MSG1 DB "Messaggio 1",CR,LF, "$"
MSG2 DB "Messaggio 2",CR,LF, "$"
.CODE ; segmento codice
ORG 100h
INIZIO:
MS_CLRSCR
MS_PRINT_STR MSG1
MS_PRINT_STR MSG2
INT 20h
END INIZIO

ENDIF
.MODEL tiny
.DATA
MSG1 DB "Messaggio 1",CR,LF, "$"
MSG2 DB "Messaggio 2",CR,LF, "$"
.CODE
ORG 100h
INIZIO:
MS_CLRSCR
MS_PRINT_STR MSG1
MS_PRINT_STR MSG2
INT 20h
END INIZIO
LIB_MACRO.INC

CR EQU 13d
LF EQU 10d
; PULISCE IL VIDEO
MS_CLRSCR MACRO
PUSH AX
MOV AH,00H
MOV AL,03H
INT 10h
POP AX
ENDM
; STAMPA UNA STRINGA
MS_PRINT_STR MACRO stringa
PUSHA
LEA DX, stringa
MOV AH, 9
INT 21H
POPA
ENDM

Disassemblando il programma eseguibile troviamo conferma alle affermazioni fatte


relativamente alle macro ovvero che ogni chiamata a macro viene sostituita con il
codice contenuto nel corpo della macro stessa.
ANALISI CODICE CON MACRO

C:\trans>debug putciao.com
-u 100 120
17C7:0100 50
PUSH AX
17C7:0101 B400
MOV AH,00
17C7:0103 B003
MOV AL,03
17C7:0105 CD10
INT 10
17C7:0107 58
POP AX
17C7:0108
17C7:0109
17C7:010A
17C7:010E
17C7:0110
17C7:0112
17C7:0113

50
52
8D162201
B409
CD21
58
5A

PUSH AX
PUSH DX
LEA DX,[0122]
MOV AH,09
INT 21
POP AX
POP DX

17C7:0114
17C7:0115
17C7:0116
17C7:011A
17C7:011C
17C7:011E
17C7:011F

50
52
8D163001
B409
CD21
58
5A

PUSH AX
PUSH DX
LEA DX,[0130]
MOV AH,09
INT 21
POP AX
POP DX

17C7:0120 CD20
INT 20
-d 122 le
17C7:0120 4D 65 73 73 61 67-67 69 6F 20 31 0D 0A 24 Messaggio 1..$
-d 130 le
17C7:0130 4D 65 73 73 61 67 67 69-6F 20 32 0D 0A 24 Messaggio 2..$

Vediamo ora di sottolineare un possibile problema di sintassi: quando nel corpo della
macro sono presenti etichette d'indirizzo necessario dichiararle nel corpo della
macro con la pseudoOperazione LOCAL.

Sebbene la riga contenente la Macroistruzione, tra le altre del programma, dia la


sensazione di eleganza e compattezza, non bisogna dimenticare l'assemblatore la
sostituir con le istruzioni del suo corpo ogni volta che viene chiamata in causa
(anche molte volte nell'ambito del programma sorgente).
Per mostrare questo effetto consideriamo la seguente macro di test.le macro sia
chiamata due volte come si vede nel listato:
M_Test MACRO Dato
LOCAL _UgualeAZero
MOV AL,Dato
CMP AL,0H
JZ _UgualeAZero
DEC AL
MOV Dato, AL
_UgualeAZero:
ENDM
.MODEL tiny
.DATA
Numero DB 12
.CODE ; segmento codice
ORG 100h
INIZIO:
M_Test Numero
M_Test Numero
INT 20h
END INIZIO

Per la presenza di LOCAL, l'etichetta originaria _UgualeAZero viene sostituita con


un nuovo simbolo fatto con 2 punti di domanda e un numero progressivo, ??0000; il
numero viene incrementato ogni volta che l'assemblatore deve sostituire
un'etichetta locale di una Macro, indipendentemente dal suo nome corrente e dalla
Macro che la utilizza.
Cos, nell'esempio, l'etichetta _UgualeAZero viene chiamata ??0000 la prima volta
e ??0001 la seconda; in questo modo l'assemblatore rende unici i simboli a
riferimenti presenti nelle macro.
Se non avessimo dichiarato LOCAL _UgualeAZero il compilatore, costretto a
riscrivere 2 volte il testo del corpo della Macro avrebbe creato due etichette con lo
stesso nome generando nella prima passata l'error A2004 (Redefinition of symbol),
mentre nella seconda passata 2 errori (error A2026: Reference to multidefined
symbol e error A2005: Symbol is multidefined: MMM1) e questo ad ogni occorrenza
dell'etichetta _UgualeAZero (cio 2 volte).
L'uso delle MacroIstruzioni molto utile ma spesso la loro definizione, all'inizio di un
programma, potrebbe diventare troppo voluminosa. La soluzione sta nell'estrarre
tutte le Macro definite, di volta in volta, dai nostri programmi sorgente,
raccogliendole in una libreria, partendo dal presupposto che quasi certamente
torneranno utili anche in altri programmi.
Naturalmente dopo averle estratte dal programma se non si prendono
provvedimenti non saranno pi reperibili dal compilatore che, alla prima etichetta di
macro non trovata, segnaler errore (error A2105: Expected: instruction, directive,

or label).
Per renderle ancora visibili senza che siano fisicamente presenti si usa la
PseudoOperazione INCLUDE, che si occupa di cercare sul disco fisso il file di testo
indicato e di inserirlo pari pari nel punto dove abbiamo l'include. Naturalmente se il
file non viene trovato viene segnalato errore (error A2116: Include file not found:
PIPPO.MAC) e la direttiva viene ignorata.
Certamente ci si chieder perch la direttiva INCLUDE sia talvolta racchiusa tra altre
2 PseudoOperazioni: IF1 che obbliga il compilatore a fare l'acquisizione solo
durante la prima passata mentre ENDIF sintatticamente necessaria per chiudere
la richiesta condizionata.
La presenza di IF1 evita inutile lavoro al compilatore: durante la prima passata legge
il file che raccoglie le nostre MacroIstruzioni, annota le etichette di ciascuna di esse
nella Tabella dei Simboli, e sostituisce le chiamate alle Macro con i rispettivi corpi;
nella seconda, se non presente IF1, aggiunge al listato tutto il testo contenuto nel
file incluso, marcando con una C ogni riga aggiunta, operazione del tutto inutile, sia
perch allunga a dismisura il listato, sia perch i compiti indispensabili sono gi stati
assolti durante la prima passata.

LE PROCEDURE

Una Procedura un insieme di istruzioni specializzato nel compiere un determinato


compito; Quando una sequenza di istruzioni utilizzata di frequente, pu tornar
utile inserirla in una procedure che pu essere utilizzata addirittura anche da altri
programmi (si pensi ad una procedura Clear_Screen).
Per questa ragione molto saggio abituarsi ad estrarre queste parti di codice e
renderle autonome, dando loro un nome e una struttura; in aggiunta pu tornare
utile raggrupparle in raccolte, dette Librerie, facilmente visibili da ogni sorgente
interessato al loro utilizzo con l'aiuto della direttiva EXTRN.
Vediamo ora di riassumere le caratteristiche di una Procedura: il progetto di una
procedura coinvolge il programmatore in uno sforzo una-tantum, ben ripagato dal
fatto che il suo lavoro sar riutilizzabile in molte altre occasioni, con notevole
risparmio di tempo
Per richiamare il servizio fornito da una procedura basta utilizzare una sola
istruzione, di norma la CALL.
Per la sua definizione si utilizzano le direttive PROC e ENDP, all'interno delle quali
sono raccolte le istruzioni chiamate a svolgere il compito affidato al
sottoprogramma; ciascuna delle due direttive sar preceduta dal nome scelto per la
procedura:
NomeProcedura PROC [NEAR/FAR]
... codice procedura
RET
NomeProcedura ENDP
Dopo la direttiva PROC si specifica il tipo di procedura che si desidera progettare,
NEAR o FAR; nel primo caso la procedura pu essere chiamata solo all'interno del
segmento di codice di definizione, mentre nel secondo la procedura pu essere
chiamata anche a partire da segmenti di codice diversi da quello di definizione.
Una procedura Near (intrasegmentale) pu essere definita anche senza queste
formalit, semplicemente aggiungendo : (due punti) dopo il suo nome
Il tipo e il numero di istruzioni che raccoglie irrilevante; di certo l'ultima dovr
essere l'istruzione RET
la presenza della RET finale consente al processore di rientrare nel programma
chiamante, subito sotto l'istruzione (CALL NOMEPROCEDURA) che ha eseguito la
chiamata
Il rientro reso possibile dal fatto che l'istruzione CALL ha salvato nello stack
l'indirizzo dellistruzione a s successiva (la sola parte offset, se di tipo Near, o quello
completo segment:offset, se di tipo Far) e che l'istruzione RET lo ripristina nei
medesimi registri puntatori di programma (solo IP, se Near, o CS:IP, se Far)
E' possibile differenziare il funzionamento di una procedura passandole dei
parametri; un parametro di norma un valore binario predisposto dal programma
chiamante in registri o variabili locali, condivise con la procedura. Inoltre

anche la procedura pu lasciare eventuali risultati in registri o variabili locali,


condivise con il programma chiamante.
La definizione e l'uso delle Procedure rende i programmi pi organizzati, leggibili e
fa risparmiare tempo di programmazione. Contrariamente alle MACROISTRUZIONI
consente un risparmio di memoria di programma.

DIFFERENZE TRA MACRO E PROCEDURE


Siamo pronti per tirare le somme: abbiamo la consapevolezza che sia le Procedure
che le Macroistruzioni sono strutture molto utili. Il problema da risolvere : quando
conviene usare le prime in alternativa alle seconde?
Entrambe rendono pi leggibile il codice sorgente (non solo assembly) e riducono gli
errori di programmazione, ma sono estremamente diverse tra loro! Vediamo alcune
prerogative emerse nei due paragrafi precedenti.
1) L'uso di una procedura quasi sempre inutile se non si provvede a inizializzare
uno o pi registri prima della sua chiamata; Il passaggio dei parametri avviene
mediante l'opportuna inizializzazione di registri e di variabili di memoria
2) La procedura consente un risparmio in byte nel segmento codice
3) La macro istruzione accetta dei parametri a fianco del nome della macro
4) La macro non comporta alcun risparmio di memoria di programma
Per arrivare alla soluzione conviene rivedere le considerazioni precedenti in una
prospettiva di confronto e con spirito di osservazione.
Riassumendo:
L'uso corretto di una Macro quello di organizzare la chiamata di una Procedura: il
suo compito quello di sostituire (con una sola riga di programma) il gruppo di
istruzioni necessarie per inizializzarla; la cosa che non fa risparmiare memoria di
programma (il codice macchina prodotto dall'assemblatore sempre lo stesso) ma
rende pi leggibile il programma.
Quindi quando una procedura non pu essere chiamata senza averle anteposte una
o pi istruzioni di inizializzazione, il momento di creare una Macro!
In sostanza la presenza di una Macroistruzione in un sorgente ASM non cambia
nulla, se non esteticamente e funzionalmente; la quantit di bytes prodotta dal
compilatore esattamente identica (a parte quella risparmiata per la presenza di
procedure) ma il codice risulter pi compatto e breve.
Tramite la macro i parametri da associare ai vari registri risultano semplicemente
elencati uno dopo l'altro, separati da una virgola, sottolineando una volta di pi la
maggior leggibilit di questa struttura

LE OPERAZIONI FONDAMENTALI

LA GESTIONE DELL'OUTPUT
Singolo carattere
A) Una prima modalit per scrivere un singolo carattere a video quella di utilizzare
il servizio DOS (Int 21h) di stampa singolo carattere (2H) illustrato nel seguente
programma:
- caricare il carattere da stampare nella parte bassa DL del registro DX
- impostare il sottoprogramma DOS di I/O da utilizzare inserendo 02h nel registro
AH
- richiamare la funzione DOS mediante l'interrupt 21
ESEMPIO FUNZIONAMENTO CMP

C:\trans>debug putciao.com
-u 100 l13
168E:0100 B402 MOV AH,02
168E:0102 B243 MOV DL,43
168E:0104 CD21 INT 21
168E:0106 B249 MOV DL,49
168E:0108 CD21 INT 21
168E:010A B241 MOV DL,41
168E:010C CD21 INT 21
168E:010E B24F MOV DL,4F
168E:0110 CD21 INT 21
168E:0112 C3 RET
-t
AX=0200 BX=0000 CX=0013 DX=0000
DS=168E ES=168E SS=168E CS=168E
168E:0102 B243 MOV DL,43
-t
AX=0200 BX=0000 CX=0013 DX=0043
DS=168E ES=168E SS=168E CS=168E
168E:0104 CD21 INT 21
-p
C
AX=0243 BX=0000 CX=0013 DX=0043
DS=168E ES=168E SS=168E CS=168E
168E:0106 B249 MOV DL,49
-t
AX=0243 BX=0000 CX=0013 DX=0049
DS=168E ES=168E SS=168E CS=168E
168E:0108 CD21 INT 21
-p
I
AX=0249 BX=0000 CX=0013 DX=0049
DS=168E ES=168E SS=168E CS=168E
168E:010A B241 MOV DL,41
-t
AX=0249 BX=0000 CX=0013 DX=0041
DS=168E ES=168E SS=168E CS=168E
168E:010C CD21 INT 21
-p
A
AX=0241 BX=0000 CX=0013 DX=0041
DS=168E ES=168E SS=168E CS=168E
168E:010E B24F MOV DL,4F
-t
AX=0241 BX=0000 CX=0013 DX=004F
DS=168E ES=168E SS=168E CS=168E
168E:0110 CD21 INT 21
-p
O
AX=024F BX=0000 CX=0013 DX=004F
DS=168E ES=168E SS=168E CS=168E
168E:0112 C3 RET

CREA CON: DEBUG<PUTC.TXT

a
MOV
MOV
INT
MOV
INT
MOV
INT
MOV
INT
RET
SP=FFFE BP=0000 SI=0000 DI=0000
IP=0102 NV UP EI PL NZ NA PO NC

AH,
DL,
21
DL,
21
DL,
21
DL,
21

2
43
49
41
4F

n putciao.com
r cx
13
w
q

SP=FFFE BP=0000 SI=0000 DI=0000


IP=0104 NV UP EI PL NZ NA PO NC

SP=FFFE BP=0000 SI=0000 DI=0000


IP=0106 NV UP EI PL NZ NA PO NC
SP=FFFE BP=0000 SI=0000 DI=0000
IP=0108 NV UP EI PL NZ NA PO NC

Legenda:
Ricordarsi di aggiungere l'invio nel file

SP=FFFE BP=0000 SI=0000 DI=0000


IP=010A NV UP EI PL NZ NA PO NC
SP=FFFE BP=0000 SI=0000 DI=0000
IP=010C NV UP EI PL NZ NA PO NC

SP=FFFE BP=0000 SI=0000 DI=0000


IP=010E NV UP EI PL NZ NA PO NC
SP=FFFE BP=0000 SI=0000 DI=0000
IP=0110 NV UP EI PL NZ NA PO NC

SP=FFFE BP=0000 SI=0000 DI=0000


IP=0112 NV UP EI PL NZ NA PO NC

B) Un'altra alternativa per scrivere un carattere a schermo quello di utilizzare i


servizi BIOS relativi al Video (Int 10h). In questo caso occorre seguire questi
passaggi:
- caricare il carattere da stampare nella parte bassa AL del registro AX
- impostare il sottoprogramma BIOS (stampa carattere in modo tty) da utilizzare

inserendo 0Eh nel registro AH


- richiamare la funzione BIOS mediante l'interrupt 10
ESEMPIO STAMPA SINGOLO CARATTERE

C:\trans>debug < putc.txt


-a
166C:0100 MOV AH, E
166C:0102 MOV AL, 43
166C:0104 INT 10
166C:0106 MOV AL, 49
166C:0108 INT 10
166C:010A MOV AL, 41
166C:010C INT 10
166C:010E MOV AL, 4F
166C:0110 INT 10
166C:0112 RET
166C:0113
-n putciao.com
-r cx
CX 0000
:13
-w
Scrittura di 00013 byte in corso
-q

CREA CON: DEBUG<PUTC.TXT

a
MOV
MOV
INT
MOV
INT
MOV
INT
MOV
INT
RET

AH,
AL,
10
AL,
10
AL,
10
AL,
10

E
43
49
41
4F

n putciao.com
r cx
13
w
q

Legenda:
Ricordarsi di aggiungere l'invio nel file
dopo la q

C:\trans>putciao
CIAO
C:\trans>

C) Altra modalit quella di scrivere direttamente nella memoria video. Si consiglia


di analizzare l'approfondimento relativo alla scheda video.
Adesso analizziamo un codice di esempio (memvideo_puts.asm) che non fa ricorso
ad alcun interrupt ma scrive direttamente nella memoria video il testo da
visualizzare.
.MODEL tiny
.CODE ; segmento codice
ORG 100h
; il prg inizia all'indirizzo 100h
INIZIO:
MOV AX,0b800h
MOV ES,AX

; posiziono il registro ES all'inizio


; della memoria relativa al modo testo

MOV AL, 'A'


MOV AH, 17h
MOV ES:[00h], AX

; Scrive la lettera A
; in bianco su sfondo blu
; sulla riga 1 - colonna 1

MOV
MOV
MOV
MOV

;
;
;
;

AL, 'Z'
ES:[02h], AL
AH, 79h
ES:[03h], AL

INT 20h

Scrive la lettera I
sulla riga 1 - colonna 2
Coloro di blu su sfondo bianco
il metodo equivalente a quello usato per la A

; termina il programma - equivalente all'int 21h AL=00h

END INIZIO
ESEMPIO STAMPA STRINGA

AZ <== si noti che questa AZ stata scritta dall'eseguibile memvideo_putc.asm


C:\trans>ml memvideo_putc.asm
Microsoft (R) Macro Assembler Version 6.15.8803
Copyright (C) Microsoft Corp 1981-2000. All rights reserved.
Assembling: memvideo_putc.asm
Microsoft (R) Segmented Executable Linker Version 5.60.339 Dec 5 1994
Copyright (C) Microsoft Corp 1984-1993. All rights reserved.
Object Modules [.obj]: memvideo_putc.obj /t
Run File [memvideo_putc.com]: "memvideo_putc.com"
List File [nul.map]: NUL
Libraries [.lib]:
Definitions File [nul.def]:

C:\TRANS>memvideo_putc

La macro che consente di stampare a video un singolo carattere che inclusa nella
libreria OUTPUT.INC allegata
MS_PUTC MACRO char
PUSH AX
; Salvo il valore nel registro
; Utilizza le funzioni VIDEO & SCREEN Service (int10 0Eh):
; scrive un carattere in modalit teletype
MOV AL, char
MOV AH, 0Eh
INT 10h
POP AX
; Ripristina valore nel registro
ENDM

Ecco come utilizzarla (UsoLib_Putc.asm):


INCLUDE output.inc
.MODEL tiny
.DATA
Lettera DB 'M'
.CODE
ORG 100h
INIZIO:
MS_CLRSCR
MS_PUTC Lettera
MS_PUTC "S"
INT 20h
END INIZIO

; inclusione della libreria contenente le macro


; segmento dati
; segmento codice
; il prg inizia all'indirizzo 100h
;
;
;
;

Macro per la pulizia del video


Macro per la stampa di una variabile carattere
Macro per la stampa di una costante carattere
Termina il programma - equivalente all'int 21h AL=00h

Stringa di caratteri
A) Un modo per stampare una stringa di caratteri quello di utilizzare il servizio
DOS (Int 21h) relativo alla stampa di una stringa (AH=09h). Un programma di
esempio (int21_prints.asm) per la stampa di una stringa il seguente:
.MODEL tiny
.DATA
frase DB "Ciao$"
.CODE
ORG 0100H
INIZIO:
MOV AH, 09H
LEA DX, frase
INT 21H
RET
END INIZIO

Questo programma d'esempio assemblato mediante ML INT21_PRINTS.ASM


fornisce l'eseguibile prints.com
ESEMPIO STAMPA STRINGA

C:\trans>ml Int21_prints.asm
Microsoft (R) Macro Assembler Version 6.15.8803
Copyright (C) Microsoft Corp 1981-2000. All rights reserved.
Assembling: Int21_prints.asm
Microsoft (R) Segmented Executable Linker Version 5.60.339 Dec 5 1994
Copyright (C) Microsoft Corp 1984-1993. All rights reserved.
Object Modules [.obj]: Int21_prints.obj /t
Run File [prints.com]: "Int21_prints.com"
List File [nul.map]: NUL
Libraries [.lib]:
Definitions File [nul.def]:

CREA CON: DEBUG<PRINTS.TXT

a
MOV AH, 9
LEA DX,[010A]
INT 21
RET
f 10A 10E 'C' 'i' 'a' 'o' '$'
n prints.com
r cx
F
w
q

C:\trans>Int21_prints
Ciao
C:\TRANS>debug Int21_prints.com
-u 100 l9
17C1:0100 B409
MOV AH,09
17C1:0102 8D160A01 LEA DX,[010A]
17C1:0106 CD21
INT 21
17C1:0108 C3
RET
-d 10A l5
17C1:0100 43 69 61 6F 24 Ciao$
-g
Ciao
L'esecuzione del programma terminata normalmente

Legenda:
Ricordarsi di aggiungere l'invio nel file
dopo la q

La funzione DOS di stampa una stringa richiede necessariamente che questa sia
terminata con il carattere '$'.
B) E' possibile visualizzare una stringa anche mediante un loop che stampi ogni
singolo carattere della sequenza. (sorgente loop_prints.asm)
.MODEL tiny
.DATA
frase db "Ciao$"
.CODE
ORG 0100H
INIZIO:
LEA SI,frase
_leggiUnChr:
MOV DL,DS:[SI]
DS:SI)
CMP DL,'$'
JZ _fine
MOV AH,2H
INT 21H

; metto in SI l'indirizzo della frase


; carico in DL l'SI-esimo carattere (che all'indirizzo
; se corrisponde al carattere di stop mi fermo
; Servizio DOS 2H di stampa singolo carattere

INC SI
JMP SHORT _leggiUnChr
_fine:
INT 20h
; Termina il programma - equivalente all'int 21h AL=00h
END INIZIO

La macro che consente di stampare a video una stringa che inclusa nella libreria
OUTPUT.INC allegata
MS_PRINT_STR MACRO stringa
PUSH AX
; Salvo i valori dei registri
PUSH DX
; DX e AX
; Utilizza il servizio DOS (Int21) print string (int21 09h)
; che scrive una stringa in modalit teletype
LEA DX, stringa
MOV AH, 9
INT 21H
POP DX
; Ripristina i valori nei registri
POP AX
; AX e DX
ENDM

Ecco un esempio di programma che mostra come utilizzarla (UsoLib_prints.asm):


INCLUDE output.inc
; inclusione della libreria contenente le macro
.MODEL tiny
.DATA
Msg DB "Salve a Tutti", 13d, 10d, "$"
.CODE
; segmento codice
ORG 100h
; il prg inizia all'indirizzo 100h
INIZIO:
MS_PRINT_STR Msg
; Macro per la stampa di una stringa
INT 20h
; Termina il programma - equivalente all'int 21h AL=00h

END INIZIO

Numero
In assembler i dati vengono interpretati sempre come sequenze di byte pertanto la
stampa di un numero richiede una conversione del dato in stringa. Per stampare
viene costruito un ciclo che estrae ad ogni iterazione un digit della rappresentazione
decimale (resto divisione per 10). Visualizziamo la macro
MS_PRINT_INT MACRO Numero
Local _NonNegativo, _Stampa, _Loop
PUSHA
; Salva il contenuto dei registri nello stack
MOV AX, Numero
; mette il valore da stampare in AX
CMP AX, 0
JNS _NonNegativo
; salta alla gestione dei positivi se non c' segno
NEG AX
; Se negativo cambio di segno con NEG
MS_PUTC '-'
; e stampo il segno
_NonNegativo:
; Inizializzazione a 10 per estrarre i singoli numeri
; della rappresentazione in base 10
MOV BX, 10
MOV CX, 0
; CX conta il numero di digit presenti nel numero
_Loop:
MOV DX, 0
DIV BX
ADD DX, 48
PUSH DX
INC CX
CMP AX, 0
JNE _Loop
_Stampa:
POP DX
MOV AH, 02h
INT 21h
LOOP _Stampa
POPA
ENDM

; questa istruzione necessaria poich potrei avere un Divide Overflow


; effettua la divisione tra il numero nei 32 bit DX:AX e BX (contiene 10d)
;
;
;
;

Essendo BX a 16 bit (1 Word) il resto della divisione va in DX


aggiungo 48 ovvero il codice ASCII di zero
memorizzo nello stack il resto
incremento di CX che conta i digit estratti

; se in AX non ho zero ripeto l'operazione


;
;
;
;
;

copio il valore in cima allo stack in DX


stampa di un singolo carattere mediante il
servizio dos INT 21 - 02h
ripeto CX volte la stampa
Ripristina i valori nei registri

Ecco un esempio di programma che mostra come utilizzarla (UsoLib_printn.asm):


INCLUDE libreria.inc
INCLUDE output.inc
.MODEL tiny
.DATA
Nr dw 21
.CODE
ORG 100h
INIZIO:
MS_PRINT_INT Nr
MS_ACAPO
MS_PRINT_INT -3
MS_ACAPO
MS_PRINT_INT 0
INT 20h
END INIZIO

; inclusione della libreria contenente le macro


; inclusione della libreria contenente le macro
; segmento dati
; segmento codice
; il prg inizia all'indirizzo 100h

; Termina il programma - equivalente all'int 21h AL=00h

LA GESTIONE DELL'INPUT
Un primo metodo di fornire dei dati quello passarglierli direttamente come i
parametri sulla linea di comando.
E' il tipico approccio applicato per i cosiddetti switch. Gli switch sono comandi
secchi, di solito costituiti da una sola lettera con davanti uno slash o il segno meno
(ad esempio: /H o -H). Questa tecnica consente di
creare un eseguibile multifunzionale in grado di funzionare in modalit diverse a
seconda della situazione contingente al momento del suo lancio.
Prima di procedere vediamo un p di teoria
Il PSP (prefisso del segmento di programma) un'area di memoria di 256 bytes (100h) collocata all'inizio del
segmento di memoria destinato (dal loader del dos) ad un Programma eseguibile, nel momento del suo
caricamento in memoria.
Quando il Caricatore DOS (loader) chiamato ad allocare in memoria un programma eseguibile riserva per esso
la prima zona di memoria Ram libera [almeno un intero segmento (65526 locazioni consecutive)] facendole
puntare tutti i registri di segmento, CS, DS, ES e SS.
Nelle prime locazioni di questo segmento, a partire cio dall'indirizzo di offset 0000H, predispone il PSP, una
vera miniera di informazioni destinate alla gestione del programma stesso, ma estremamente utili anche a noi.
In particolare le prime 92 locazioni, al di sotto dell'offset 005CH, contengono dati gestionali molto importanti,
come:
- l'indirizzo a cui verr ceduto il controllo quando il programma ha termine,
- gli indirizzi delle procedure di servizio degli errori critici e della combinazione di tasti Ctrl-C,
- il puntatore alle stringhe Ascii dell'Environment (ambiente) che DOS utilizza per passare informazioni al
programma.
Per questa ragione fortemente consigliato non alterare il contenuto del PSP, almeno al fino a questo indirizzo.
Il contenuto delle rimanenti 164 locazioni non indispensabile: sono sostanzialmente dei buffer di servizio
utilizzabili dal nostro programma per il trasferimento di dati verso o da la memoria di massa, peraltro con
tecniche ormai obsolete.
Ci significa che generalmente questa parte del prefisso pu essere manipolata.
Solitamente il programma (codice e dati) viene caricato subito dopo il PSP, a partire dalla locazione 0100H,
almeno per gli eseguibili di tipo COM: in questo caso il contenuto dei registri di segmento non viene modificato,
cio puntano ancora tutti l'inizio del PSP. I 4 segmenti sono dunque fisicamente sovrapposti.
In un programma tipo EXE pu capitare invece che la sua intestazione (header) suggerisca al loader di cambiare
l'indirizzo di partenza del codice (comunque puntato da CS:IP) lasciando in IP ad un valore diverso da 0100H; in
questo caso pu succedere che CS e SS assumano valori diversi da quelli inizialmente predisposti.
In ogni caso DS e ES continuano dunque a puntare all'inizio del PSP.
Le ultime 128 locazioni del PSP (da 0080H a 00FFH) sono senz'altro riutilizzabili senza problemi dal programma.
Di solito questa zona di memoria usata dal Dos per 2 funzioni molto specifiche:
- come Buffer temporaneo di default per i dati da trasferire da e verso un disco: quest'area significativa solo
con le (obsolete) funzioni DOS che gestiscono i files mediante la tecnica dei File Control Block; in questo caso
questa zona assume il nome di Area di Trasferimento per il Disco (DTA, Disk Transfer Area).
- come area di memoria per la stringa dei parametri eventualmente passati al nostro programma sulla linea di
comando;

Per capire questa modalit analizziamo in dettaglio questo esempio. Si consideri un


comando Set_Date che rivisualizza la data e l'ora digitate sulla linea di comando
C:\LAVORO>Set_Date 04/01/2010 12:21:32

Vediamo la situazione in memoria, dentro il PSP (dopo le prime 128 / 80h locazioni),

dopo aver lanciato il comando suggerito precedentemente:


ESEMPIO LETTURA PARAMETRI IN LINEA

C:\trans>debug prn_date.com 04/01/2010 12:21:32


-d 80
17C7:0080 14 20 30 34 2F 30 31 2F-32 30 31 30 20
17C7:0090 32 31 3A 33 32 0D 00 00-00 00 00 00 00
17C7:00A0 00 00 00 00 00 00 00 00-00 00 00 00 00
17C7:00B0 00 00 00 00 00 00 00 00-00 00 00 00 00
17C7:00C0 00 00 00 00 00 00 00 00-00 00 00 00 00
17C7:00D0 00 00 00 00 00 00 00 00-00 00 00 00 00
17C7:00E0 00 00 00 00 00 00 00 00-00 00 00 00 00
17C7:00F0 00 00 00 00 00 00 00 00-00 00 00 00 00
-u 100 12a
17C7:0100 B400
MOV
AH,00
17C7:0102 A08000
MOV
AL,[0080]
17C7:0105 48
DEC
AX
17C7:0106 83F800
CMP
AX,+00
17C7:0109 7C17
JL
0122
17C7:010B FC
CLD
17C7:010C BE8200
MOV
SI,0082
17C7:010F 8D3E2C01 LEA
DI,[012C]
17C7:0113 8BC8
MOV
CX,AX
17C7:0115 F3
REPZ
17C7:0116 A4
MOVSB
17C7:0117 03F9
ADD
DI,CX
17C7:0119 C60524
MOV
BYTE PTR [DI],24
17C7:011C 8D162C01 LEA
DX,[012C]
17C7:0120 EB04
JMP
0126
17C7:0122 8D16AA01 LEA
DX,[01AA]
17C7:0126 B409
MOV
AH,09
17C7:0128 CD21
INT
21
17C7:012A C3
RET
-q
C:\trans>prn_date
Non hai digitato alcun parametro in linea!
C:\trans>prn_date 04/01/2010 12.21.32
04/01/2010 12.21.32

31
00
00
00
00
00
00
00

32
00
00
00
00
00
00
00

CREA CON: DEBUG<PRN_DATE.TXT

3A
00
00
00
00
00
00
00

. 04/01/2010 12:
21:32...........
................
................
................
................
................
................

a
MOV AH,0
MOV AL,[80]
DEC AX
CMP AX,0
JL 0122
CLD
MOV SI,82
LEA DI,[12C]
MOV CX,AX
REPZ
MOVSB
ADD DI,CX
MOV BYTE PTR [DI],24
LEA DX,[012C]
JMP 0126
LEA DX,[1AA]
MOV AH,9
INT 21
RET
f 12c 1a9 20
f 1aa l2b 'Non hai digitato
alcun parametro in linea!$'
n prn_date.com
r cx
D5
w
q

Legenda:
Ricordarsi di aggiungere l'invio nel file
dopo la q

Osserviamo che 21 bytes del PSP sono stati coinvolti ed esattamente 1 in pi


rispetto al numero di caratteri digitati dopo il nome del programma sul prompt del
Dos (la stringa compresa lo spazio iniziale " 04/01/2010 12:21:32" ha 20 caratteri).
In pratica quella dei parametri una struttura ben precisa: il primo byte (all'indirizzo
0080H) esprime la lunghezza della coda del comando (cio della sequenza di
parametri). In altre parole il numero esadecimale dei caratteri digitati dopo il
nome dell'eseguibile. L'ultimo byte (dopo l'ultimo carattere della stringa) sempre
0DH, il valore Ascii che rappresenta la pressione del tasto Invio.
Quindi il testo dei parametri in linea inizia sempre alla locazione 0082H e non pu
essere pi lungo di 126 caratteri; in caso contrario andrebbe a sovrascrivere il
codice macchina del nostro eseguibile, senza dubbio presente a partire da 0100H. In
ogni caso il Dos previene (ed evita) questa eventualit limitando d'ufficio il numero
di caratteri digitabili in una riga di comando, e segnalando acusticamente con lo
speaker di sistema ogni eventuale abuso.
Mostriamo ora in che modo il nostro programma (prn_date.asm) pu leggere i
parametri passati sul comando di linea (cio digitati dopo il nome del programma).
Prendiamo come riferimento il comando dato all'inizio della pagina per inizializzare
l'ora.
.MODEL tiny
.DATA
;
Buffer db 126 dup (' ')
;
Avviso db 'Non hai digitato
.CODE
;
ORG 100h
;

segmento dati
area per registrare i parametri digitati linea
alcun parametro in linea!$'
segmento codice
il prg inizia all'indirizzo 100h

INIZIO:
MOV AH, 0
MOV AL, DS:[80h]
DEC AX
l'invio finale
CMP AX, 0
JL _noparam

;
;
;
;

Carico in AX la lunghezza della stringa digitata sulla


linea di comando, dopo il nome dell'eseguibile sul prompt,
il cui valore scritto all'indirizzo DS:[80h]
decremento di 1 tale valore poich escludo dalla conta

; se AX < 0 stamper che non ho


; argomenti in linea di comando

; Sequenza per copiare nell'array BUFFER i parametri digitati


; sulla linea di comando - si rimanda all'CrossRef relativo al
; comando MOVSB
CLD
; Setta a 0 il flag DF - DI e SI verranno quindi
incrementati
MOV SI,82H
; indirizzo stringa sorgente
LEA DI,buffer
; indirizzo stringa di destinazione
MOV CX, AX
; quante volte ripeto MOVSB
REP MOVSB
ADD DI, CX
MOV byte ptr [di], '$' ; aggiungo in fondo il terminatore di stringa
_siparam:
LEA DX, buffer
JMP _stampa
_noparam:
LEA DX, Avviso
_stampa:
MOV AH, 09h
INT 21h
RET
END INIZIO

; carica la stringa che avvisa che non sono digitati


; parametri di linea
; stampa la stringa il cui indirizzo in DX
; servizio DOS 21h - 09h: stampa stringa

Per funzionare correttamente la zona dati del nostro programma deve prevedere
un'adeguata quantit di bytes, identificata con l'etichetta Buffer, in cui trasferire i
caratteri del parametro in linea.
Il codice suggerito deve essere il primo ad essere eseguito, per evitare di perdere la
precaria informazione del PSP.
La macro che consente di leggere la stringa digitata sulla linea di comando subito
dopo il nome del programma, inclusa nella libreria INPUT.INC allegata, :
MS_GETARGS MACRO Q, Stringa
PUSHA
; copia in cima allo stack il contenuto di tutti i registri d'uso generale
; (ax, cx, dx, bx, sp, bp, si, di) e decrementa di 16 il valore nel registro
SP
PUSHF
; decrementa il registro SP di 2 e carica una copia del
; registro dei flag in cima allo stack.
MOV
MOV
DEC
MOV

AH, 0
AL, DS:[80h]
AX
Q, AX
;

; Carico in AX la lunghezza della stringa


; digitata dopo il nome dell'eseguibile sul prompt
; del dos - escludo dalla conta l'invio finale
registro in NR il numero di caratteri digitati

CMP AX, 0
JL _Esci

; se AX < 0 esco dalla macro lasciando NR a 0

CLD
MOV
LEA
MOV
REP

;
;
;
;

SI, 82H
DI, Stringa
CX, AX
MOVSB

Setta a 0 il flag DF - DI e SI verranno quindi incrementati


indirizzo stringa sorgente
indirizzo stringa di destinazione
quante volte ripeto MOVSB

ADD DI, CX
MOV byte ptr [DI], '$' ; aggiungo in fondo il terminatore di stringa
_Esci:
POPF
POPA
ENDM

Ecco un esempio di programma che mostra come utilizzarla


(UsoLib_LeggiParam.asm):
INCLUDE Input.inc
INCLUDE Output.inc

; inclusione delle librerie


; contenenti le macro

.MODEL tiny
.DATA
; segmento dati
Nr dw 0
Buffer db 126 dup (' ')
; area per registrare i parametri digitati linea
Avviso db 'Non hai digitato alcun parametro in linea!$'
.CODE
; segmento codice
ORG 100h
; il prg inizia all'indirizzo 100h
INIZIO:
MS_GETARGS Nr, Buffer
MOV AX, Nr
; ricarico in AX il Nr di caratteri scritto sulla linea di
comando
CMP AX, 0
; se AX < 0 stamper che non ho
JL _NoParam
; argomenti in linea di comando
MS_PRINT_STR Buffer
JMP _Fine
_NoParam:
; carica la stringa che avvisa che non sono digitati
MS_PRINT_STR Avviso
_Fine:
INT 20h
; Termina il programma - equivalente all'int 21h AL=00h
END INIZIO

Singolo carattere
Una prima modalit per leggere un singolo carattere da tastiera quello di utilizzare
il servizio DOS (Int 21h) di lettura singolo carattere (1h) con visualizzazione del
carattere appena digitato (ECHO). I passaggi da svolgere sono i seguenti:
- impostare il sottoprogramma DOS di I/O da utilizzare inserendo 01h nel registro
AH
- richiamare la funzione DOS mediante l'interrupt 21
- caricare in una opportuna locazione di memoria il carattere appena letto salvato
nella parte bassa DL del registro DX
Una seconda modalit per leggere un singolo carattere da tastiera quello di
utilizzare il servizio DOS (Int 21h) di lettura singolo carattere (8h) senza visualizzare
il carattere appena digitato (NO ECHO). I passaggi da svolgere sono i seguenti:
- impostare il sottoprogramma DOS di I/O da utilizzare inserendo 08h nel registro
AH
- richiamare la funzione DOS mediante l'interrupt 21
- caricare in una opportuna locazione di memoria il carattere appena letto salvato

nella parte bassa DL del registro DX


Le macro che consentono questa lettura, incluse nella libreria INPUT.INC allegata,
sono:
MS_GETC_ECHO MACRO Char
PUSH AX
; Salvo i valori dei registri
MOV AH, 01h ; Servizio dos Int 21h - 01h lettura con echo
INT 21h
MOV Char, AL
POP AX
; Ripristino i valori dei registri
ENDM
MS_GETC_NOECHO MACRO Char
PUSH AX
; Salvo i valori dei registri
MOV AH, 08h ; Servizio dos Int 21h - 08h lettura senza echo
INT 21h
MOV Char, AL
POP AX
; Ripristino i valori dei registri
ENDM

Ecco un esempio di programma che mostra come utilizzarla


(UsoLib_LeggiChar.asm):
INCLUDE libreria.inc
INCLUDE Input.inc
INCLUDE Output.inc

; inclusione delle librerie


; contenenti le macro

.MODEL tiny
.DATA
Carattere DB ?
.CODE
ORG 100h
INIZIO:
MS_GETC_NOECHO Carattere
MS_PUTC Carattere
MS_GETC_ECHO Carattere
INT 20h
AL=00h
END INIZIO

; segmento dati
; segmento codice

; Termina il programma - equivalente all'int 21h

Viene proposta un'ulteriore macro che legge un singolo carattere ma consente


anche di annullarlo mediante l'uso del backspace. Il carattere viene confermato
quando batto invio (sempre inclusa nella libreria INPUT.INC allegata)
MS_GETC MACRO Char
LOCAL _Lettura1Lettera, _Check_AltriChar, _LetturaSuccLettere ,_FineLettura
PUSHA
_Lettura1Lettera:
MOV CL, 0h
MOV AH, 01h
INT 21h
MOV CL, AL
CMP AL, BackSpace
JE _Lettura1Lettera
CMP AL, CarriageReturn
JE _FineLettura
_LetturaSuccLettere:
MOV AH, 08h
INT 21h
CMP AL, BackSpace
JNE _Check_AltriChar

; Inizializzo come se non avessi letto niente


; Servizio dos Int 21h - 01h lettura con echo
;
;
;
;
;

Salvo in CL la lettera appena letta


Se scrivo subito BackSpace
lo ignoro e riparto con la lettura
considero confermata la lettura con l'invio
e salto alla fine della macro

;
;
;
;

Servizio dos Int 21h - 01h lettura senza echo


Aanalizzo eventuali caratteri successivi
Se scrivo BackSpace cancello
Se non ho scritto backspace analizzo eventuali char nel buffer

MS_ERASECHAR
JMP _Lettura1Lettera

; Se scrivo backspace elimino l'ultimo carattere

_Check_AltriChar:
CMP AL, CarriageReturn
JE _FineLettura
MS_BEEP
; emetto il BEEP se non un invio o il primo char scritto
JMP _LetturaSuccLettere
_FineLettura:
MS_ACAPO
MOV Char,CL
POPA
ENDM

Ecco un esempio di programma che mostra come utilizzarla


(UsoLib_AdvReadChar.asm):
INCLUDE libreria.inc
INCLUDE Input.inc
INCLUDE Output.inc
.MODEL tiny
.DATA ; segmento dati
Carattere DB ?
.CODE ; segmento codice
ORG 100h
INIZIO:
MS_GETC A_Char
MS_GETC B_Char
MS_PUTC A_Char
MS_PUTC B_Char
INT 20h
AL=00h
END INIZIO

; inclusione delle librerie


; contenenti le macro

; Termina il programma - equivalente all'int 21h

;-----------------------------------------------------------------------------;
;
WRITE_STRING(DS:SI Text)
;-----------------------------------------------------------------------------;
;
Writes string from DS:SI until character #0 is met
Write_String:
mov ah, 0xE
xor bh, bh
mov bl, 0x7
.nextchar
lodsb
or al,al
jz .return
int 10h
jmp .nextchar
.return
ret

BiosCls :MOV AH,00H ;Pulisci lo schermo


MOV AL,03H ;(ClearScreen)
INT 10H
RET
KeyWait :MOV
INT
RET

AH,00H
16H

;Aspetta la pressione
;di un tasto

_prog

SEGMENT BYTE PUBLIC 'CODE'


ASSUME CS:_prog,DS:_prog
ORG
0100H
INIZIO: JMP
Main
KeyWait:MOV
INT
RET

AH,00H
16H

;Aspetta la pressione
;di un tasto

BiosCls:MOV
MOV
INT
RET

AH,00H
AL,03H
10H

Main:

BiosCls
KeyWait
AH,4CH
21H

CALL
CALL
MOV
INT

;Pulisci lo schermo
;(ClearScreen)

;Torna al dos

_prog
ENDS
-----------------------------; Ricerca del massimo in un vettore (array), questa volta uso scasb per
; scandire il vettore array, inoltre uso un trucco per definire in
; modo automatico la lunghezza del vettore.
;
;
; Per compilare usare tasm /Zi nomefile.asm
;
tlink /v nomefile
; Quindi per eseguire in debug td nomefile.exe
;
.MODEL SMALL
; uso della memoria
.STACK 100h
; profondita' stack
.386
; uso anche istruzioni del 386 e successivi
;
; segmento dati
.DATA
array
DB 5, 4, 88, -6, 23, 1, 2, -36, 100, -120
db 8, 11, 0, -33, 44
len
equ $ - array
; $ indica l'indirizzo corrente,
; quindi calcolo quanto e' lungo array
add_arr
dd array
; far address of array, cioe' cs:indirizzo
ciao
risp
endmes

DB
DB
db

13,10,'Esempio di programma in Assembler',13,10,'$'


13,10,'Il massimo : $'
13,10,'- Fine',13, 10,'$'

n
max

dw
db

len
?

;lunghezza array
; la risposta

; segmento codice
.CODE
inizio:
mov
ax,@data
mov
ds,ax
mov
dx,OFFSET ciao
mov
ah,9
int
21h

; comincia qui l'esecuzione


;set DS to point to the data segment
;point to the time prompt
;DOS: print string
;display the time prompt

xor
xor
xor
xor

ax,ax
bx, bx
cx, cx
dx, dx

;
;

mov
mov
les
mov
cld

al, array
max, al
di,add_arr
cx, n

;
;
;
;
;

primo elemento del vettore


lo salvo, caso mai fosse il massimo
indirizzo d'inizio del vettore
numero di elementi
clear direction flag for scasb

;
;
;
;
;
;

inizia la ricerca
al > es:[di] ?
no, al prossimo
si decrementiamo puntatore
sostituiamo il massimo
incrementiamo il puntatore

ciclo:
scasb
jg salta
dec di
mov al, es:[di]
inc di
salta:
loop ciclo

azzeriamo tutti i registri


(solo per chiarezza, e' inutile)

; controllo se e' l'ultimo e decremento cx

fine:
mov max, al
; salviamo il risultato in max
; ora stampo la risposta, prima un messaggio...
mov
dx,OFFSET risp
;point to the time prompt
mov
ah,9
;DOS: print string
int
21h
;display the prompt
; quindi max
mov al, max
shr al,4
call print
mov al, max
and al, 0fH
call print
mov
mov
int

dx,OFFSET endmes
ah,9
21h

mov
mov

ah,4ch
al,0

;
;
;
;

isoliamo una cifra esadecimale


stampiamo
riprendo risultato
isolo l'altra cifra
;messaggio finale
;DOS: print string
;display the prompt
;DOS: terminate program
;return code will be 0

int

21h

;terminate the program

; procedura ausiliaria per trasformare una cifra esadecimale in carattere


; e stamparla.
print:
add al, 30h
;converto le cifre 1-9 in caratteri
cmp al, 39H
; A-F hanno bisogno di un ritocco
jbe cambia
add al,7
cambia:
mov dl, al
; uso servizio del bios per stampare
mov ah, 2
int 21h
ret
end inizio
; fine dell'assembler, l'esecuzione
;
dovra' cominciare da inizio
-----------------------------; Programma di esempio in assembler:
; Scopo: Ricerca del massimo nel vettore "array"
;
; Le prime righe contengono alcuni comandi per indicare all'assemblatore
; il tipo di programma e come verra' gestita la memoria
;
; Per compilare usare tasm /Zi nomefile.asm
;
tlink /v nomefile
; Quindi per eseguire in debug td nomefile.exe
;
.MODEL
.STACK
.386
;
.DATA
array
ciao
risp
endmes
n
max

SMALL
100h

; I dati, contenuti nel loro segmento


DB 5, 4, 88, -6, 23, 1, 2, -36, 100, -120
DB 13,10,'Esempio di programma in Assembler',13,10,'$'
DB 13,10,'Il massimo : $'
db 13,10,'- Fine',13, 10,'$'
dw 10
;lunghezza array
db ?
; la risposta

.CODE
inizio:
mov
mov
mov
mov
int
mov
mov
mov
mov
add
dec

; uso della memoria


; profondita'dello stack (256 decimale)
; uso anche istruzioni del 386

; Qui inizia il vero programma nel suo segmento codice


ax,@data
ds,ax
dx,OFFSET ciao
ah,9
21h

al, array
max, al
bx, offset array
cx, bx
cx, n
cx

ciclo:
inc bx
cmp bx, cx
ja fine
cmp al, [bx]
jg ciclo
mov al, [bx]
jmp ciclo
fine:
; ora stampo il contenuto di al
mov max, al
mov
dx,OFFSET risp
mov
ah,9
int
21h
mov al, max
shr al,4
call print
mov al, max
and al, 0fH
call print
mov
dx,OFFSET endmes
mov
ah,9
int
21h
mov
ah,4ch
mov
al,0
int
21h

; DS punta al data segment


; messaggio iniziale
; DOS: print string
;
;
;
;
;
;

primo elemento del vettore


lo salvo, caso mai fosse il massimo
indirizzo d'inizio del vettore
ne faccio una copia
indirizzo finale + 1
indirizzo finale

;
;
;
;
;
;
;
;

inizia la ricerca
punto all'elemento successivo
e' l'ultimo ?
si
no, allora lo confronto, e' maggiore ?
no, vado al prossimo
si, lo sostituisco
e passo al prossimo

; salviamo il risultato in max


; il messaggio che precede il risultato
; DOS: print string

;
;
;
;

isoliamo una cifra esadecimale


stampiamo
riprendo risultato
isolo l'altra cifra

; messaggio di fine
; DOS: print string
; DOS: terminate program

print:
add
cmp
jbe
add
cambia:
mov

al, 30h
al, 39H
cambia
al,7

; converto le cifre 0-9 in caratteri


; A-F hanno bisogno di un ritocco

dl, al

; uso servizio del bios per stampare

mov ah, 2
int 21h
ret
end inizio

INT 10h / AH = 13h - write string.

input:
AL = write mode:
bit 0: update cursor after writing;
bit 1: string contains attributes.
BH = page number.
BL = attribute if string contains only characters (bit 1 of AL is zero).
CX = number of characters in string (attributes are not counted).
DL,DH = column, row at which to start writing.
ES:BP points to string to be printed.
example:
mov al, 1
mov bh, 0
mov bl, 0011_1011b
mov cx, msg1end - offset msg1 ; calculate message size.
mov dl, 10
mov dh, 7
push cs
pop es
mov bp, offset msg1
mov ah, 13h
int 10h
jmp msg1end
msg1 db " hello, world! "
msg1end:

Potrebbero piacerti anche