![]() |
(Incompleto) MINI CORSO DI ASSEMBLER 8086 |
![]() |
OP-CODE | DEST | SOURCE |
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.
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 il terzo parametro ...
push [secondo_param] // ; Seguito dal secondo ...
push [primo_param] // ; e dal primo
call foo
add sp, 12 // ; 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>
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> |
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 | variabile n di un byte inizializzata a 10 (range da 0 a 255) |
n dw 20 | variabile n di due byte inizializzata a 20 (range da 0 a 65.535) |
n dd 30 | variabile n di quattro byte inizializzata a 30 (range da 0 a 4.294.967.295=232-1) |
n dq 40 | variabile n di otto byte inizializzata a 40 (disponibile dal 486) (range: da 0 a 18.446.744.073.709.551.615) |
n dt 50 | variabile n di dieci byte inizializzata a 50 (range: da 0 a 280-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 db -10 n sbyte -10 |
variabile n di un byte inizializzata a -10 (range da -128 a 127) |
n dw -20 n sword -20 |
variabile n di due byte inizializzata a -20 (range da -32.768 +32.767) |
n
dd -30 n sdword -30 |
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 dd -10 n real4 -10 |
variabile n di quattro byte inizializzata a -10 (range da 1.18 x 10-38 a 3.40 x 1038 ) |
n dq -20 n real8 20 |
variabile n di otto byte inizializzata a -20 (range da 2.23 x 10-308 a 1.79 x 10308 ) |
n
dt -30 n real10 30 |
variabile n di dieci byte inizializzata a -30 (range da 3.37 x 10-4932 a 1.18 x 104932 ) |
La dichiarazione di stringhe
lettera db "a" lettera db 'a' |
variabile lettera di un byte inizializzata a "a" |
s db "ciao" s db "c","i","a","o" invio db 0Dh, 0Ah |
variabile s inizializzata a "ciao". Il nome s fa riferimento 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
- Istruzione MOV
- Istruzione ADD
- Istruzione ADC
ASSEMBLER (OPER_SOMMA.ASM) |
INCLUDE Libreria.inc
; files contenenti le macro generali |
- Istruzione
SUB
Corrisponde all'istruzione: <operando1> = <operando1> - <operando2>
- Istruzione SBB
ASSEMBLER (OPER_DIFFERENZA.ASM) |
INCLUDE Libreria.inc
; files contenenti le macro generali
INT 20h
; Termina il programma - equivalente all'int 21h AL=00h
|
- Istruzione
INC e DEC
- Istruzione MUL e IMUL
ASSEMBLER (OPER_PRODOTTO.ASM) |
INCLUDE Libreria.inc
; files contenenti le macro generali |
- Istruzione
DIV e IDIV
ASSEMBLER (OPER_DIVISIONE.ASM) |
INCLUDE Libreria.inc
; files contenenti le macro generali |
- Operatore PTR
Esempio 3)
INC
[BX]
La cella da incrementare corrisponde ad una word o ad un byte ? L’assemblatore
non può saperlo e genera errore.
La soluzione è:
INC BYTE PTR [BX]
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 condizionati
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 (bit0÷bit3) e il secondo nibble (bit4÷bit7). |
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). |
SF | (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 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
- IF THEN ELSE END IF
if
Condizione then |
|
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 | ASSEMBLER (IFTHEN.ASM) |
SE
(A <B) ALLORA |
INCLUDE Output.inc
; contenenti le macro
|
- CICLO WHILE
While
Condizione |
|
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
; contenenti le macro
|
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
ESEMPIO USO MACRO CON UN SOLO FILE MACRO1.ASM | CON DUE FILE: MACRO2.ASM |
CR
EQU 13d |
IF1 |
LIB_MACRO.INC | |
CR
EQU 13d |
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 50 PUSH AX 17C7:0109 52 PUSH DX 17C7:010A 8D162201 LEA DX,[0122] 17C7:010E B409 MOV AH,09 17C7:0110 CD21 INT 21 17C7:0112 58 POP AX 17C7:0113 5A POP DX 17C7:0114 50 PUSH AX 17C7:0115 52 PUSH DX 17C7:0116 8D163001 LEA DX,[0130] 17C7:011A B409 MOV AH,09 17C7:011C CD21 INT 21 17C7:011E 58 POP AX 17C7:011F 5A 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
DIFFERENZE TRA MACRO E PROCEDURE
Quindi quando una procedura non può essere chiamata senza averle anteposte una o più istruzioni di inizializzazione, è il momento di creare una Macro!
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 | CREA CON: DEBUG<PUTC.TXT |
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 SP=FFFE BP=0000 SI=0000 DI=0000 DS=168E ES=168E SS=168E CS=168E IP=0102 NV UP EI PL NZ NA PO NC 168E:0102 B243 MOV DL,43 -t AX=0200 BX=0000 CX=0013 DX=0043 SP=FFFE BP=0000 SI=0000 DI=0000 DS=168E ES=168E SS=168E CS=168E IP=0104 NV UP EI PL NZ NA PO NC 168E:0104 CD21 INT 21 -p C AX=0243 BX=0000 CX=0013 DX=0043 SP=FFFE BP=0000 SI=0000 DI=0000 DS=168E ES=168E SS=168E CS=168E IP=0106 NV UP EI PL NZ NA PO NC 168E:0106 B249 MOV DL,49 -t AX=0243 BX=0000 CX=0013 DX=0049 SP=FFFE BP=0000 SI=0000 DI=0000 DS=168E ES=168E SS=168E CS=168E IP=0108 NV UP EI PL NZ NA PO NC 168E:0108 CD21 INT 21 -p I AX=0249 BX=0000 CX=0013 DX=0049 SP=FFFE BP=0000 SI=0000 DI=0000 DS=168E ES=168E SS=168E CS=168E IP=010A NV UP EI PL NZ NA PO NC 168E:010A B241 MOV DL,41 -t AX=0249 BX=0000 CX=0013 DX=0041 SP=FFFE BP=0000 SI=0000 DI=0000 DS=168E ES=168E SS=168E CS=168E IP=010C NV UP EI PL NZ NA PO NC 168E:010C CD21 INT 21 -p A AX=0241 BX=0000 CX=0013 DX=0041 SP=FFFE BP=0000 SI=0000 DI=0000 DS=168E ES=168E SS=168E CS=168E IP=010E NV UP EI PL NZ NA PO NC 168E:010E B24F MOV DL,4F -t AX=0241 BX=0000 CX=0013 DX=004F SP=FFFE BP=0000 SI=0000 DI=0000 DS=168E ES=168E SS=168E CS=168E IP=0110 NV UP EI PL NZ NA PO NC 168E:0110 CD21 INT 21 -p O AX=024F BX=0000 CX=0013 DX=004F SP=FFFE BP=0000 SI=0000 DI=0000 DS=168E ES=168E SS=168E CS=168E IP=0112 NV UP EI PL NZ NA PO NC 168E:0112 C3 RET |
a MOV AH, 2 MOV DL, 43 INT 21 MOV DL, 49 INT 21 MOV DL, 41 INT 21 MOV DL, 4F INT 21 RET n putciao.com r cx 13 w q |
Legenda: Ricordarsi di aggiungere l'invio nel file |
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 | CREA CON: DEBUG<PUTC.TXT |
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
C:\trans>putciao |
a MOV AH, E MOV AL, 43 INT 10 MOV AL, 49 INT 10 MOV AL, 41 INT 10 MOV AL, 4F INT 10 RET n putciao.com r cx 13 w q |
Legenda: Ricordarsi di aggiungere l'invio nel file dopo la q |
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
; posiziono il registro ES
all'inizio
MOV ES,AX
; della memoria relativa al
modo testo
MOV AL, 'A'
; Scrive la lettera A
MOV AH, 17h
; in bianco su sfondo blu
MOV ES:[00h], AX
; sulla riga 1 - colonna 1
MOV AL, 'Z'
; Scrive la lettera I
MOV ES:[02h], AL
; sulla riga 1 - colonna 2
MOV AH, 79h
; Coloro di blu su sfondo
bianco
MOV ES:[03h], AL
; il metodo è equivalente a
quello usato per la A
INT 20h
; 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 è
Ecco come utilizzarla (UsoLib_Putc.asm):
INCLUDE output.inc
; inclusione della libreria
contenente le macro
.MODEL tiny
.DATA
; segmento dati
Lettera DB 'M'
.CODE
; segmento codice
ORG 100h
; il prg inizia all'indirizzo
100h
INIZIO:
MS_CLRSCR
; Macro per la pulizia del
video
MS_PUTC Lettera
; Macro per la stampa di una
variabile carattere
MS_PUTC "S"
; Macro per la stampa di una
costante carattere
INT 20h
; Termina il programma -
equivalente all'int 21h AL=00h
END INIZIO
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 | CREA CON: DEBUG<PRINTS.TXT |
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]: 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 |
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 |
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 ; metto in SI
l'indirizzo della frase
_leggiUnChr:
MOV DL,DS:[SI] ; carico in DL l'SI-esimo
carattere (che è all'indirizzo DS:SI)
CMP DL,'$' ; se
corrisponde al carattere di stop mi fermo
JZ _fine
MOV AH,2H ;
Servizio DOS 2H di stampa singolo carattere
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 è
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
Ecco un esempio di programma che mostra come
utilizzarla (UsoLib_printn.asm):
INCLUDE libreria.inc ; inclusione della libreria
contenente le macro
INCLUDE output.inc ; inclusione della libreria
contenente le macro
.MODEL tiny
.DATA
; segmento dati
Nr dw 21
.CODE
; segmento codice
ORG 100h
; il prg inizia all'indirizzo
100h
INIZIO:
MS_PRINT_INT Nr
MS_ACAPO
MS_PRINT_INT -3
MS_ACAPO
MS_PRINT_INT 0
INT 20h
; Termina il programma -
equivalente all'int 21h AL=00h
END INIZIO
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 | CREA CON: DEBUG<PRN_DATE.TXT |
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 31 32 3A . 04/01/2010 12: 17C7:0090 32 31 3A 33 32 0D 00 00-00 00 00 00 00 00 00 00 21:32........... 17C7:00A0 00 00 00 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 00 00 00 ................ 17C7:00C0 00 00 00 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 00 00 00 ................ 17C7:00E0 00 00 00 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 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 |
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
; segmento dati
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:
MOV AH, 0
; Carico in AX la
lunghezza della stringa digitata sulla
MOV AL, DS:[80h]
; linea di comando, dopo il
nome dell'eseguibile sul prompt,
; il cui valore è scritto all'indirizzo DS:[80h]
DEC AX
; decremento di 1 tale
valore poiché escludo
dalla conta l'invio finale
CMP AX, 0
; se AX < 0 stamperò
che non ho
JL _noparam
; 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:
; carica la stringa che avvisa
che non sono digitati
LEA DX, Avviso
; parametri di linea
_stampa:
MOV AH, 09h
; stampa la stringa il cui
indirizzo è in DX
INT 21h
; servizio DOS 21h - 09h:
stampa stringa
RET
END INIZIO
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, è:
Ecco un esempio di programma che mostra come
utilizzarla (UsoLib_LeggiParam.asm):
INCLUDE Input.inc
; inclusione delle librerie
INCLUDE Output.inc
;
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:
Ecco un esempio di programma che mostra come
utilizzarla (UsoLib_LeggiChar.asm):
INCLUDE libreria.inc
; inclusione delle librerie
INCLUDE Input.inc
;
contenenti le macro
INCLUDE Output.inc
.MODEL tiny
.DATA
; segmento dati
Carattere DB ?
.CODE
; segmento codice
ORG 100h
INIZIO:
MS_GETC_NOECHO Carattere
MS_PUTC Carattere
MS_GETC_ECHO Carattere
INT 20h
; Termina il programma -
equivalente all'int 21h AL=00h
END INIZIO
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)
Ecco un esempio di programma che mostra come
utilizzarla (UsoLib_AdvReadChar.asm):
INCLUDE libreria.inc
; inclusione delle librerie
INCLUDE Input.inc
;
contenenti le macro
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
; Termina il programma -
equivalente all'int 21h AL=00h
END INIZIO
BiosCls :MOV AH,00H ;Pulisci lo schermo MOV AL,03H ;(ClearScreen) INT 10H RET KeyWait :MOV AH,00H ;Aspetta la pressione INT 16H ;di un tasto RET _prog SEGMENT BYTE PUBLIC 'CODE' ASSUME CS:_prog,DS:_prog ORG 0100H INIZIO: JMP Main KeyWait:MOV AH,00H ;Aspetta la pressione INT 16H ;di un tasto RET BiosCls:MOV AH,00H ;Pulisci lo schermo MOV AL,03H ;(ClearScreen) INT 10H RET Main: CALL BiosCls CALL KeyWait MOV AH,4CH ;Torna al dos INT 21H _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 DB 13,10,'Esempio di programma in Assembler',13,10,'$' risp DB 13,10,'Il massimo : $' endmes db 13,10,'- Fine',13, 10,'$' n dw len ;lunghezza array max db ? ; la risposta ; segmento codice .CODE inizio: ; comincia qui l'esecuzione mov ax,@data mov ds,ax ;set DS to point to the data segment mov dx,OFFSET ciao ;point to the time prompt mov ah,9 ;DOS: print string int 21h ;display the time prompt xor ax,ax ; azzeriamo tutti i registri xor bx, bx ; (solo per chiarezza, e' inutile) xor cx, cx xor dx, dx mov al, array ; primo elemento del vettore mov max, al ; lo salvo, caso mai fosse il massimo les di,add_arr ; indirizzo d'inizio del vettore mov cx, n ; numero di elementi cld ; clear direction flag for scasb ciclo: ; inizia la ricerca scasb ; al > es:[di] ? jg salta ; no, al prossimo dec di ; si decrementiamo puntatore mov al, es:[di] ; sostituiamo il massimo inc di ; incrementiamo il puntatore salta: loop ciclo ; 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 ; isoliamo una cifra esadecimale call print ; stampiamo mov al, max ; riprendo risultato and al, 0fH ; isolo l'altra cifra call print mov dx,OFFSET endmes ;messaggio finale mov ah,9 ;DOS: print string int 21h ;display the prompt mov ah,4ch ;DOS: terminate program mov al,0 ;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 SMALL ; uso della memoria .STACK 100h ; profondita'dello stack (256 decimale) .386 ; uso anche istruzioni del 386 ; .DATA ; I dati, contenuti nel loro segmento array DB 5, 4, 88, -6, 23, 1, 2, -36, 100, -120 ciao DB 13,10,'Esempio di programma in Assembler',13,10,'$' risp DB 13,10,'Il massimo : $' endmes db 13,10,'- Fine',13, 10,'$' n dw 10 ;lunghezza array max db ? ; la risposta .CODE ; Qui inizia il vero programma nel suo segmento codice inizio: mov ax,@data mov ds,ax ; DS punta al data segment mov dx,OFFSET ciao ; messaggio iniziale mov ah,9 ; DOS: print string int 21h mov al, array ; primo elemento del vettore mov max, al ; lo salvo, caso mai fosse il massimo mov bx, offset array ; indirizzo d'inizio del vettore mov cx, bx ; ne faccio una copia add cx, n ; indirizzo finale + 1 dec cx ; indirizzo finale ciclo: ; inizia la ricerca inc bx ; punto all'elemento successivo cmp bx, cx ; e' l'ultimo ? ja fine ; si cmp al, [bx] ; no, allora lo confronto, e' maggiore ? jg ciclo ; no, vado al prossimo mov al, [bx] ; si, lo sostituisco jmp ciclo ; e passo al prossimo fine: ; ora stampo il contenuto di al mov max, al ; salviamo il risultato in max mov dx,OFFSET risp ; il messaggio che precede il risultato mov ah,9 ; DOS: print string int 21h mov al, max shr al,4 ; isoliamo una cifra esadecimale call print ; stampiamo mov al, max ; riprendo risultato and al, 0fH ; isolo l'altra cifra call print mov dx,OFFSET endmes ; messaggio di fine mov ah,9 ; DOS: print string int 21h mov ah,4ch ; DOS: terminate program mov al,0 int 21h print: add al, 30h ; converto le cifre 0-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
input:example:
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.
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: