|
Lezione 1
In questo corso
presumo che abbiate già delle conoscenze almeno a livello nozionistico della
programmazione di un computer, ad ogni modo non sarà una grande mancanza, perché
mi servirà solo per dei semplici esempi. 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 eviene 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ù leggibile 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.
Lezione 2
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.
Lezione 3
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 65535 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.
Lezione 4
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.
Lezione 5
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.
Lezione 6
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?
Lezione 7
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.
Lezione 8
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.
Lezione 9
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
Lezione 10
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...
Lezione 11
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...
Lezione 12
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!!!
|