![]() | ![]() |
|
|
Dunque per prima cosa facciamo una breve carrellata sull'arte della
programmazione:
Qualunque programma per PC sia esso con estensione EXE o COM per DOS o
WINDOWS x.x ecc. ecc. ,esso è composto da una serie di numeri in sequenza che
indicano al processore (486,Pentium o altro) cosa fare.
A questo punto urge una breve spiegazione della circuiteria interna di un
computer.
Il computer possiede una memoria, (quella che è già insita nel sistema più
quella che aggiungete voi comprando le famose SIMM), bene, cos'è questa
memoria ?, essa è semplicemente una sequenza di minuscole celle in ognuna delle
quali è possibile memorizzare un numero compreso fra 0 e 255, (più avanti
capirete perché proprio 255), inoltre ogni cella è caratterizzata da un
indirizzo formato da un numero, proprio come il numero civico della vostra
abitazione, ovviamente questi numeri vanno da 0 per la prima cella fino alla
fine della memoria.
Se vi è chiaro questo sappiate che le cose sono un po' più complicate.Vi state
chiedendo come?. Bene ,allora bisogna sapere che il primo processore da cui
discende il vostro Pentium poteva indirizzare, ossia accedere fino 65535 celle
di memoria, alias 64Kb, ma ora i programmi più moderni 64Kb di memoria se la
mangiano a colazione, e quindi si è deciso di aumentare questo numero di
blocchi di memoria, ma non aumentando la dimensione del blocco originario, bensì
aggiungendo altri blocchi da 64Kb e facendo in modo che i processori successivi
tra cui il vostro pentium potesse scegliere tra i diversi blocchi in modo da
averli tutti a disposizione per l'uso. Comodo no?.
Bene, questi blocchi, che d'ora in poi chiameremo col loro nome , ossia
SEGMENTI, nel modello base sono sedici, numerati da 0 a 15, alcuni di questi
blocchi non sono disponibili ai programmi perché vengono monopolizzati da altre
perti del computer o dal sistema operativo, per esempio il blocco 0 è usato dal
BIOS e dal DOS per i loro affari, mentre i blocchi 10 e 11 sono di proprietà
della scheda video che li usa per mostrare sullo schermo tutto quello che i
programmi memorizzano in queste locazioni di memoria, inoltre il blocco 15 è di
proprietà del BIOS e non potete neanche scriverci perché è un blocco di sola
lettura. In pratica i blocchi disponibili ai programmi sono solo quelli che
vanno dal n.1 al n. 9 e neanche completamente.
Ok, detto questo vediamo come sono fatte queste celle di memoria, esse sono
semplicemente delle piccole celle che contengono 8 elementi elettronici i quali
possono trovarsi ognuno in due soli stati con tensione o senza tensione, ora se
gli stati possibili fossero 10 potrei semplicemente assegnare ad ogni stato un
numero da 0 a 9 e quindi ogni cella potrebbe contenere un numero compreso fra 0
e 99999999, ma ciò non è possibile e bisogna adoperare solo due numeri 0 e 1.
Come si fa?.
Prima chiediamoci perché il sistema numerico conta fino a 10, fatto?, ok, il 10
è stato scelto non in base a considerazioni altamente scientifiche, ma semplicemente
perché le nostre mani hanno 10 dita e prima si contava sulle mani. Tutto qui.
Delusi? No.
Ora abbiamo visto che il computer ha solo due dita. Vogliamo provare? Dunque
contiamo: 0 e scriviamo 0, aggiungiamo 1 e viene 1, aggiungiamo ancora 1,
che succede?, scriviamo zero con riporto di 1 quindi viene 10 che non è il
nostro dieci, ma il due scritto sulle dita del computer, aggiungiamo ancora 1 e
fanno 11 (3 in decimale), ancora uno e facciamo 100 (4 in decimale), se
continuate ad contare in questo modo raggiungerete le otto cifre ossia 11111111
che corrispondono a 255 in decimale, ecco spiegato il perché di quello strano
numero.
Vi anticipo che viene usato anche un altro sistema numerico dai programmatori,
che ovviamente non serve al processore, ma ai programmatori stessi per scrivere
in modo più le 525h72f ggibile i loro programmi, ossia il sistema esadecimale, che si
basa su una mano con sedici dita, perché sedici, perché 15 in binario si scrive
1111, ossia mezza cella di memoria utile per alcune funzioni
Veniamo alla seconda parte del corso. In questa lezione vedremo come è fatto un
processore per computer e come interagisce con il sistema complessivo.
Dunque, il processore non è altro che un chip elettronico simile a quelli che
vedete su qualsiasi altro circuito elettronico (quei scatolini neri di plastica
con tante zampette metelliche intorno), bene, sedici di queste zampette il
processore le usa per scegliere il blocco di memoria da cui vuole leggere o
scrivere un numero, altre sedici le usa per l'indirizzo della cella all'interno
del blocco stesso, inoltre possiede altre otto zampette su cui riceve o
spedisce il numero per/da la memoria, vi sono inoltre molte altre zampette che
servono per altri scopi, ma a noi non interessano.
Ma il processore dove mette questo numero?, bene, esso possiede al suo interno
delle celle di memoria simili a quelle della memoria stessa, ma più grandi,
infatti si dice di processori a 16 BIT ossia 16 elementi invece di 8 che
possono contenere in binario 1111111111111111 (65535 in decimale, non vi suona
familiare questo numero?) ora queste celle si chiamano registri e vengono
contrassegnati con delle sigle, così abbiamo il registro AX detto anche
accumulatore, il BX, il CX, il DX, il DI e il SI detti anche registri
indice (poi vedremo perché), il F (che ha funzioni particolari), il BP ed il SP, vi sono
inoltre il IP non accessibile dai programmi, CS DS SS ES FS GS che
contengono i numeri relativi a blocchi di memoria.
Quando caricate in memoria un programma e lo mandate in esecuzione il registro
IP punta al primo numero del programma chiamato istruzione ed in base al valore
di questo numero esegue una determinata azione che può essere per esempio INC AX, ossia
incrementa il valore contenuto nel registro AX, quindi se AX conteneva 10 (2 in
decimale) dopo l'operazione conterrà 11 (3 in decimale).
Come vedete sono istruzioni molto semplici ed infatti per eseguire una
operazione un po' elaborata a volte occorrono decine di istruzioni di questo
tipo. Se avete intenzione di scrivere un programma come Write in assembler è
possibile, ma armatevi di una santa pazienza e prevedete di fermarvi davanti
alla vostra tastiera per i prossimi dieci anni. In effetti l'assembler si usa
solitamente per scrivere dei piccoli programmi dove conta molto la velocità di
esecuzione o non è possibile scriverli con linguaggi più evoluti.
Pensate che per scrivere "Hello world" sullo schermo in basic basta
scrivere:
PRINT
"HELLO WORLD"
In C scrivereste:
PRINTF
"HELLO WORLD";
In assembler invece la cosa è molto più laboriosa:
MOV
ES, B800 'metti nel registro di segmento l'indirizzo del segmento video non
grafico
MOV
CX,11 ' metti in CX la lunghezza della stringa di testo
MOV
DX, buffer 'metti in DX l'indirizzo in memoria della frase
MOV
BX,0 'metti in BX l'indirizzo della prima cella del video
Etichetta:
MOV AX,(DX) 'metti in AX il primo carattere puntato da DX (H)
MOV
ES:(BX),AX 'scrivi il carattere nella memoria video puntata da ES e BX
INC
BX 'incrementa
BX
INC
DX
'incrementa DX
DEC
CX 'decrementa
il contatore di caratteri
JNZ
etichetta 'se non è zero CX torna a etichetta e ricomincia da lì
Come
vedete l'assembler è molto più prolisso, ma sappiate che anche le istruzioni
del basic o del C inseriscono nel programma le stesse istruzioni assembler,
magari molte di più perché devono tener conto che non sanno in anticipo la
lunghezza della stringa, che chi scrive in assembler sa già in partenza. Alla
prossima lezione.
Nella lezione precedente abbiamo visto che in assembler le istruzioni sono del
tipo MOV AX,25 ecc. ecc. , cosa vuol dire?, semplicemente che questa istruzione
andrebbe scritta nel file .EXE .COM con i corrispondenti numeri, ossia B8 C0 19 00, capirete
che letti così questi numeri non significano un granchè per chi li legge,
mentre per il processore è tutto chiaro, lui li interpreta in questo modo:
Vogliono che prenda il valore 0019h (25 in decimale) e lo metta nel registro AX
.Nota bene: in memoria viene messo prima il valore basso (19h) e poi il valore
alto 00,Questa è una convenzione legata al progetto originale dei primi
processori, i processori della Motorola, invece, usano il contrario, ma non
preoccupatevi di queste sottigliezze, poiché voi scrivete il numero come lo
scrivereste su un foglio e penserà il compilatore a metterli nel giusto ordine.
Ora c'è un particolare che avrete notato benissimo, il registro AX è a 16 bit e
quindi si aspetta un valore a 16 bit, ma 25 entra benissimo in 8 bit, ma lui è
testardo vuole 16 bit e noi lo accontendiamo portando il valore a 16 bit con
l'aggiunta di uno zero, 0019h, in alternativa avremmo potuto caricare 25 in AL
e lui non avrebbe protestato, ma AH sarebbe rimasto al suo valore originale, e
questo se non ci creava problemi era la cosa più logica, ma se volevo 25 in
tutto AX la soluzione è quella usata nell'esempio.
D'ora in poi, per comodità, per indicare i numeri aggiungerò una 'h' finale se
il numero è decimale, una 'b' finale se il numero è binario e niente se il
numero è decimale.
Visto che abbiamo conosciuto i registri approfondiamo questo importante
argomento; Abbiamo visto che esistono 8 registri (AX,BX,CX,DX,DI,SI,SP,BP). Tralasciamo
per ora i registri SP e BP perché sono usati per speciali funzioni che
descriverò in un'altra lezione, dunque, i 6 rimanenti sono registri a 16 bit e
possono contenere numeri fino a ossia FFFFh, ma se io volessi
operare su numeri a 8 bit?. Si può fare ragazzi. Se po' fà.
I progettisti della INTEL hanno pensato bene di dividere i registri in 2
sottoregistri da 8 bit l'uno, quindi il registro AX è formato dai due
sottoregistri AH e AL, ovviamente AL (L sta per low basso) sono i primi 8 bit
di AX a partire da destra, , mentre AH sono gli altri 8 (H sta per high alto),
e questo vale per tutti i quattro registri base (AX,BX,CX,DX), non esistono per
SI e DI, ma tanto non servirebbero, visto l'uso cui sono destinati questi
ultimi due.
Quindi l'istruzione vista prima MOV AX,25 caricherebbe il valore 25 nel
registro AL ed il valore 0 nel registro AH e l'aspetto complessivo di AX
sarebbe 0019h, ora è anche chiaro che scrivendo i numeri nel formato
esadecimale si riesce chiaramente a vedere i valori presenti nei singoli
sottoregistri.
Chiaramente queste istruzioni scritte nella forma MOV XX,YY vanno poi
passate ad un compilatore, ossia un programma che legge ciò che voi avete
scritto e lo trasforma nella giusta sequenza di numeri leggibili dalla CPU.
I
REGISTRI DI SEGMENTO:
Abbiamo visto nella lezione precedente che la memoria è divisa in blocchi da
64Kbyte ciascuno e che i registri di segmento selezionavano il blocco
desiderato, ma perché esistono 6 registri?. E qui ti volevo, dunque per capire
il perché bisogna capire come è strutturato un programma quando viene caricato
in memoria.
STRUTTURA
DI UN PROGRAMMA IN MEMORIA:
Qualsiasi programma generalmente è formato dal codice, cioè le istruzioni che
voi avete inserito, più i dati necessari, per esempio la stringa 'Hello world'
che nella prima lezione abbiamo stampato sullo schermo, bene, il codice ed i
dati dei file EXE vengono caricati in memoria in due blocchi separati, inoltre
il DOS o Windows che si occupano di caricare in memoria questi programmi
inseriscono nel registro CS il blocco che contiene le istruzioni e.. Bé.
Passate avanti e vedremo il seguito.
Dunque si diceva, inserisce il valore del segmento del codice in CS ed il
valore del segmento dei dati in DS.Semplice no?.
Certo, ma e gli altri?. Bene, il registro SS punta ad un altro blocco che viene
denominato STACK (in inglese CATASTA), a che serve questo stack?!.
Immaginate che il vostro programma ad un certo punto deve andare
momentaneamente 1000 istruzioni più avanti (deve richiamare una funzione o sub
per gli amanti del C), per fare qualcosa ed alla fine deve ritornare dov'era,
come fa a ricordarsi dov'era? Semplice, prende l'indirizzo corrente puntato da CS:IP e lo
memorizza in 4 byte all'indirizzo SS:SP poi salta al nuovo indirizzo, alla fine
preleva da SS:SP l'indirizzo originario e vi ritorna, in pratica questo
salvataggio avviene automaticamente quando eseguite un'istruzione 'CALL indirizzo' e
viene prelevato automaticamente quando eseguite l'istruzione RETF (ritorna).
E se la sub chiamata contenesse a sua volta un'altra chiamata ad un'altra sub?,
niente panico, perché il processore prima di salvare l'indirizzo di ritorno
diminuisce SP di quattro e quindi accatasta tutti gli indirizzi di ritorno uno
sull'altro, ecco perché STACK, ovviamente quando si esegue una RETF avviene
l'inverso e viene scaricato l'ultimo indirizzo salvato. In pratica lo stack
viene considerato LIFO (Last In First Out) l'ultimo ad entrare è il primo ad
uscire.
Volete un esempio?. Eccolo:
Indir. mem. istruzione codici
commenti
1000h MOV AL,10
B0h C0h 0Ah inserisci 10 in AL
1003h CALL FAR sub 9Ah 00h 11h
chiama la routine sub
.....
Sub:
1100h ADD AL,1
04h 01h aggiungi 1 ad AL
1102h RETF
CBh
ritorna al chiamante
Il compilatore inserisce automaticamente l'indirizzo di sub, quindi vi basta
scrivere CALL sub e basta.
I registri ES,FS e GS, invece, non sono vincolati ad alcun segmento e si
possono usare per puntare a qualsiasi altro segmento, per esempio quello video.
I
FILE COM
Veniamo ora ai file .COM che sono anche la scelta più naturale per chi ha
intenzione di scrivere in assembler.
I file COM sono caratterizzati dal fatto che non possono essere più lunghi di
64Kbytes ossia di un segmento di memoria ed inoltre in questo segmento deve
trovar posto anche l'area dati e lo stack, ma niente paura, se riuscite a
scrivere un programma che superi la metà del segmento siete già dei mostri.
Il DOS carica il programma dall'indirizzo 256 in poi e pone nei primi 255 byte
alcune informazioni sull'ambiente operativo tipo le variabili PATH, PROMPT ecc. ,
inoltre dall'indirizzo 129 in poi inserisce la stringa di comando, ovvero il
testo che digitate dopo il nome del programma, es. EDIT LETTERA.TXT, quindi
all'indirizzo 128 trovate il numero 12 (la lunghezza di LETTERA.TXT più lo
spazio fra EDIT e LETTERA.TXT) e da 129 in poi la stringa stessa, un carattere
per ogni byte nel formato ASCII, uno standard mondiale che assegna ad ogni
carattere un numero compreso fra 0 e 255.
Il programma potrà controllare questa stringa e farne ciò che gli pare, nel
caso dell'esempio edit.com aprirà il file per modificarlo o altro'
Ovviamente il programma assembler andrà scritto con un editor di testo,
EDIT.COM è valido, ma potete usare anche notepad o altro, l'importante è che
possa salvare il file in formato testo puro.
Prima di introdurre la lista delle istruzioni che il processore riconosce
bisogna fare alcune importanti precisazioni, innanzitutto dal 386 in poi i
registri principali (AX,BX,CX,DX,DI SI,SP,BP), sono diventati a 32 bit, ovvero
hanno raddoppiato le loro dimensioni, ma restano comunque compatibili con l'uso
a 16 bit, inoltre esistono altre istruzioni che permettono di usare la memoria
in modo diverso, ma ne riparleremo.
Abbiamo visto che il processore conosce solo il sistema binario (0 e 1) per cui
bisogna ragionare con questo sistema per riuscire a capire il risultato di
tutto quello che chiediamo al nostro processore (d'ora in poi chiamerò il
processore CPU cioè Central Processin Unit, unità centrale di processo).
Il sistema binario possiede una sua logica chiamata algebra booleana, che è
simile alla nostra algebra, ma che agisce su solo due cifre invece che le
nostre 10 (0-9).
L'addizione si esegue in modo uguale, per esempio:
1011b +
0110b =
10001b
ovvero 1+1=0 con riporto di 1 non esistendo il 2.
Stesso dicasi per le altre operazioni.
Ma l'algebra booleana possiede altre funzioni più interessanti e non
disponibili con altri sistemi.
Per esempio la funzione AND che dati due numeri esegue il confronto bit per bit
del primo numero col secondo. Per esempio:
1001b
AND
1100b =
1000b
Ovvero solo se entrambi i bit sono a 1 il risultato è 1, negli altri casi è 0.
A che serve?. Calma ci arriviamo subito.
Supponete di avere ottenuto il risultato di un calcolo e che questo sia
10110110b, ora non mi serve il risultato complessivo, ma solo i primi 4 bit,
bene basta fare AND 00001111b ed avrò estratto solo i primi 4 bit.
Un'altra funzione interessante è l'OR che funziona come AND, ma dà risultato 1
se almeno uno dei due bit è a 1.
Un OR più drastico è lo XOR che richiede che uno ed uno solo dei due bit
sia ad 1 per dare 1 altrimenti ritorna 0.
Questo è quasi tutto quello che c'è da sapere sull'algebra booleana, per quello
che ci riquarda. Vogliamo, ora, fare una lista delle istruzioni disponibili?.
Lo temevo che avreste detto di si.
La lista non sarà completa, ma comprenderà solo quelle istruzioni di uso più
frequente e meno difficile, per altre informazioni vi consiglio di procurarvi
un bel tomo della Intel sui suoi processori.
Prima devo spiegare la funzione di un registro che ho nominato precedentemente,
e voi vi state ancora chiedendo a che serve. Giusto?. Questo è il registro F, che è
l'iniziale di FLAG, in inglese bandiera, ebbene il nome è proprio appropriato
perché il registro F si comporta come una bandiera che può essere alzata o
abbassata a seconda delle condizioni, anzi 16 bandiere perché il registro F è a
16 bit, ed ogni bit rappresenta una situazione.
Si va bene, ma quale situazione?. Bé, per rispondere farò ora un piccolo
esempio.
Prendiamo il sequente pezzo di codice:
MOV
CX,2
'carica 2 nel registro CX
DEC
CX
'decrementa il registro CX di 1, CX=CX-1
DEC
CX
' decrementa di nuovo il registro
Cos'è successo dopo il secondo decremento?, che CX è diventato zero, la CPU
giustamente se ne accorge e pone a 1 il bit corrispondente in F che si chiama
bit di zero, quindi se io volessi controllare se un'operazione ha dato zero come
risultato mi basta controllare questo flag, esiste anche il flag di carry che
segnala se in una sottrazione c'è stato prestito, un altro chiamato flag di
overflow che segnala se l'operazione ha ecceduto la capacità del registro, il
flag di segno che segnala se il sedicesimo bit è ad 1, poiché in alcuni casi i
registri vengono considerati a 15 bit e negativi o positivi, e quindi il
sedicesimo bit è zero se positivo e 1 se negativo.
Esiste anche il flag di direzione che indica se i registri SI e DI devono incrementarsi
o decrementarsi in seguito ad alcune istruzioni di lettura scrittura in
memoria.
LISTA DELLE ISTRUZIONI ADD
XX,YY ; Ovvero aggiungi ad XX che può essere un registro il dato YY
che può essere un altro registro od un valore numerico.
A proposito di numeri, se sono compresi tra 0 e 255, ossia la capacità di una
cella di memoria si chiamano BYTE, se sono invece, tra 0 e 65535, ossia 16 bit
si chiamano WORD.
ADC
XX,YY ;Come per ADD, ma aggiunge 1 se il flag di carry è settato ad
1.
AND
XX,YY ; Conoscete già la funzione AND, vero?
CALL
(NEAR/FAR) XX ;Chiama un altro pezzo di programma che può trovarsi sullo
stesso segmento (NEAR) oppure lontano (FAR), XX è l'indirizzo.
CLC ; Clear
carry flag, azzera il flag di carry
CLD ; Azzera il
flag di direzione.
CLI ;Azzera il
flag di interrupt, così la CPU non eseguirà gli interrupt.
Lo so, lo so, vi state chiedendo che diavolo sono questi interrupt. Detto fatto.
Gli interrupt sono delle interruzioni che il sistema invia alla CPU, pensateci
un po' su, il vostro programma sta facendo le sue cose, ma l'orologio del
computer continua a funzionare, oppure il vostro programma non sta leggendo la
tastiera, ma voi premete CTR-ALT-CANC ed il computer si resetta lo stesso, come
mai?. Semplice, alcuni dispositivi come l'orologio, la tastiera, ecc. sono in
grado di mandare dei segnali alla CPU del tipo 'Hey, smettila di fare quello
che stai facendo, e fai un attimo quest'altra cosa, poi puoi riprendere da
dov'eri rimasta.
Pensate ad un computer che gestisce una centrale nucleare, voi lo state usando
per giocare a tetris ed intanto il nocciolo è in fusione e sta mandando a fuoco
il resto del mondo, bene gli allarmi possono mandare un interrupt alla CPU e
questa metterà da parte il vostro tetris mentre spegnerà il nocciolo e salverà
il mondo.
Con l'istruzione CLI dite alla CPU di lasciar perdere gli interrupt e badare
solo al vostro tetris, che vada pure a fuoco tutta la terra.
CMC ;Complementa
il flag di carry, ossia giralo a rovescio.
CMP
XX,YY ;Confronta XX con YY. La CPU sottrae virtualmente YY da XX, la
sottrazione viene solo simulata, ma vengono aggiornati i flag interessati.
CMPSB ;E' una
variante di CMP, in pratica il confronto avviene tra la cella di memoria
puntata da ES:(DI) e quella puntata da DS:(SI), dopo il confronto vengono
incrementati di 1 sia DI che SI.
CMPSW ;Come per
CMPSB, ma il confronto è su word.
DEC
XX
;DECrementa XX che può essere un registro o altro.
DIV
CL ;Dividi
AX per CL, risultato in AL resto in AH.
DIV
CX
; Dividi DX:AX per CX risultato in AX resto in DX
INC
XX
; INCrementa XX, lo sapevate eh?
INT ;Richiama
l'interrupt numero b, comodo per simulare la fusione del nocciolo senza
rischiare il mondo.
IRET ; Fine
dell'interrupt, ritorna pure dove eri.
JA ;Salta
all'indirizzo relativo YY, se il flag di carry è 0 ed il flag di zero è 0.
Salto relativo significa che YY viene considerato positivo o negativo, come si
diceva nella lezione precedente, e sommato o sottratto all'indirizzo corrente.
Il programma riprenderà da qui.
JB ; (Jump
Below) salta se carry=1, il risultato è minore.
JBE ;(Jump Below
Equal) salta se carry=1 e zero=1
JC
YY
;(Jump Carry) salta se carry=1
JCXZ
YY
;(Jump CX Zero) salta se CX=0, ve l'avevo detto che CX è il registro principe
per contare all'indietro.
JE
YY
; (Jump Equal) salta se zero=1
JG
YY
; (Jump Greater) salta se il primo operando è più grande del secondo. Considera
il segno flag S
JGE ; (Jump
Greater Equal) come sopra, anche uquale.
Ovviamente esistono anche le condizioni opposte ossia ,NZ (non zero), Less (minore), NE (not equal),
ecc.
JMP
SHORT ;Salta e basta di b bytes avanti o indietro (+-127)
JMP
NEAR
;Salta e basta di w bytes (+-32737)
JMP
FAR SS:AA ; Salta in un altro segmento (SS) indirizzo AA.
LODSB ; Carica il
byte puntato da DS:(SI) in AL, incrementa SI.
LODSW ;Carica la
word puntata da DS:(SI) in AX, incrementa SI.
LOOP ; Decrementa
CX, se non è zero salta di b bytes.
LOOPE ; Decrementa
CX, salta se cx è maggiore di 0 e flag di zero=1
LOOPNE ; Come
sopra, ma solo se flag di zero=0 e CX è maggiore di 0
MOV
XX,YY ;Copia il valore di XX in YY, alla fine sia XX che YY
conterranno il valore di XX, che può essere un registro, una cella di memoria
od un valore numerico
MUL
b
;Moltiplica AX per b. Risultato in AX
OR
XX,YY ;Ricordate l'operazione OR vero?.
POP
XX
;E questo?. Bé, per spiegarlo farò un piccolo esempio:
Immaginiamo di aver scritto un videogioco e di avere memorizzato in AX la
posizione della nostra navetta spaziale, in CX il numero di vite che ancora
abbiamo, in DX la navetta nemica, insomma abbiamo occupato tutti i registri.
Ad un certo punto del programma abbiamo bisogno di scrivere sullo schermo che
si sta avvicinando un grosso asteroide e ci serve, per farlo, il registro AX,
come si fa?.
Se lo usiamo subito così, perderemo la posizione della nostra navetta e ci
perderemo negli abissi siderali, dunque bisognerebbe appoggiarlo da qualche
parte, usare AX e dopo recuperare il vecchio valore.
Secondo voi, dove potremmo appoggiarlo?, una cella di memoria?, giusto, ma se
volessi qualcosa di più pratico ed immediato?, ma certo!, una catasta, lo STACK,
come ho fatto a non pensarci prima?, è il posto ideale. Bé, alla prossima.
Dunque, eravamo rimasti allo stack, e come si fa' a mettere AX sullo stack?, ma
con PUSH
AX,
naturalmente, e dopo quando vorremo recuperarlo basterà fare POP AX, tenete
presente che se nel frattempo effettuate una CALL od una PUSH di un altro
registro non potete eseguire la POP perché la CALL spinge sullo stack
l'indirizzo corrente prima di saltare al nuovo e, quindi, il vostro valore di
AX passerebbe in seconda posizione, mentre la POP recupera sempre
l'ultimo valore aggiunto allo stack, ovviamente se eseguo PUSH AX e dopo POP BX otterro'
l'effetto di spostare il valore di AX in BX, ma in questo caso era preferibile MOV BX,AX, con lo
stesso risultato.
Comunque l'esempio fatto è un po' forzato perché in genere i valori che vengono
usati dal programma vengono memorizzati in celle di memoria prefissate chiamate
variabili (perché il loro valore può cambiare nel corso di esecuzione del
programma).
A ciascuna cella possiamo assegnare un nome qualsiasi e riferirci ad essa con
questo nome, per esempio la cella che contiene le vite del nostro gioco potremo
chiamarla vite ed ogni volta che ci ammazzano possiamo fare 'DEC vite' che
decrementa le vite e controllare il valore se è arrivato a zero (game over).
Ricordate che tutte le operazioni di lettura e scrittura diretta in memoria
avvengono nel segmento DS a meno che non specifichiate un segmento diverso di
volta in volta.
Notate il nome dei registri di segmento:
CS ; (Code
Segment) segmento del codice.
DS ; (Data
Segment) segmento dei dati.
ES ; (Extra
Segment) a vostra disposizione per altri segmenti.
SS ; (Stack
Segment) segmento dello stack.
Dal 386 in poi esistono anche i segmenti FS e GS simili ad ES.
Volete un esempio sull'uso delle variabili?. Eccolo:
Pippo
DB 10 ;Inizializza una variabile ad un byte (DB),chiamala pippo e
dagli il valore 10.
Beppe
DW 10000 ;Inizializza una variabile a due byte (DW) chiamala beppe e
mettici 10000.
Ugo
DB 'Ciao mondo' ;Inizializza una serie di byte (10) chiamali ugo e ponici i
valori ASCII della frase''.
Successivamente, se il programma ha necessità di aumentare pippo di 1 per
portarlo a 11, farà:
MOV
AL, pippo ; Carica in AL il contenuto di pippo (ricordate che i valori
byte ad 8 bit vanno caricati nei registri ad 8 bit, vero?
INC
AL
;incrementa AL AL=AL+1 (11)
MOV
pippo,AL ;poni in pippo AL, ora pippo=11
Proseguiamo con la lista?. Ok, stavo solo chiedendo.
RCL
XX
; (Rotate with carry left) ruota a sinistra con riporto. Chiaro no?. Come no?.
OK, mettiamo che XX sia il registro AL ad 8 bit. Dunque AL=10110001b, ruotare a
sinistra significa, prendi il primo bit a destra (1) e spostalo al secondo
posto, prendi il secondo e spostalo al 3° e così via, alla fine prendi l'ottavo
e spostalo nel flag di carry, poi prendi il vecchio valore del flag di carry e
mettilo al primo bit di Al. Ovviamente gli spostamenti avvengono sempre sui
valori originali dei singoli bit.
RCR
XX
;Come sopra, ma verso destra. Stavolta il carry entra all'ottavo posto.
RET ;Ritorna al
programma chiamante dopo una CALL.
RETF ;Come sopra,
ma preleva dallo stack anche il valore del segmento di ritorno, si usa dopo una
CALL FAR.
ROL ; Come RCL,
ma l'ottavo bit rientra direttamente al primo posto senza coinvolgere il carry.
ROR ; Come
sopra, ma verso destra.
STC ; Poni a 1
il flag di carry.
STD ; Poni ad 1
il flag di direzione, SI e DI verranno incrementati dopo istruzioni tipo LODSB
o STOSB.
STI ; Poni ad 1
il flag degli interrupt. Interrupt abilitati.
TEST
XX,YY ; testa i valori XX e YY ed aggiorna i flag. In pratica avviene
un AND virtuale tra XX e YY.
Ovviamente ci sono tante altre istruzioni che ho tralasciato perché un po' più
complicate o non utili per i nostri scopi.
Infatti, se ricordate, abbiamo detto che l'assembler è utile più che altro per
quelle piccole utility di poco conto che non chiedono grandi risorse, ma almeno
un po' di velocità, oppure questo corso può servire a capire qualcosa quando
leggerete un listato assembler.
A proposito di utility, che ne dite di un esempio di programmino in assembler
che può anche tornare utile in qualche modo?.
Piano con quelle mani, ho capito, dunque partiamo:
Vi piacerebbe un programma di pochi bytes che vi da' la data del giorno di
Pasqua per l'anno che voi desiderate?. Ci proviamo?. OK, let's go, dicono a New
York, o era Abbiategrasso?. Bo!!
Org
0100
;Istruzione per il compilatore è un .COM
Jmp
start ; salta a start
Ag
DB ?
;variabile ag con valore indefinito tipo byte
Bg
DB ? Cg DB ? Hg DB ? Dg DB ? Fg DB ? Kg DB ? Eg DB ? Gg DB ? Mg DB ? Anno
DW ?
;variabile di tipo word
Nocom
DB
'Inserire un anno per calcolare la data di Pasqua$'
Noanno
DB
'Inserire un anno a 4 cifre (es. 1998)!$'
Messaggio
DB
'Il giorno di Pasqua cade il: '
Giorno
DB '00/' Mese
DB '00$' Start: mov
al,[080]; ;all'indirizzo 80 la lunghezza del comando.
cmp al,5 ; se è diverso da 5, non
abbiamo inserito la data
jz ok mov dx, offset Noanno ;punta alla stringa di errore
mov ah, 9 ;per il DOS 9 in ah significa stampa
int 33 ;chiama il DOS
mov ax, 04c00 ;per il DOS 04c in ah significa
esci dal progr.
int 33 ;chiama il DOS
Il resto del listato alla pagina successiva
Riprendiamo con il nostro programmino
Ok: ;questa è
l'etichetta a cui saltare
xor bx, bx ;azzera bx
xor ah, ah ;ed anche ah
mov cx, 1000 mov al, [082] ;in al la cifra dei millenni (1 nel nostro
caso)
sub al, 48 ;convertila nell'intervallo 0-9
mul cx ;moltiplica per cx
mov bx, ax ;e spostala in bx
xor ah, ah ;azzera di nuovo ah
mov al, [083] ;leggi le centinaia
sub al, 48 ;convertila
mov cx, 100 ;moltiplicatore in cx
mul cx ;esegui la moltiplicazione
add bx, ax ; ed aggiungila alle migliaia
xor ah, ah ; ripeti tutto per le decine
mov cx, 10 mov al, [084] sub al, 48 mul cx add bx, ax mov al, [085] sub al, 48 xor ah, ah add ax, bx ;il risultato finale in ax
mov anno, ax ;salvalo in anno
;ora eseguiamo il calcolo vero e proprio
mov dx, 0 mov cx, 19 div cx ;anno /19 quoziente in ax e resto in dx
mov ag, dl mov dx, 0 mov ax, anno mov cx, 4 div cx ;anno/4 come sopra
mov bg, dl mov ax, anno mov dx, 0 mov cx, 7 ;anno/7
div cx mov cg, dl mov al, ag ;hg=19*ag+24
mov ah, 0 mov cl, 19 mul cl add ax, 24 mov hg, al mov dx, 0 ;dg=hg mod 30
mov cx, 30 div cx mov dg, dl mov al, bg ;kg=5+2*bg+4*cg+6*dg
add al, al add al, 5 mov bl, al mov al, cg mov ah,0
Continua...
Riprendiamo con il nostro programmino
mov cl, 4
mul cl
add bl, al
mov al, dg
mov cl, 6
mul cl
add bl, al
mov kg, bl
mov al, bl ;eg=kg mod 7
mov ah, 0
mov cl, 7
mul cl
mov eg, ah
mov bl, dg ;mg=dg+eg
add ah, bl
mov mg, ah
cmp ah, 9 ;se mg>9 allora vai a L1
ja L1
mov al, 22
mov ah, mg
sub al, ah
mov giorno, al
mov mese, 3 ;marzo
jmp fine
L1:
mov al, mg
sub al, 9
mov fg, al
cmp al, 26
jne L2
mov giorno, 19
move mese, 4 ;aprile
jmp fine
L2:
mov al, fg
cmp al, 25
jne L5
mov ah, dg
cmp ah, 28
je L3
mov giorno, al
mov mese, 4
jmp fine
L3:
mov giorno, 18
mov mese, 4
jmp fine
L5:
mov al, fg
mov giorno, al
mov mese, 4
Fine:
mov bx, offset giorno ;trasforma i numeri in caratteri
mov ah, 0
mov al, gg
mov dx, 0
mov cx, 10
div cx
add dl, 48
add al, 48
Continua...
Riprendiamo con il nostro programmino
mov [bx], al
inc bx
mov [bx], dl
mov bx offset mese
mov al, mese
mov ah, 0
mov dx, 0
mov cx, 0
div cx
add dl, 48
add al, 48
mov [bx], al
inc bx
mov [bx], dl
mov dx, offset messaggio
mov ah, 9
Int 33
Exit:
mov ax, 04c00
int 33
Fine del listato.- come potete vedere in assembler è milto più complicato e
lungo scrivere dei programmi, anche piccoli, per questo ho detto subito che
serve per piccole routine dove occorre il controllo sulle operazioni da fare
oppure una grande velocità, he si perché in assembler l'esecuzione del
programma è velocissima, oserei dire che questo programmino viene eseguito
quasi instantaneamente (BOOOOM).
Vi sarete chiesti perché c'è un carattere '$' alla fine delle stringhe da
stampare, ebbene è un indicatore che indica al DOS la fine della stringa,
infatti non viene neanche stampato.
Bene dopo tutto questo tormentone, vi sembrerà di avere in mano il vostro
processore, ma ecco che arriva la randellata:
Ebbene il sistema di programmazione che abbiamo considerato si chiama 'Modo
reale', in che senso?, nel senso che opera in modo aperto verso il sistema, è
l'unico programma in esecuzione sul vostro computer, ed inoltre può fare della
memoria quello che gli pare. Non vi sembra una cosa potenzialmente pericolosa?,
pensate con un programmino del genere potrei cancellare il DOS e costringere
l'utente a resettare il computer, In fondo i primi virus ci sguazzavano in
queste cose.
Ebbene la Intel, dal 80386 in poi ha costruito una nuova serie di processori
che oltre a possedere delle nuove istruzioni più potenti consentivano al
sistema operativo (leggi Windows 95 o altro) di operare in modalità protetta.
Cioè?. Be la modalità protetta è una cosa alquanto complicata, essa prevede che
solo il sistema operativo può effettuare determinate operazioni, inoltre i
registri di segmento non possono più indirizzare direttamente i segmenti, ma
solo indicare il segmento desiderato al processore, sarà questi poi a
verificare se il programma che ha chiesto l'accesso alla memoria è autorizzato
a farlo o no, se avete windows 95 sicuramente vi sarà capitato con qualche
programma di ricevere il seguente messaggio "Questo programma ha eseguito
un'operazione non valida e sarà terminato", ebbene quel programma ha
tentato di accedere ad un segmento di memoria su cui non era stato autorizzato
da Windows.
Questo fatto comporta molte cose, che più programmi possono essere
contemporaneamente in esecuzione, ognuno nel suo spazio assegnatogli, che non
potrà interferire con altre applicazioni né tantomeno cancellare Windows.
Ed il nostro programmino?: Bè, Windows gli assegnerà una finestra in modo
virtuale, ossia una parvenza di reale, e lui non si accorgerà di niente e farà
il suo dovere. A presto!!!
P.S. Per compilare i programmi assembler avete bisogno ovviamente di un
compilatore. Con una ricerca sul web potrete trovarne decine in formato
shareware o freeware, oppure se disponete di visual C++ il compilatore è già
compreso nel pacchetto. Buon divertimento.
Privacy |
Articolo informazione
Commentare questo articolo:Non sei registratoDevi essere registrato per commentare ISCRIVITI |
Copiare il codice nella pagina web del tuo sito. |
Copyright InfTub.com 2025