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:
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).
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
.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
CR EQU 0Dh
LF EQU 0Ah
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
(range: da 0 a 2 -1)
db -10
sbyte -10
dw -20
sword -20
n dd -30
n sdword -30
dd -10
real4 -10
dq -20
real8 20
dt -30
real10 30
variabile
a 3.40 x 10
38
(range da 2.23 x 10
variabile
-38
-308
a 1.79 x 10
308
(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
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
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>
- 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>
- 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>
MOV AL, X
ADD AL, Y
MOV S, AX
MS_PRINT_INT S
MS_ACAPO
;
;
;
;
- 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>
- 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>
<operando>
<operando>
<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)
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
<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)
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
- 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>
- 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>
JL : salta se Operatore1<Operatore2
JG : salta se Operatore1>Operatore2
JLE : salta se Operatore1<=Operatore2
JGE : salta se Operatore1>=Operatore2
destinazione sia abbastanza vicino: tra -128 byte e +127 byte. Ecco alcuni dei flag
coinvolti nelle istruzioni di salto condizionato
CF
PF
AF
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
- 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
...
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)
- 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
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
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
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
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.
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
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
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
Legenda:
Ricordarsi di aggiungere l'invio nel file
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>
; 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
END INIZIO
ESEMPIO STAMPA STRINGA
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
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
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]:
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
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
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
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;
Vediamo la situazione in memoria, dentro il PSP (dopo le prime 128 / 80h locazioni),
31
00
00
00
00
00
00
00
32
00
00
00
00
00
00
00
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
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
;
;
;
;
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
;
CMP AX, 0
JL _Esci
CLD
MOV
LEA
MOV
REP
;
;
;
;
SI, 82H
DI, Stringa
CX, AX
MOVSB
ADD DI, CX
MOV byte ptr [DI], '$' ; aggiungo in fondo il terminatore di stringa
_Esci:
POPF
POPA
ENDM
.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
.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
;
;
;
;
MS_ERASECHAR
JMP _Lettura1Lettera
_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
;-----------------------------------------------------------------------------;
;
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
AH,00H
16H
;Aspetta la pressione
;di un tasto
_prog
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
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
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
;
;
;
;
;
;
;
;
;
;
;
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
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
;
;
;
;
int
21h
SMALL
100h
.CODE
inizio:
mov
mov
mov
mov
int
mov
mov
mov
mov
add
dec
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
;
;
;
;
;
;
;
;
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
;
;
;
;
; messaggio di fine
; DOS: print string
; DOS: terminate program
print:
add
cmp
jbe
add
cambia:
mov
al, 30h
al, 39H
cambia
al,7
dl, al
mov ah, 2
int 21h
ret
end inizio
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: