Sistemi e Reti, manuale
April 3, 2017 | Author: thebourney | Category: N/A
Short Description
Manuale per Sistemi e Reti...
Description
S3Abacus – Architetture/Asm
SISTEMI E INFORMAZIONE..............................3 Definizioni...............................................................................................3 Classificazione.......................................................................................4
AUTOMI A STATI FINITI.............................................................5 Reversibilità...........................................................................................................5 Raggiungibilità.......................................................................................................5 Indistinguibilità.......................................................................................................5 Stati d'equilibrio.....................................................................................................5 Uscite d'equilibrio..................................................................................................5 Minimizzazione......................................................................................................6
RAPPRESENTAZIONE E CODIFICA DELL’INFORMAZIONE...................10 Notazioni e rappresentazioni posizionali.............................................10 Sistema di numerazione in base Due (BIN).......................................................11 Sistema di numerazione in base Otto (OCT).....................................................12 Sistema di numerazione in base Sedici (HEX)..................................................12
Trasformazioni tra sistemi Posizionali.................................................12 Raggruppamento di Bit.......................................................................................14
Contenitori binari..................................................................................15 Operazioni Bitwise..............................................................................................15 Maschere di bit....................................................................................................16
Numeri con segno................................................................................16 Numeri con parte frazionaria................................................................17 Rappresentazioni in eccesso 2n-1.....................................................................17 Standard IEEE 754.............................................................................................18
CODICI, COMUNICAZIONE, INFORMAZIONE..................................20 Codifiche numeriche............................................................................21 Codifica Numerica BCD......................................................................................22 Codifica Numerica Eccesso 3.............................................................................22 Codifica Numerica Aiken....................................................................................22 Codifica Numerica Gray......................................................................................22
Codifiche Alfanumeriche......................................................................22 Codifica Alfanumerica BAUDOT.........................................................................22 Codifica Alfanumerica ASCII..............................................................................22
CODICI ED ERRORI................................................................24 Parità...................................................................................................................24 Parità incrociata..................................................................................................24 Codice di Hamming.............................................................................................25 Codici Ciclici - CRC.............................................................................................26 CheckSum...........................................................................................................27
ARCHITETTURE ..............................................28 ARCHITETTURA DI VON NEUMANN............................................29 Tassonomia di Flynn...........................................................................................29
Macchina di Von Neumann..................................................................29 Memoria...............................................................................................29 Tecnologie...........................................................................................................30
Bus.......................................................................................................30 Tecnologie...........................................................................................................31
Input/Output.........................................................................................31 Tecnologie...........................................................................................................32
Processore...........................................................................................32 Cisc, Risc, Crisc..................................................................................................33 Cache..................................................................................................................33 Prefetch, Pipeline, Superscalarità......................................................................33 Esecuzione Predicativa e Speculativa...............................................................34 Esecuzione Fuori ordine e VLIW........................................................................34
ARCHITETTURE INTEL............................................................35 x-86......................................................................................................35 Intel 8086.............................................................................................................35 Registri................................................................................................................35 Indirizzamento.....................................................................................................36
IA-32.....................................................................................................36 Netburst, EM64T.................................................................................................37 MultiCore ............................................................................................................37
IA-64.....................................................................................................37 Tabella riassuntiva architetture Intel (2008).........................................38
ASSEMBLY X-86..............................................39 Istruzioni x-86.......................................................................................39 Registro Flags (PSW).........................................................................................41 Sintassi e indirizzamenti.....................................................................................42
x-86 e MsDos.......................................................................................42 Memoria..............................................................................................................43 Boot.....................................................................................................................43 Formato degli eseguibili e Rilocazione: EXE e COM.........................................43 API , Interruzioni Sw e Servizi............................................................................44
Video e tastiera con le Interruzioni Sw del Bios...................................45 Video e tastiera con le Interruzioni Sw di MsDos................................46 Terminare i programmi in MsDos.........................................................46
ASSEMBLY CON DEBUG.........................................................47 Comandi...............................................................................................47 Esplorare la memoria...........................................................................47 Consultare I registri..............................................................................48 Scrivere un programma........................................................................48
.doc - 1684Kb - 08/mar/2011 - 1 / 82
.doc - 1684Kb - 08/mar/2011 - 2 / 82
S3Abacus – Architetture/Asm
Il programma più corto del mondo......................................................................49 Video e tastiera...................................................................................................50
Strutture di controllo.............................................................................50 Editare un programma.........................................................................52 Area Dati e area Codice.......................................................................53 Allineamento........................................................................................54
ASSEMBLY CON TASM...........................................................57 Struttura dei programmi.......................................................................57 Ciclo di vita di un programma..............................................................57 Ciclo di vita di un programma TASM...................................................58 Scrivere un programma EXE...............................................................60 Modelli di memoria...............................................................................61 Scrivere un programma COM..............................................................62
ASSEMBLY X-86 AVANZATO.........................64 MACRO.............................................................................65 Macro costanti.....................................................................................................65 Macro di codice...................................................................................................66 Macro con parametri e etichette.........................................................................67
STACK..............................................................................68 PROCEDURE.......................................................................71 Definizione di procedura ....................................................................................71 Meccanismo di chiamata....................................................................................72 Preservare i registri.............................................................................................72
PASSAGGIO DI PARAMETRI......................................................74 VARIABILI LOCALI.................................................................76 Notazioni per il passaggio di parametri e le variabili locali................................77
DIRETTIVE PER LA PROGRAMMAZIONE
E
LIBRERIE.........................79
Salti lunghi, direttiva JUMPS..............................................................................79 Duplicazione di etichette, direttiva LOCALS......................................................79 Librerie, direttive INCLUDE, PUBLIC ed EXTRN..............................................80 Makefile...............................................................................................................82
.doc - 1684Kb - 08/mar/2011 - 3 / 82
S3Abacus – Architetture/Asm
SISTEMI E INFORMAZIONE
DEFINIZIONI Per affrontare la teoria dei Sistemi sono necessarie alcune nozioni matematiche: - riduzione di espressioni di n-mo grado - uso di matrici e calcolo della priorità - calcolo derivate e integrali - risoluzione equazioni differenziali In generale un S i s t e m a è una entità che riceve sollecitazioni e fornisce, come reazione ad esse, delle nuove condizioni. In termini più formali si dicono i n g r e s s i le sollecitazioni e u s c i t e le reazioni agli ingressi. Gli elementi tipici per l' individuazione di un sistema sono: - T un insieme ordinato e crescente di tempi; -I insieme di n canali di ingresso; - VI insieme dei valori per gli ingressi; - U insieme di m uscite; - VU insieme dei valori per le uscite; - S insieme degli stati interni. Servono poi due funzioni che possano descrivere l' andamento del sistema e, da questo, fornire l'andamento delle uscite: s(t) = f(t0,t,s(t0),in(t)[t0,...,t[)
funzione di Tr a n s i z i o n e :
La prima è detta
funzione di Tr a s f o r m a z i o n e :
La seconda è detta
u (t) = g (t,s(t),i (t)) m
m
n
La situazione può essere rappresentata da un diagramma funzionale: i (t)
s(t)
1
i 2(t)
u1 (t)
f( ) m
g()
in(t)
u2 (t)
um(t)
s(t0)
L'insieme T è ordinato e rappresenta tutti i possibili istanti in cui deve essere analizzato il sistema. Esso può essere finito o infinito ma sicuramente è limitato inferiormente da un valore detto t0 (tempo iniziale). L'insieme I descrive tutte le possibili sollecitazioni autonome a cui il sistema è soggetto; in altre parole potrebbe essere visto come l'insieme delle variabili d'ingresso. E' opportuno, per ogni elemento di I (variabile) specificarne il campo di validità o l'insieme dei valori. L'insieme VI è composto da tutti i valori che possono assumere contemporaneamente gli elementi di I, quindi se la cardinalità di I è n, l'insieme VI è rappresentato - in teoria - da tutte le possibili n-ple che si possono comporre. Analoghi discorsi si applicano a U e VU, mentre per S si intende l'insieme di tutte le configurazioni interne che il sistema può assumere durante l'analisi. Risulta particolarmente intuitivo che gli ingressi, gli stati e le uscite debbano dipendere dal tempo t, cioè: i=i(t), s=s(t), u=u(t). In particolare risulta intuitivo che saputi gli ingressi al tempo iniziale t0, saputo lo stato iniziale al tempo t0 e sapute le relazioni tra ingressi e stati per il calcolo delle uscite, si possa conoscere senza dubbio sia l'uscita successiva (al tempo t1) che lo stato successivo (al tempo t1); inoltre applicando tali deduzioni, si può estendere il principio ad un qualsiasi tempo t diverso da t0. E' per questo motivo che le due relazioni funzionali f e g assumono la forma vista poco sopra.
S3Abacus – Architetture/Asm
.doc - 1684Kb - 08/mar/2011 - 4 / 82
E' da notare come t0 non sia espressamente solo il tempo iniziale dell'analisi, ma un qualsiasi tempo t per il quale siano noti i(t) e s(t). Anche la notazione in(t), um(t) e gm(..) sono da intendersi come n-ple di valori di ingresso, m-ple di valori di uscita e m-ple di funzioni di trasformazione, avendo generalizzato la numerosità di ingressi e uscite. Infatti se da n ingressi diversi il sistema offre m uscite diverse, la funzione di traformazione in realtà deve descrivere tutti gli m modi in cui gli n ingressi si trasformano nelle m uscite. Pertanto questi m modi sono le gm funzioni di trasformazione diverse. Le due funzioni fondamentali e sono da interpretare in modo consequenziale l’una dall’altra e non contemporaneo, anche se la stessa dipendenza da t di s(t) e di um(t) potrebbe far pensare diversamente. Infatti la loro valutazione contemporanea induce ad uno sfasamento nell’analisi: la valutazione delle uscite attuali (al tempo t) dipende strettamente dallo stato attuale (al tempo t) che pertanto deve essere già conosciuto. Anche il diagramma funzionale riportato contiene questa inesattezza, infatti le n entrate dell’ingresso in f() non sono le stesse che entrano poi in gm(), ma dovrebbero essere gli ingressi al tempo t precedente. La corretta relazione tra le due funzioni si ottiene integrandole matematicamente: um(t) = gm(t, f(t0,t,s(t0),in(t)[t0,...,t[),in(t)) Solo in base a questa considerazione l’interpretazione delle due funzioni diviene coerente: - lo stato che desidero conoscere, cioè lo stato al tempo attuale t e quindi s(t) è calcolabile con la funzione f solo se è possibile conoscere lo stato iniziale s(t0) e l’intera evoluzione degli stati per tutti i tempi successivi fino a t escluso; si tratterebbe quindi di determinare tutti i vari s(t1), s(t2), ...; ma ciò è possibile solo se per ogni istante t0, t1, ..., conosco gli ingressi applicati, cioè tutti gli ingressi applicati da t0 fino a t escluso. Pertanto la scrittura in(t)[0..t[ significa: tutti i valori degli n ingressi valutati a partire da t0 fino a t escluso. Allora la formula descrive esattamente questo appena affermato. - l’uscita al tempo attuale t non è altro che una m-pla di valori, e quindi la si scrive: um(t); per poter calcolare ogni singolo valore della m-pla il ragionamento è sempre identico: è necessario sapere quale è lo stato al tempo t, cioè s(t) e quale è l’ingresso al tempo t, cioè l’n-pla specifica: in(t). Ma questo è ciò che rappresenta formalmente la formula . In realtà la potrebbe essere riscritta: sarebbe infatti sufficiente conoscere lo stato immediatamente precedente e l’ingresso (n-pla) immediatamente precedente per conoscere lo stato attuale s(t); in altre parole la funzione di transizione potrebbe anche esprimersi: s(t) = f(t0,t,s(tprec),in(tprecl)) ’ Cionondimeno si preferisce la per evidenti scopi didattici, dato che costringe a ripensare alla storia passata del sistema. Le presenze di t e sono esplicitate nelle due funzioni per mettere in evidenza la dipendenza dalla variabile indipendente t connaturata alla dinamicità dei sistemi e la dipendenza assoluta dall’istante iniziale di analisi - e quindi dallo stato iniziale del sistema - che così fortemente condiziona l’evoluzione di ogni sistema.
CLASSIFICAZIONE I sistemi in generale possono essere suddivisi in categorie in base alla loro natura; si possono quindi distinguere tipi contrapposti di sistemi: - Va r i a n t i :
sono tali quei sistemi per cui, al trascorrere del tempo, uno o più parti costitutive del sistema si evolvono e quindi ne modificano la struttura d'analisi. Se ad esempio in un sistema 'missile' la massa del missile viene considerata costante durante l'analisi per esempio del calcolo della velocità e delle accelerazioni, che possono essere viste come funzioni di trasformazione o transizione, tale assunzione risulta evidentemente falsa dato che la massa del missile diminuisce al consumo di carburante. In altre parole un parametro costitutivo del sistema dipende esplicitamente dal tempo e varia con esso. - Invarianti: in contrapposizione, se le caratteristiche intrinseche del sistema rimangono costanti nel tempo e variano solo ingressi, stati e uscite. - Deterministici: sono tali quei sistemi in cui le grandezze d'analisi sono precisamente e univocamente determinate da relazioni matematiche, grafiche o descrittive. - Stocastici: in contrapposizione, quei sistemi per cui le grandezze caratteristiche dipendono da dati e analisi aleatorie e non precisamente determinabili a priori (si pensi ai sistemi complessi delle economie, delle previsioni meteorologiche o dei modelli atomici). - Continui: sono tali quei sistemi per cui le caratteristiche fondamentali dipendono da quantità analogiche continue riferibili a numeri Reali o Complessi. - Discreti: in contrapposizione, quei sistemi governati da quantità finite ed enumerabili - in genere riferibili al dominio dei numeri Naturali o Interi. Sono detti spesso anche sistemi digitali. - Propri: se la funzione di trasformazione non dipende dai valori assunti dall' ingresso mentre si dicono I m p r o p r i se la funzione di trasformazione dipende esplicitamente dai valori degli ingressi, così come espresso nella definizione generale . Per i sistemi propri quindi le due funzioni possono essere riscritte: s(t) = f(t0,t,s(t0),in(t)[t0,...,t[) ' um(t) = gm(t,s(t)) '
.doc - 1684Kb - 08/mar/2011 - 5 / 82
S3Abacus – Architetture/Asm
AUTOMI A STATI FINITI Un sottoinsieme dei sistemi generali è rappresentato dagli A u t o m i , che possono essere definiti come Sistemi invarianti, deterministici, discreti in cui VI e VU sono insiemi finiti. Tra questi sono particolarmente significativi gli A u t o m i A S t a t i F i n i t i (AASF), cioè automi in cui anche l'insieme S è finito. Di questi ultimi ci occuperemo in particolar modo mostrando metodi di analisi e metodi per l'individuazione delle funzioni di transizione e trasformazione. Dato che per i sistemi discreti - come gli automi - l'avanzamento del tempo è implicito, si possono scrivere le funzioni tipiche omettendo la variabile indipendente t e riportando le due forme, impropria e propria: IMPROPRIO
PROPRIO
s(t) = f(s(t0),in(t)[t0,...,t[)
s(t) = f(s(t0),in(t)[t0,...,t[)
'
um(t) = gm(s(t),in(t))
um(t) = gm(s(t))
'
Per gli automi a stati finiti, la versione Impropria viene detta Automa di MEALY, mentre la versione Propria è detta Automa di MOORE. L'analisi e la determinazione delle due funzioni g e f per un automa a stati finiti si realizza con la costruzione di un grafo particolare detto g r a f o d e l l e t r a n s i z i o n i , sempre identificabile. Lo schema costruttivo del grafo di transizione per un AASF ove gli insiemi tipici sono univocamente determinati si sintetizza nei seguenti passi: 1. Disposizione in aree circolari di tutti gli stati appartenenti a S; 2. A partire da uno stato qualsiasi - magari s0 - applicare tutti gli ingressi possibili (gli elementi di VI) e per ognuno disegnare un arco orientato verso lo stato raggiunto, riportando l'ennupla d'ingresso e l'emmupla dell'uscita relativa con la notazione i1,i2,..in/u1,u2,..,um. 3. Naturalmente una regola fondamentale, che al termine della costruzione deve essere assolutamente rispettata, consiste nella completa mancanza di due coppie identiche di n-ple d'ingresso che, da uno stesso stato, conducono a due stati differenti ovvero che originano due dfferenti m-ple di uscite. Un altro sistema per descrivere il funzionamento di un AASF (determinazione di f e g) è ben descritto dalla t a b e l l a d i f u n z i o n a m e n t o , che consiste in una tabella cartesiana ove sulle ascisse sono riportati tutti i possibili ingressi (elementi di VI), sulle ordinate tutti i possibili stati (elementi di S) e sugli incroci, ordinatamente, uscita e stato successivo determinati dai valori d'incrocio. Tale metodo risulta particolarmente pesante quando VI è ampio ma sufficientemente efficace per automi in cui VI è limitato. Sia la tabella di funzionamento che il grafo di transizione riassumono esplicitamente per gli AASF le funzioni f e g tipiche per la completa determinazione di un sistema. Esse in alcuni casi possono anche essere scritte in notazioni matematiche, anche se ciò non risulta così efficace come i metodi suddetti. Proseguendo nell'analisi degli AASF è particolarmente interessante introdurre alcune nozioni relative a stati e uscite, nozioni che in una qualche misura si possono generalizzare anche per i sistemi continui.
REVERSIBILITÀ Un automa si dice Reversibile se, dato un qualsiasi stato sa Î S e un qualsiasi valore i Î VI, esiste e unico uno stato sb Î S tale per cui è vera sa = f(sb, i) Per verificare questa proprietà su un automa dato è sufficiente verificare che invertendo tutti i versi degli archi del grafo di transizione si ottenga ancora un automa (cioè non si infranga la regola 3.)
RAGGIUNGIBILITÀ Uno stato s2 è Raggiungibile dallo stato s1 se esiste un a sequenza di ingressi che applicati a s1 consentono di arrivare a s2. Se la proprietà è estensibile a qualsiasi coppia di stati di un automa esso si dice Connesso, altrimenti Non Connesso.
INDISTINGUIBILITÀ Due stati s1 e s2 sono Indistinguibili se tutte le coppie ingresso/uscita applicabili a s1 sono le stesse rilevabili su s2.
STATI D'EQUILIBRIO Uno stato s è d'Equilibrio per l'automa se esiste una sequenza di ingressi tali da mantenere l'automa sempre nello stato s.
USCITE D'EQUILIBRIO Una uscita u di un automa è d'Equilibrio se esiste uno stato s e una sequenza di ingressi tale per cui l'uscita dell'automa risulti essere sempre u. Naturalmente le definizioni di cui sopra implicano situazioni di immediata intuizione che per ora non espliciteremo, attendendo che possano essere messe in evidenza dagli esempi riportati. La proprietà dell'Indistinguibilità invece torna immediatamente utile per introdurre un metodo che semplifichi la costruzione di un grafo delle transizioni per un automa qualsiasi. Come ci si renderà ben presto conto, la costruzione dei grafi di transizione, pur semplice, spesso conduce a diagrammi molto ricchi di stati e archi, ingenerando confusione e dilungando sensibilmente l'analisi. Dopo aver costruito un grafo complesso e ricco di stati si rende necessario pensare se il modello ottenuto sia il più semplice possibile e spesso la risposta è negativa. Esiste infatti un metodo di analisi dei grafi che consente di semplificarli senza perderne le caratteristiche. Tale metodo è detto di m i n i m i z z a z i o n e e si basa sulla eliminazione metodica degli stati equivalenti (ovvero indistinguibili) eventualmente presenti in un modello.
.doc - 1684Kb - 08/mar/2011 - 6 / 82
S3Abacus – Architetture/Asm
MINIMIZZAZIONE Dopo aver disposto completamente il grafo di transizione iniziale e quindi aver esplicitamente descritto tutti gli stati (es. s0,..,sn) e applicato tutti i VI ad ogni stato avendole associate alle proprie VU, seguire i seguenti passi: 1. Indicare con P0 l'insieme di tutti gli stati (prima partizione P1); 2. Individuare tutti i gruppi di stati nei quali applicando i VI si ottengono le stesse VU; per ipotesi immaginiamo che si formino i gruppi di stati nominati G1, G2, ..., Gi, ognuno dei quali contiene stati che a partire dai medesimi ingressi offrono medesime uscite (stati indistinguibili). 3. Questi i gruppi di stati rappresentano la seconda partizione P2; 4. Per ogni stato individuare tutti i possibili stati di arrivo (applicando gli ingressi ad ogni stato) e formare una tabella che ad ogni stato associ l'identificazione del gruppo di P2 - cioè ai Gj - a cui si è arrivati. 5. Identificare quindi i nuovi gruppi della partizione P3 raggruppando tutti gli stati che giungono alle stesse sequenze di Gj descritte al punto precedente. Questa sarà la nuova partizione P3. 6. Ripetere i passi 3. e 4. fino a ottenere partizioni identiche notando l'impossibilità di proseguire. 7. A questo punto supponiamo che gli elementi dell'ultima partizione siano k, con k £ i £ n e chiamiamoli Q1,Q2,..,Qk (analoghi ai G1,G2,...,Gi visti in precedenza); essi determinano k stati generali dei quali, per ogni ingresso, si può determinare sia l'uscita (osservando la partizione P1) sia lo stato generale a cui passare (osservando l'ultima partizione ottenuta). Questo nuovo automa è perfettamente equivalente a quello dato.
ESEMPI
DI
AUTOMI
Esempio 1. Analizziamo in questo caso solo sistemi discreti, cioè automi a stati finiti cominciando da un sistema costituito da un distributore di lattine. Il sistema contiene n lattine, possiede un ingresso per inserire una moneta, una uscita per la lattina e una per la restituzione della moneta. Gli insiemi tipici risultano essere: T I VI U VU S
= = = = = =
{t0,t1,...,tn} {M}; dove M stà per moneta e può assumere i valori 1 e 0 (moneta IN, moneta OFF) {1,0}; naturalmente sono singoletti, dato che il canale è unico; {L,R}; con L si indica l'uscita lattina (0 o 1) e con R la restituzione (0 o 1); {,,}; manca il caso in cui è resa sia la lattina che la moneta; {s0,...,sn}; indica il riempimento del sistema (s0=vuoto)
Allora il grafo si costruisce: 1 / 1 /
S0
1 /
Sn-1
1 / 0 /
0 /
Sn 0 /
Come si può notare sono stati riportati tutti gli stati (agli stati intermedi sono stati sostituiti i quadratini di sospensione) e ad ogni stato sono applicati tutti gli ingressi possibili; Le funzioni f e g si possono anche esplicitare in: f = ; si=si-1 se ingresso=1 e i≠ 0 si=si se ingresso=1 o se i=0; g =
; ui= se ingresso=1 e i≠ 0 ui= se ingresso=0 ui= se ingresso=1 e i=0
Esempio 2. Sia dato un ascensore che si muove su 3 piani, con un pulsante per piano per la chiamata e un pulsante interno per indicare la destinazione. Naturalmente è necessario stabilire alcune regole per determinare il comportamento del sistema tipo indicare la priorità dei pulsanti (detti PP e PA) e introdurre altre ipotesi di funzionamento (le uscite possono essere la direzione D (alto o basso o fermo) e la velocità V (0,1,2). Tali ipotesi devono essere poste dall'analizzatore del sistema se non espressamente indicate. Pertanto: T I VI U VU S
= = = = = =
{t0,t1,...,tn} {PP,PA}; PP=0,1,2,3; PA=0,1,2,3 {,,,,,...,} {D,V}; D=0,a,b; V=0,1,2 {,,,,} {P1,P2,P3} 0,2/a,1 1,2/a,1 2,0/a,1 2,2/a,1 3,2/a,1
0,0/0,0 0,1/0,0 1,0/0,0 1,1/0,0 2,1/0,0 3,1/0,0
Nel grafo sono state messe in evidenza ed esplicitate solo le transizioni del primo stato P1, riportando tutti i possibili valori per gli ingressi (coppie), mostrandone le uscite e riportandone le transizioni.
P2
In vari casi si nota come la priorità scelta tra i due ingressi sia più alta per il pulsante nell'ascensore (PA) cioè il secondo valore della coppia (Es. la coppia su P1 ferma l'ascensore su P1 dando l'uscita ).
P1
0,3/a,2 1,3/a,2 2,3/a,2 3,0/a,2 3,3/a,2
P3
.doc - 1684Kb - 08/mar/2011 - 7 / 82
S3Abacus – Architetture/Asm
Si nota anche che nessun ingresso applicato su P1 può generare l'uscita dato che l'ascensore si trova al piano più basso. Naturalmente analoghe analisi con l'applicazione di tutte le coppie di ingresso devono essere eseguite anche per P2 e P3. In questo caso non è particolarmente efficace rappresentare f e g con notazioni matematiche. Esempio 3. Una categoria abbastanza significativa di automi è rappresentata dagli automi riconoscitori. Si debba implementare un automa che acquisisce in ingresso simboli binari e debba emettere un segnale tutte le volte che riconosce una sequenza di ingresso pari alla codifica binaria del simbolo '1' in ASCII (=30h=00110000b). Gli insiemi tipici: T = {t0,t1,...,tn} I = {IB}; che indica Input Binario, con valori 0,1; VI = {1,0}; U = {R}; Riconoscimento, valevole ACK (riconosciuto), NACK o STBY (in riconoscimento) VU = {ACK,STBY,NACK}; S = {S0,R1,R2,R3,R4,R5,R6,R7}; indica le fasi di riconoscimento. 1/NACK
0/STBY
0/STBY
1/NACK 0/ACK
R1
S0
R2 1/STBY 0/NACK
1/NACK
0/NACK
R7 0/STBY
1/NACK
1/NACK
1/NACK
R5
R6
1/STBY
Si noti come l'uscita segnali una fase di riconoscimento in corso con il valore STBY.
R4
0/STBY
1/NACK
R3
La corretta implementazione dell'automa impone alcune correzioni sulle transizioni che riportiamo in seguito.
0/STBY
0/STBY
0/STBY
0/NACK
1/NACK
R1
S0
0/ACK
1/NACK
R2 0/NACK
R7 0/STBY
1/STBY
R3 1/STBY
1/NACK
1/NACK
1/NACK
R5
R6
0/STBY
R4
0/STBY
Il grafo riportato è in grado di riconoscere la sequenza indicata (00110000) solo in alcuni casi; infatti, ipotizzando lo stato iniziale in S0, una sequenza di ingressi del tipo: 11100110000101... viene correttamente analizzata dall'automa che segnala ACK nel momento giusto. Sempre con stato iniziale in S0, una sequenza del tipo: 10001100001011... non viene segnalata, dato che al terzo zero ricevuto l'automa si ritrova in S0, perdendo sincronismo con la sequenza da riconoscere.
Questa seconda versione individua tutte le sequenze richieste senza sbagliare nessun riconoscimento e senza perderne alcuno. Le modifiche consistono nel far ritornare l'automa allo stato corretto dopo ogni NACK, evitando di farlo sempre partire da capo. Si notino le differenze di comportamento sugli stati R2, R3, R7.
.doc - 1684Kb - 08/mar/2011 - 8 / 82
S3Abacus – Architetture/Asm
Esempio 4. Vediamo ora come si può minimizzare un automa con il metodo delle partizioni. Sia dato il seguente automa per mezzo del grafo: 0/0
S6
0/0 0/0
1/0
S7
S0 0/0
0/0
1/0
1/0
S3
1/0 1/1
1/1 1/0
S5
S4
1/0
S1 0/0
0/0
S2
0/0
Evidentemente gli stati sono otto (S0,...,S7) con un solo canale di ingresso che può valere 0 o 1 e analogamente, un solo canale per le uscite che può valere 0 o 1. A priori non si può affermare che l'automa riprodotto non sia riducibile (minimizzabile), ma basta osservare gli stati S0 e S4 per verificare che sono Indistinguibili. Infatti ogni valore dell'ingresso su S0 genera le stesse uscite che gli stessi valori per l'ingresso generano su S4 (0/0; 1/0). L'automa è minimizzabile. Utilizziamo il metodo delle partizioni. Passo 1. P0 = (S0, S1, S2, S3, S4, S5, S6, S7); Passo 2. Per determinare i gruppi Gj della seconda partizione P1, applichiamo ad ogni membro di P0 gli ingressi e valutiamo le uscite: S0= 0-0 1-0 S1= 0-0 1-1 S2= 0-0 1-0 S3= 0-0 1-0 S4= 0-0 1-0 S5= 0-0 1-1 S6= 0-0 1-0 S7= 0-0 1-0 Individuiamo quindi i due gruppi G1=(S0,S2,S3,S4,S6,S7) e G2=(S1, S5) che compongono la partizione P1. Passo 3. Al gruppo G1 di P1 e al gruppo G2 di P1 applichiamo gli ingressi e costruiamo la tabella delle transizioni sui gruppi Gj: Gruppo G1 Gruppo G2 S0, 0-1= G1-G2 S1, 0-1= G1-G1 S2, 0-1= G1-G2 S5, 0-1= G1-G1 S3, 0-1= G1-G1 S4, 0-1= G1-G2 S6, 0-1= G1-G2 S7, 0-1= G1-G1 Passo 4. Individuiamo quindi i sottogruppi Q1=(S0,S2,S4,S6), Q2=(S3,S7) e Q3=(S1,S5) che compongono la P2. Passo 5. Come al passo 3., applichiamo ad ogni sottogruppo gli ingressi: Gruppo Q1 Gruppo Q2 Gruppo Q3 S0, 0-1= Q1-Q3 S3, 0-1= Q2-Q1 S1, 0-1= Q1-Q2 S2, 0-1= Q1-Q3 S7, 0-1= Q2-Q1 S5, 0-1= Q1-Q2 S4, 0-1= Q1-Q3 S6, 0-1= Q1-Q3 Notiamo che non si ottengono altri sottogruppi, quindi il metodo di partizione si interrompe. Passo 6. Si individuano quindi tre stati differenti equivalenti a Q1, Q2, transizioni e le uscite per ogni valore di ingresso, infatti: Q1 e I=0: U=0 (da uno stato qualsiasi di Q1) e transizione in Q1 Q1 e I=1: U=0 (da uno stato qualsiasi di Q1) e transizione in Q3 Q2 e I=0: U=0 (da uno stato qualsiasi di Q2) e transizione in Q2 Q2 e I=1: U=0 (da uno stato qualsiasi di Q2) e transizione in Q1 Q3 e I=0: U=0 (da uno stato qualsiasi di Q3) e transizione in Q1 Q3 e I=1: U=1 (da uno stato qualsiasi di Q3) e transizione in Q2
0/0
Q3 di cui si conoscono le (vedi (vedi (vedi (vedi (vedi (vedi
P2); P2) P2) P2) P2) P2);
Q1 0/0 1/0
Q2 0/0
1/1
Q3
1/0
.doc - 1684Kb - 08/mar/2011 - 9 / 82
S3Abacus – Architetture/Asm
Esempio 5. E' dato un file di testo, formattato tipograficamente, giustificato con spazi a sinistra, numero di pagina, intestazione, e caratteri di controllo. Si deve ottenere un file di testo ASCII, non formattato, con una sola riga al posto dei salti pagina, senza spazi a sinistra, eliminando tutti gli eventuali spazi di giustificazione tra le parole. Si ricorda che un salto riga è descritto dai due caratteri ascii sequenziali CR/LF (13,10); indichiamo con ^ i caratteri di controllo. T I VI U VU S
= = = = = =
{...} {file originale} {0..255} ≡ {[A..Z], [10], [13], [32], [^]} dove [A..Z] indicano gli ASCII [32..125] {file da ottenere} {0, 255, Null} ... A..Z/A..Z
32/Null
32/32
OK A..Z/A..Z
A..Z/A..Z
32/Null 10/Null
^/Null ^/Null
LF/Null
10/Null A..Z/Null SP/Null 13/Null
^/Null
LF 13/Null
10/Nullll
SP
A..Z/A..Z
^
^/Null
Al trascorrere del tempo, e al variare degli ingressi, ogni sistema significativo modifica il suo stato (o i suoi stati). La configurazione che assumono gli stati è di fondamentale importanza per lo studio del sistema. Tale configurazione è detta, genericamente, movimento.
13/Null
13/Null
^/Null
10/Null
CR
Riga 10/Null 32/Null
In Turbo Pascal questo si realizza con un ARRAY di BOOLEAN, un elemento dell'array per ogni stato. Per indicare lo stato attuale si pone a TRUE l' elemento (FALSE in caso contrario); si avranno quindi 6 istruzioni IF per ogni stato che selezioneranno le valutazioni opportune degli ingressi e determineranno uscite e transizioni. Aggiungere uno stato è molto facile: basta aggiungere un blocco di 6 IF.
10/Null 13/Null 32/Null
Come al solito il diagramma a fianco, rappresenta per gli automi a stati finiti l'equivalente della funzione di Trasformazione e, se riportante anche le uscite (come in questo caso) esso sintetizza anche la funzione di Trasferimento. CR/CR
Simulazione per l' automa Tempi t0 t1 t2 t3 t4 t5 t6 ...
(filtro-file): Ingressi a n 32 32 13 10 ... ...
Stati OK OK OK SP SP CR LF ...
La colonna degli stati rappresenta quello che, per
Uscite a n 32 Null Null Null ... ... sistemi continui, chiameremo Movimento.
.doc - 1684Kb - 08/mar/2011 - 10 / 82
S3Abacus – Architetture/Asm
RAPPRESENTAZIONE E CODIFICA DELL’INFORMAZIONE NOTAZIONI
E RAPPRESENTAZIONI POSIZIONALI
Un sistema di numerazione è l’insieme delle convenzioni che si debbono seguire per descrivere ed operare sul concetto primitivo di numero. Il concetto originale è unico ed è stato assiomatizzato da tempo, ma il modo in cui esprimerlo, che potremmo associare ad una lingua non è unico (come uniche non sono le lingue con le quali si può operare sulle cose). Il sistema di numerazione adottato universalmente è il sistema decimale, ovvero in base 10, introdotto verso il 1200 dalla cultura araba; tale sistema è detto p o s i z i o n a l e . Il sistema in uso precedentemente nella nostra cultura era quello romano di tipo ‘additivo’ ed era basato sull'insieme dei simboli: I,V,X,L,C,D,M (es. MCMXCII=1992). Questo sistema, oltre ad essere scarsamente leggibile poichè ogni cifra per essere decodificata costringe sempre ad un calcolo, non permette di utilizzare i sistemi di calcolo veloci per le operazioni sulle cifre, ad esempio quando si volessero applicare le quattro operazioni aritmetiche. I sistemi posizionali al contrario risultano più compressi e veloci oltrechè intuitivi nella decodifica - si pensi solo al fatto che una cifra con un numero maggiore di simboli di un’altra indica direttamente il fatto di esserne maggiore. Essi sono inoltre adatti per effettuare calcoli aritmetici veloci. Le caratteristiche di un qualsiasi sistema di numerazione posizionale sono: - Base, e cioè il numero di simboli utilizzabili per rappresentare una cifra qualsiasi; - Set di simboli, cioè l’insieme dei simboli utilizzabili; - Peso del simbolo, cioè la posizione che ogni simbolo assume all’interno di una qualsiasi cifra adottando una numerazione che parte da zero per il simbolo più a destra nella rappresentazione. Esistono anche sistemi di numerazione misti, cioè che utilizzano il sistema decimale all’interno di classi di numeri suddivise in base ad una seconda base detta secondaria, come il sistema sessaggesimale per la numerazione degli angoli. Spesso risulta più comodo e veloce esprimere i numeri in una base diversa da 10, pertanto è necessario possedere una certa pratica nel manipolare direttamente i numeri in diverse basi. Dato però che il sistema di riferimento è quello a base 10 sarà sempre utile sapere anche trasformare numeri da una base diversa da dieci alla base di riferimento. In sostanza vedremo come operare direttamente su sistemi di numerazione in diverse basi e, sempre, sapere trasformare ogni risultato parziale in base 10 e viceversa. Risulta fin da ora intuitivo che, essendo la logica elettronica ed informatica basata su due soli stati spesso rappresentabili in ultima istanza con due livelli di tensione, un sistema di numerazione particolarmente utile sarà il sistema in base 2 o binario. Per analoghe considerazioni si vedrà come utili saranno anche quei sistemi che utilizzano basi direttamente connesse alla base 2 e cioè sistemi di numerazione la cui base è una potenza di due (sistemi di numerazione in base 4, 8 e 16). Indichiamo con il termine n u m e r o il concetto generale ed intuitivo di numero, a prescindere dalla sua rappresentazione; Indichiamo con c i f r a la rappresentazione in simboli di un numero in un determinato sistema posizionale; Si ricorda che per s i m b o l o si intende uno qualsiasi dei simboli appartenenti al set di simboli di un determinato sistema posizionale. Si può dire quindi che un determinato numero è rappresentato dalla cifra 101 nel sistema di numerazione posizionale in base dieci utilizzando i simboli 1 e 0 della base decimale. Ogni cifra quindi dovrà essere dotata di una specificazione che indichi il sistema di numerazione in cui è espressa, dato che purtroppo gli insiemi di simboli utilizzati dalle diverse basi di numerazione spesso coincidono. Dato che si analizzeranno più frequentremente i sistemi in base dieci, in base due, in base otto, e in base sedici, utilizzeremo rispettivamente le lettere D, B, O, e H (Hex) per indicare i sistemi di numerazione; pertanto la cifra di poco fa si scriverà: (101)D , mentre le cifre (101)B , (101)O , (101)H rappresenteranno altri tre diversi numeri nelle basi due, otto e sedici. La rappresentazione di numeri in un sistema posizionale di base generica N composta da n simboli è sempre traducibile con uno stesso algoritmo; infatti data una cifra qualsiasi composta da una sequenza di k simboli S, e assegnati i pesi ad ogni simbolo si ottiene sempre:
( Sk... S2S1S0 ) N ESEMPIO: RAPPRESENTAZIONE
=
k
∑ Si ( n) (N ) i
N
i=0
DECIMALE
Data la cifra 143 in base 10, verificare la formula . Per la cifra data gli ingredienti di sono: k=2; N=D n=10; quindi: (0)
(1)
(2)
(143)D = 310 ( )D D + 410 ( )D D + 110 ( )D D = (3 * 1 + 4 * 10 + 1 * 100)D = (143)D Data la cifra 123 in base 4, verificare la formula . Per la cifra data gli ingredienti di sono: k=2; N=Q n=4; quindi: (0)Q
(123)Q = 3(10)Q
(1)Q
+ 2(10)Q
(2)Q
+ 1(10)Q
= (3 * 1 + 2 * 10 + 1 * 100)Q = (123)Q
.doc - 1684Kb - 08/mar/2011 - 11 / 82
S3Abacus – Architetture/Asm
Naturalmente la formula è generalizzabile per rappresentazioni di cifre con parte frazionaria, avendo cura di pesare i simboli a partire da -1 per il simbolo immediatamente posto dopo la virgola.
ESEMPIO: RAPPRESENTAZIONE
DECIMALE DI CIFRA CON VIRGOLA
Data la cifra 234,31 in base 10, verificare la . (34312 , )D = 210 ( )(D−3)D + 110 ( )(D−2)D + 310 ( )(D−1)D + 410 ( )(D0)D + 310 ( )(D1)D = (2 * 10−3 + 1 * 10−2 + 3 * 10−1 + 4 * 100 + 3 * 101)D = = (2 * 0,001 + 1 * 0,01 + 3 * 0,1 + 4 * 1 + 3 * 10)D = (34312 , )D Il sistema di numerazione posizionale a noi noto è il sistema a base dieci. Ciò significa che il set di simboli utilizzati per la rappresentazione dei numeri è composto da dieci simboli: 0,1,2,3,4,5,6,7,8,9. I numeri sono quindi rappresentati da cifre che, a partire dal primo numero, lo zero, associano in sequenza i simboli formando dieci rappresentazioni da un simbolo. Per i numeri successivi la rappresentazione si comporrà di due simboli, il primo dei quali sarà il secondo simbolo (1) e il secondo simbolo sarà progressivamente la successione dei dieci simboli nello stesso ordine in cui si erano disposti per la rappresentazione a un solo simbolo. Questo meccanismo quindi si ripete progressivamente e si otterranno via via rappresentazioni con tre, quattro, cinque, ..., simboli in sequenza. Tale regola, che per tutti è implicita, consente anche di effettuare i calcoli aritmetici semplici - addizione, sottrazione, moltiplicazione e divisione utilizzando sostanzialmente il solo accorgimento del riporto e del prestito, dato che moltiplicazioni e divisioni sono riconducibili rispettivamente a addizioni e sottrazioni ripetute. Le stessa regole di formazione delle cifre e di calcolo tra le cifre si applicano pari pari in ogni altra rappresentazione di numeri in altre basi.
SISTEMA
DI NUMERAZIONE IN BASE
DUE (BIN)
In questo caso i simboli utilizzati per descrivere i numeri e costruirne le rappresentazioni (cifre) sono due: 0 e 1. Per quanto detto precedentemente, la costruzione delle rappresentazioni dei numeri quindi sarà la seguente: 0,1,10,11,100,101,110,111,1000,1001, ..., tutte rappresentazioni in base 2 e quindi più precisamente: (0)B , (1)B , (10)B , (11)B , (100)B , (101)B , (110)B , (111)B , (1000)B , (1001)B , ... Naturalmente eseguire i calcoli i base due può non essere immediatamente intuitivo;il fatto che 1+1 faccia 10 non è proprio naturale anche se è proprio così dato che 10 in binario rappresenta il numero due, come si può verificare dalla sequenza sopra riportata. Il gioco dei riporti e dei prestiti si riduce quindi ad una conoscenza pratica dell’aritmetica binaria in cui, ad esempio: 0-1 = -1 OPPURE 1 con prestito 1 dalla cifra a sinistra; 1+1 = 10 OPPURE 0 con riporto 1 sulla cifra a sinistra; 1+1+1+1+1 = 101 OPPURE 1 con riporto 10 sulle cifre a sinistra. La somma e la sottrazione quindi si riducono in una opportuna gestione dei riporti e dei prestiti. La moltiplicazione risulta essere molto semplice dato che le singole moltiplicazioni tra cifre binarie sono banali: 0*0=0; 0*1=0; 1*1=1. Anche la divisione risulta semplice dato che il divisore se inferiore al dividendo sta una volta (Q=1) o se superiore sta zero volte (Q=0) e quindi il ricalcolo del resto dopo la moltiplicazione per 1 o per 0 diventa immediato: basta eseguire una sottrazione binaria.
ESEMPIO: CALCOLI BINARI Si calcoli (101001)B + (11110)B. r 1 0 1 0 0 1 + ( 41 ) + 1 1 1 1 0 = ( 30 ) = --------------------1 0 0 0 1 1 1 ( 71 ) Si calcoli (101011)B * (111001)B. 1 0 1 0 1 1 * 1 1 1 0 0 1 = ----------1 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 - 1 0 1 0 1 1 - - 1 0 1 0 1 1 - - - 1 0 1 0 1 1 - - - - ----------------------1 0 0 1 1 0 0 1 0 0 1 1 Si calcoli (10000)B p p p p 1 0 0 0 0 1 = --------1 1 1 1
meno (1)B.
Si calcoli (110110)B : (1001)B. _______ - 1 1 0 1 1 0 : 1 0 0 1 = 1 1 0 1 0 0 1 ------- 1 0 0 1 1 0 0 1 ------- 0 0 0 0 Quindi Q=110 e R=000 Si calcoli (110011)B : (1000)B. _______ - 1 1 0 0 1 1 : 1 0 0 0 = 1 1 0 . 0 1 1 1 0 0 0 ------- 1 0 0 1 1 0 0 0 ------- 0 0 1 1 0 0 1 0 0 0 ------- 1 0 0 0 Quindi Q=110.011 e R=000 Si calcoli (111100)B : (111)B.
.doc - 1684Kb - 08/mar/2011 - 12 / 82
S3Abacus – Architetture/Asm
_____ - - 1 1 1 1 0 0 : 1 1 1 = 1 0 0 0 . 1 0 0 1 1 1 1 ----- - - 1 0 0 0 1 1 1 ------- - - 1 0 0 0 1 1 1 ------- - - 1
1 0 0 0.1 0 0 1 1 1 1 --------------1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 - ------------------1 1 1 0 1 1.1 1 1 1 0.0 0 0 1 ------------------1 1 1 1 0 0.0 0 0 0
* =
+ =
Quindi Q=1000.1001 e R=0.0001, infatti:
SISTEMA
DI NUMERAZIONE IN BASE
OTTO (OCT)
In questo caso i simboli utilizzati per descrivere i numeri e costruirne le rappresentazioni (cifre) sono otto: 0,1,2,3,4,5,6,7. Pertanto non potranno aversi rappresentazioni del tipo (1769)O, dato che il simbolo 9 non fa parte del set del sistema ottale. Per quanto detto precedentemente, la costruzione delle rappresentazioni dei numeri quindi sarà la seguente: (0)O , (1)O , (2)O , (3)O , (4)O , (5)O , (6)O , (7)O , (10)O , (11)O , ... Anche in questo caso bisogna tener presente l’aritmetica ottale che prevede prestiti e riporti opportuni: 6+2 = 10 OPPURE 0 con riporto 1 sulla cifra a sinistra 4+10+7 = 23 OPPURE 3 con riporto di due sulla cifra a sinistra Spesso si eseguono calcoli in base ottale facendo a mente le trasformazioni in decimale degli operandi, trovando il risultato in decimale e quindi riconvertendo il risultato in ottale. Ancora meglio è eseguire i caloli direttamente in ottale facendo uso delle tabelline di +, - e * riportate in ottale in modo da non confondere i riporti.
ESEMPI:
CALCOLI
OTTALI 2 4 5 3 1 0 6 7 --------1 3 3 4 3
Calcolare (5327)O + (321)O. 5 3 2 7 + 3 2 1 = ------5 6 5 0
Calcolare (715206)O - (31777)O 7 1 5 2 0 6 3 1 7 7 7 = ----------6 6 3 2 0 7
Calcolare (275)O * (37)O 2 7 5 * 3 7 = ------
SISTEMA
DI NUMERAZIONE IN BASE
SEDICI (HEX)
In questo caso i simboli utilizzati per descrivere i numeri e costruirne le rappresentazioni (cifre) sono sedici e il sistema è anche detto Esadecimale: 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F. Come di norma, la sequenza numerica di rappresentazione sarà: (0)H , (1)H , (2)H , (3)H , (4)H , (5)H , (6)H , (7)H , (8)H , (9)H , (A)H , (B)H , (C)H , (D)H , (E)H , (F)H ,(10)H , (11)H , ... Anche in questo caso potrebbe essere utile disporre di tabelline per il calcolo esadecimale, relativamente alle operazioni aritmetiche.
ESEMPIO: CALCOLI
IN
ESADECIMALE 1 6 8 5 6
Calcolare (A701)H + (FFB)H : A 7 0 1 + F F B = ------B 6 F C
Calcolare (FAC0)H * (21C)H : F A C 0 * 2 1 C = ------B C 1 0 0 F A C 0 1 F 5 8 0 - ------------2 1 0 E D 0 0
Calcolare (1A001)H meno (37AB)H : 1 A 0 0 1 3 7 A B = ---------
TRASFORMAZIONI
TRA SISTEMI
POSIZIONALI
L’algoritmo che a partire dalla rappresentazione di un numero in una data base N composta da n simboli, fornisce la rappresentazione dello stesso numero in una diversa base M composta da m simboli si può enunciare a passi:
.doc - 1684Kb - 08/mar/2011 - 13 / 82
S3Abacus – Architetture/Asm
0. Costruire una tabella biunivoca con le rappresentazioni dei simboli nella base maggiore associate alle rappresentazioni delle cifre della base minore. 1. Considerare la cifra data come dividendo, la base di destinazione come divisore, effettuare la divisione (nella base di partenza) con i numeri rappresentati nella base di partenza. 2. Considerare il quoziente Q e il resto R della divisione; indicare con indice progressivo a partire da zero il resto R e ripetere il passo 1. utilizzando Q come dividendo. 3. Procedere fino a quando Q coincide con zero. 4. Considerare i resti Ri indiciati. La nuova rappresentazione si ottiene disponendo i resti Ri secondo l’indice che rappresenterà il peso e traducendo eventualmente ogni resto Ri con la tabella di cui al punto 0. Quando si avessero cifre decimali con parte frazionaria, si può applicare un ulteriore algoritmo da applicare sulla parte frazionaria, del tutto autonomo e che consente di ottenere la parte frazionaria nella base desiderata: 1. Considerare la cifra frazionaria nella base di partenza e moltiplicarla per la base di destinazione espressa nella base di partenza. 2. Si ottiene una cifra in cui si nomina la parte frazionaria F e la parte intera I indiciandola a partire da -1. 3. Ripetere i passi 1. e 2. fino a ottenere F=0 o fino a quando richiesto. 4. La cifra frazionaria richiesta nella base di destinazione si ottiene dalle parti intere Ii in cui l’indice i è il peso.
ESEMPIO: TRASFORMAZIONE
DI CIFRE TRA BASI
Trasformare la cifra 123,625 espressa in base dieci nell’equivalente espressa in base due. Parte Intera Passo 0. La tabella diventa: DEC BIN 0 0 1 1 2 10 3 11 4 100 5 101 6 110 7 111 8 1000 9 1001 Passo 1. e 2. 123 DIV 2 61 DIV 2 30 DIV 2 15 DIV 2 7 DIV 2 3 DIV 2 1 DIV 2
Q:61 Q:30 Q:15 Q: 7 Q: 3 Q: 1 Q: 0
R0:1 R1:1 R2:0 R3:1 R4:1 R5:1 R6:1
Passo 3. La rappresentazione equivalente binaria della parte intera: (123)D = (R6R5R4R3R2R1R0)B = (1111011)B Passo 0,625 0,25 0,5
1. e 2. * 2 = 1,25 * 2 = 0,50 * 2 = 1,0
Parte Frazionaria F:0,25 F:0,5 F:0
I-1:1 I-2:0 I-3:1
Passo 3. La rappresentazione equivalente binaria della parte frazionaria: (0,625)D = (I-1I-2I-3)B = (0,101)B Totale In totale allora: (123,625)D = (1111011,101)B Trasformare la cifra 101010 espressa in base due nell’equivalente espresso in base dieci. Parte Intera Passo 0. La tabella, come prima, diventa: DEC BIN 0 0 1 1 2 10 3 11 4 100 5 101 6 110 7 111
.doc - 1684Kb - 08/mar/2011 - 14 / 82
S3Abacus – Architetture/Asm
8 9
1000 1001
Passo 1. Attenzione, la base di destinazione, dieci, in binario vale 1010! 101010 DIV 1010 100 DIV 1010
Q:100 Q:0
R0:10 [=2] R1:100 [=4]
e, pertanto, (101010)B = (42)D Come si può facilmente notare quando la base di partenza è diversa da dieci, l’algoritmo costringe ad usare, per le divisioni, l’aritmetica della base (diversa da dieci) che non sempre risulta essere ben conosciuta, o almeno non quanto l’aritmetica decimale. In effetti esiste una via alternativa per passare da una base N (≠ 10) ad una base M (≠ 10) che consiste nel passaggio intermedio alla base dieci. Infatti esiste un algoritmo piuttosto semplice per trasformare una cifra espressa in una base N (≠ 10) alla cifra equivalente in base dieci:
( Sk... S2S1S0 ) N
=
k
∑ (Si)D ( n) D( ) i
D
i=0
Allora, dalla base dieci alla base M (≠ 10) di destinazione si può effettivamente adottare l’algoritmo che implica l’utilizzazione dell’aritmetica decimale.
ESEMPI: TRASFORMAZIONI
DA BASI
N≠ 10
A BASE
DIECI = 28225
Trasformazioni BIN DEC (11010)B = (x)D x = 0*20 + 1*21 + 0*22 + 1*23 + 1*24 = = 0*1 + 1*2 + 0*4 + 1*8 + 1*16 = = 0 + 2 + 0 + 8 + 16 = = 26
(713,52)O = (x)D x = 2*8-2 + 5*8-1 + 3*80 + 1*81 + 7*82 = = 2/64 + 5/8 + 3 + 8 + 7*64 = = 459,65625
(101,11)B = (x)D x = 1*22 + 0*21 + 1*20 + 1*2-1 + 1*2-2 = = 1*4 + 0*2 + 1*1 + 1*(1/21) + 1*(1/22) = = 4 + 0 + 1 + 0,5 + 0,25 = = 5,75
Trasformazioni HEX DEC (A01F)H = (x)D x = 15*160 + 1*161 + 0*162 + 10*163 = = 15 + 16 + 0 + 40960 = = 40991
Trasformazioni OCT DEC (67101)O = (x)D x = 1*80 + 0*81 + 1*82 + 7*83 + 6*84 = = 1 + 0 + 64 + 3587 + 24576 =
ESEMPI: TRASFORMAZIONI
DA
BASE 10
Trasformazione DEC BIN (1045,123)D = (x)B 1045 | 1 0,123 * 522 | 0 0,246 * 261 | 1 0,492 * 130 | 0 0,984 * 65 | 1 0,968 * 32 | 0 0,936 * 16 | 0 8 | 0 4 | 0 2 | 0 1 | 1 0 | x =10000010101,00011
2 2 2 2 2 2
= = = = = =
0,246 0,492 0,984 1,968 1,936 ...
Trasformazione DEC OCT (3674,65625)D = (x)O
RAGGRUPPAMENTO
DI
A BASE
(1F,2A)H = (x)D x = 15*160 + 1*161 + 2*16-1 + 10*16-2 = = 15 + 16 + 2*(1/16) + 10*(1/256) = = 31,1640625
N≠ 10 3674 459 57 7 0
| | | | |
2 3 1 7
0,65625 * 8 = 5,25 0,25 * 8 = 2,0
x = 7132,52 Trasformazione DEC HEX (40991,615)D = (x)H
40991 2561 160 10 0
| | | | |
15 = F 1 0 10 = A
0,615 0,84 0,44 0,04 0,64 0,24 0,84
* 16 * 16 * 16 * 16 * 16 * 16 ...
= = = = = =
9,84 13[D],44 7,04 0,64 10[A],24 3,84
x = A01F,9D70A3
BIT
I sistemi di numerazione in base ‘potenza di due’, come ad esempio il sistema Ottale o il sistema Esadecimale posseggono una proprietà particolare nei confronti del sistema Binario. Molto probabilmente la stessa proprietà è valida tra basi analoghe (es. base 3 e basi 9, 12, ecc..). La proprietà consiste in questo: Si considerino una base opportuna N, in modo che ∃ k : 2k = n e si decodifichino in binario tutti gli n simboli della base N in modo da ottenere n rappresentazioni binarie da k simboli binari ognuna (riempiendo eventualmente con zeri).
.doc - 1684Kb - 08/mar/2011 - 15 / 82
S3Abacus – Architetture/Asm
All’interno delle macchine digitali le informazioni sono sempre rappresentate numericamente, anche le informazioni di natura non numerica come ad Allora data una qualsiasi cifra in base N, si ottiene la rappresentazione binaria di quella cifra sostituendo ad ogni simbolo della cifra la esempio parole ed immagini. corrispondente codifica binaria Evidentemente esistono modi specifici per interpretare l’informazione numerica di volta in volta come numero, numero con segno, numero con parte ovvero decimale, lettera alfanumerica, immagine, ecc... Data una cifra binaria qualsiasi, si possono raggruppare i bit in gruppi da k elementi, a partire da destra; si ottiene la rappresentazione in base N L’insieme di tali modi di interpretazione è detto anche codifica e rappresentazione dell’informazione. sostituendo ai gruppi il rispettivo simbolo in base N. Un aspetto che in ogni caso è necessario mettere in evidenza è che la rappresentazione numerica generale più spesso utilizzata per rappresentare Se si fosse in presenza di cifre dotate di parte frazionaria, il raggruppamento della parte frazionaria deve partire immediatamente dopo la virgola. le informazioni rispetto alle macchine digitali è la rappresentazione binaria o in generale rappresentazioni in basi potenze di due (base Otto e base Questa curiosa ma estremamente utile proprietà è anche detta del r a g g r u p p a m e n t o d i b i t . Sedici). Un’altra considerazione che è opportuno ricordare sempre è che i contenitori digitali delle informazioni numeriche sono sempre ben definiti in dimensione, in genere misurati con gruppi da otto bit detti B y t e (due simboli esadecimali). E SEMPI : RAGGRUPPAMENTO Altre misurazioni tipiche sonoDIil NBiIT b b l e (4 bit ovvero un simbolo esadecimale) o la P a r o l a , detta spesso anche Word, contenente in genere 16 bit (4 simboli esadecimali). Siano ad esempio le tabelle di decodifica: Un’altra questione terminologica che spesso si accompagna a queste, riguarda il riferimento ai bit di maggior peso in un contenitore, indicati con la BIN M OCT BINSignificant HEX BIN sigla S B (Most Bit) HEX o il riferimento ai bit di minor peso, indicati con L S B (Low Significant Bit). 0 000 0 0000con 8la quale 1000 La limitatezza intrinseca si rappresentano le informazioni a livello digitale implica un paio di considerazioni: 1 001 un1certo0001 1001 - assegnato numero9n di bit per la rappresentazione di una informazione, è sempre possibile definire un campo di validità per la 2 010 2 definito 0010 dal A numero 1010di bit assegnati. rappresentazione, 3 011 3 0011 B 1011 all’interno di un contenitore a n bit presenteranno alcuni zeri MSB per completare il contenitore. - le rappresentazioni di numerosi numeri 4 100 4 0100 C 1100 5 0101 un D contenitore 1101 Se ad101 esempio5si stabilisce da n=8 bit per rappresentare numeri naturali, il campo di validità del contenitore - in questo caso il byte 6 da110 1110 va 0 a 255, 6 infatti0110 la primaErappresentazione possibile evidentemente è (00000000)B=(0)D e l’ultima invece: (11111111)B = (255)D. 7 111 7 0111 F 1111 Inoltre si può notare come il numero (7)D, che possiede una rappresentazione binaria in (111)B, in questo caso si rappresenterà in (00000111)B. Si deve notare che se un contenitore è provvisto di n bit, i l c a m p o d i v a l i d i t à (range) per quel contenitore vale da 0 a 2n -1 Si deve anche considerare poi la situazione per cui una operazione, ad esempio di somma, tra due numeri contenuti nello stesso contenitore, potrebbe originare un numero con una rappresentazione che utilizza un numero di bit superiori a quelli previsti. In questo caso si parla di t r a b o c c a m e n t o (overflow) e si può operare un troncamento dei bit fuoriusciti mantenendo solo i bit all’interno del contenitore. A scanso di equivoci sarà necessario d’ora in poi specificare sempre il contenitore per una data rappresentazione, soprattutto per quanto riguarda le operazioni sui bit che introduciamo ora.
OPERAZIONI BITWISE Le operazioni a livello di bit (bitwise) si effettuano conoscendo a memoria le rispettive tabelline binarie che coincidono con le tavole di verità già viste in logica matematica. Esse sono: |NOT --+--0 | 1 1 | 0
| |AND ------0|0| 0 0|1| 0 1|0| 0 1|1| 1
| | OR ------0|0| 0 0|1| 1 1|0| 1 1|1| 1
| |XOR ------0|0| 0 0|1| 1 1|0| 1 1|1| 0
Se gli operandi non avessero lo stesso numero di bit è necessario ‘allineare’ l’operando con minor numero di bit giustapponendo degli zeri. Esistono anche operazioni miste, cioè con un argomento binario e uno decimale, il cui risultato è binario. - SHR (spostamento a destra " b shr n " ) Si tratta di spostare a destra di n posizioni i bit del 1° argomento. I bit LSB naturalmente scompaiono, mentre se l’operazione si riferisce ad un contenitore specifico si sostituiscono gli spazi vuoti con degli 0. - SHL (spostamento a sinistra " b shl n " ) Si tratta di spostare a sinistra di n posizioni i bit del 1° argomento. Se l’operazione si riferisce ad un contenitore specifico si sostituiscono gli spazi vuoti con degli 0 e i bit MSB man mano scompaiono. Inoltre si possono ricordare anche operazioni analoghe a queste però sempre riferite a contenitori, dette rotazioni: - ROR (rotazione a destra " b ror n " ) Analogo a shr, con l’accortezza di di far rientrare a sinistra i bit espulsi a destra. - ROL (rotazione a sinistra " b rol n " ) Analogo a shl, con l’accortezza di di far rientrare a destra i bit espulsi a sinistra.
ESEMPIO: OPERATORI
SUI
BIT
NB. Le cifre sono tutte binarie. Eseguire NOT(1001); NOT(1001)=110 oppure, se riferito al byte: 11110110 Eseguire (1001) AND (111); 1001 AND 111 = 1, oppure se riferito al byte: 00000001 Eseguire (1001) OR (111); 1001 OR 111 = 1111, oppure se riferito al byte: 00001111 Eseguire (1001) XOR (111); 1001 XOR 111 = 1110, oppure se riferito al byte: 00001110 Eseguire (1001) SHR (3); 1001 SHR 3 = 0001, oppure se riferito al byte: 00000001 Eseguire (1001) SHL (3); 1001 SHR 3 = 1001000, oppure se riferito al byte: 01001000
.doc - 1684Kb - 08/mar/2011 - 16 / 82
S3Abacus – Architetture/Asm
Eseguire (00001001) ROR (3); 00001001 ROR 3 = 00100001 Eseguire (00001001) ROL (3); 00001001 ROL 3 = 01001000 Calcolare 1. (x)B = Oppure (x)B =
le seguenti (34)H SHR 3 se riferito (34)H SHR 3
espressioni: = (110100)B SHR 3 = (000110)B a Byte: = (00110100)B SHR 3 = (00000110)B
2. (x)H = (3F1)H SHL 1 = (1111110001)B SHL 1 = (11111100010)B = (7E2)H Oppure se riferito a Parola: (x)H = (3F1)H SHL 1 = (0000001111110001)B SHL 1 = (0000011111100010)B = (07E2)H Le operazioni a bit Shr e Shl possono essere utilizzate per dividere o moltiplicare per potenze di 2. In particolare lo Shr divide e restituisce il quoziente (parte intera) della divisione per 2n, dove n è il secondo argomento dell'operazione. Lo Shl moltiplica e restituisce il prodotto della moltiplicazione per 2n, dove n è il secondo argomento dell'operazione.
MASCHERE
DI BIT
Spesso quando si utilizzano informazioni numeriche risulta utile riuscire a sapere se un dato bit di peso n vale zero o uno, oppure è necessario saper mettere a zero o a uno un determinato bit di peso n all’interno di una quantità numerica. Per leggere il valore del bit di peso n in una configurazione X è sufficiente creare un valore d’appoggio Mn con uguale numero di bit presenti in X, porre a 1 il solo bit di peso n e tutti i rimanenti a zero. Ora è possibile verificare il bit n-mo di X con l’operatore AND, cioè: - se X AND Mn è diverso da zero, allora il bit n-simo di X vale 1, altrimenti vale zero. Per scrivere a 1 il bit n-mo di un valore X invece è sufficiente costruire come prima Mn e utilizzare l’operatore OR: - X OR Mn da come risultato ancora X con il bit n-mo sicuramente settato a 1. Per scrivere a zero il bit n-mo di X invece si nega Mn (ottenendo ad esempio Qn) e si utilizza l’AND: - X AND Qn da come risultato sicuramente X con il bit n-mo a zero. Si noti che per costruire il valore Mn è sufficiente considerare che Mn = 2n.
ESEMPIO: PROPRIETÀ SPOSTAMENTI
E
MASCHERE
DI
BIT
Si verifichi che (7)D SHL 2 coincide con (28)D. (7)D SHL 2 = (111)B SHL 2 = (11100)B = (24+23+22)D = (16+8+4)D = (28)D Verificare il valore del bit 3 di (26)D. (26)D = (11010)B; Mn = (01000)B; (11010) AND (01000) = (01000) ≠ 0; il bit 3 di (26)D vale 1. Settare a 1 il bit 3 di (33)D. (33)D = (100001)B; Mn = (001000)B; (100001) OR (001000) = (101001) Settare a 0 il bit 4 di (26)D. (26)D = (11010)B; Qn = (01111)B; (11010) AND (01111) = (01010)
NUMERI
CON SEGNO
Ora ci si pone il problema della rappresentazione di numeri con segno, dato che una macchina digitale non possiede il simbolo ‘-’ e comunque oltre ad identificare tali rappresentazioni numeriche bisogna anche essere in grado di utilizzarle per le varie operazioni. Il problema è questo: avendo a disposizione uno dei contenitori descritti più sopra, come è possibile rappresentare in modo efficiente numeri con segno? cioè distinguere ad esempio -37 da + 37 dato che la loro rappresentazione binaria assoluta è la medesima? E’ necessario porre una convenzione di rappresentazione, in modo tale che quando la macchina è informata di tale convenzione riguardo una configurazione binaria essa possa comportarsi di conseguenza. Naturalmente la convenzione non può che utilizzare ancora solo i simboli zero e uno, magari disposti con oculatezza. E’ anche evidente che se un contenitore di ordine n può contenere 2n configurazioni di bit di valore assoluto, lo stesso contenitore dovrà ora contenerne la metà di segno positivo e l’altra metà di segno negativo, considerando lo zero avente uno dei due segni. La convenzione è la seguente: - I numeri positivi mantengono la consueta rappresentazione limitatamente a n-1 bit, badando di mantenere a zero il bit MSB; - I numeri negativi si ottengono dal rispettivo positivo complementandolo a due in binario. L’operazione di complementazione a 2 di un numero binario si ottiene facilmente negando il numero e aggiungendo uno. Consideriamo un contenitore a 4 bit; numeri positivi: numeri negativi: 0001 = +1 -1 = C2(0001) = (1110)+1 = 1111 0010 = +2 -2 = C2(0010) = (1101)+1 = 1110 0011 = +3 -3 = C2(0011) = (1100)+1 = 1101 0100 = +4 -4 = C2(0100) = (1011)+1 = 1100 0101 = +5 -5 = C2(0101) = (1010)+1 = 1011 0110 = +6 -6 = C2(0110) = (1001)+1 = 1010 0111 = +7 -7 = C2(0111) = (1000)+1 = 1001
.doc - 1684Kb - 08/mar/2011 - 17 / 82
S3Abacus – Architetture/Asm
Rimangono ora le due sole configurazioni 0000 e 1000 che si decide di assegnare la prima allo zero e la seconda al -8, dato che come si può notare, tutti i numeri positivi possiedono il MSB a zero e i numeri negativi a uno. La ragione per cui si usa la complementazione a due per la rappresentazione dei numeri negativi risiede nella proprietà che la somma tra due numeri con segno risulta essere perfettamente algebrica (se si tronca il traboccamento) e quindi nei vari microprocessori non è necessario realizzare un circuito apposta per la differenza dato che basta il sommatore. Si può generalizzare questa convenzione ad un contenitore di n bit e quindi affermare che il campo di validità per numeri con segno vale: -2n ... +2n -1
ESEMPI: OPERAZIONI Eseguire 1. (+4)D 2. (-2)D 3. (+5)D 4. (-1)D
CON
COMPLEMENTAZIONE
su 4 bit: + (-3)D + (+5)D + (-8)D + (-4)D
Leggendo le codifiche direttamente dalla tabella: (0100)+(1101) = (0001) = (+1)D (1110)+(0101) = (0011) = (+3)D (0101)+(1000) = (1101) = (-3)D (1111)+(1100) = (1011) = (-5)D
Naturalmente se si decidesse di sommare algebricamente -1 a -8 oppure +2 a +7 il risultato non sarebbe corretto e si parla rispettivamente di u n d e r f l o w e di o v e r f l o w generato dall’operazione. queste situazioni sono rilevate generalmente senza alcun problema dalle macchine digitali.
NUMERI
CON PARTE FRAZIONARIA
Lo stesso problema di rappresentazione si ripropone a maggior ragione per numeri con parte frazionaria. in questo caso la classica rappresentazione in virgola mobile consistente di m a n t i s s a per base elevata ad esponente del tipo m·be (es. 6,68183·103 = 0,668183·104 = 668183·10-2 ) può essere utilizzata per formalizzare la rappresentazione in v i r g o l a m o b i l e n o r m a l i z z a t a in cui la mantissa è tale per cui: 1/b < m < 1. Nell'esempio precedente la forma in virgola mobile normalizzata diventa: 0,668183*104 Ad esempio, dovendo rappresentare il valore binario 11010,111 (26,875 decimale), la sua virgola mobile normalizzata binaria diventa: 0,11010111·10101 (cioè 0,83984·25), che in una rappresentazione a 16 bit può diventare: mantissa esponente ± 1 1 0 1 0 1 1 1 0 0 0 ± 1 0 1 dove i simboli ± rappresentano i bit di segno di mantissa ed esponente Nell’esempio entrambi i simboli ± sarebbero stati a zero dato che la parte esponenziale in genere è rappresentata in complemento a due, mentre la mantissa in modulo e segno. Il numero di bit dedicati alla mantissa è detta p r e c i s i o n e della rappresentazione e il numero di bit destinati all'esponente è detto o r d i n e d i g r a n d e z z a della rappresentazione. Inoltre, siccome il primo bit dopo il segno della mantissa è sempre a 1, esiste la convenzione di darlo per scontato, recuperando quindi una posizione di precisione (c o n v e n z i o n e d e l b i t n a s c o s t o ).
RAPPRESENTAZIONI
IN ECCESSO
2
N-1
Prima di introdurre i dettami dello standard che dal 1980 disciplina la convenzione di rappresentazione dei numeri in virgola mobile, introduciamo un secondo tipo di rappresentazione dei numeri negativi in formato binario. Dato un contenitore a m bit, si definisce rappresentazione di numeri n e g a t i v i i n e c c e s s o 2 m-1 la rappresentazione che si ottiene quando, dato un qualsiasi numero con segno s in notazione decimale ad esempio, si rappresenta quel numero in binario con la forma binaria standard di: s + 2m-1
ESEMPIO: RAPPRESENTAZIONI
IN ECCESSO
8
Se si parla di eccesso 8 significa che Iniziamo la rappresentazione a partire 0, allora si considera 0 + 8 = 8, la +1, allora si considera +1 + 8 = 9, la +2, allora si considera +2 + 8 =10, la ... +7, allora si considera +7 + 8 =15, la
il contenitore vale 4 (infatti da zero: cui rappresentazione binaria è cui rappresentazione binaria è cui rappresentazione binaria è
24-1 = 8). 1000, quindi 0 -> 1000 1001, quindi +1 -> 1001 1010, quindi +2 -> 1010
cui rappresentazione binaria è 1111, quindi +7 -> 1111
Evidentemente non è possibile rappresentare +8, dato che +8+8=16 che non è rappresentabile su 4 bit. Si passa ai numeri negativi, e quindi si inizia con meno uno: -1, allora si considera -1 + 8 = 7, la cui rappresentazione binaria è 0111, quindi -1 -> 0111 -2, allora si considera -2 + 8 = 6, la cui rappresentazione binaria è 0110, quindi -2 -> 0110 ... -8, allora si considera -8 + 8 = 0, la cui rappresentazione binaria è 0000, quindi -8 -> 0000 Si noti come la rappresentazione eccesso 2m-1 è identica alla rappresentazione in complemento due se si invertono i bit di segno.
.doc - 1684Kb - 08/mar/2011 - 18 / 82
S3Abacus – Architetture/Asm
STANDARD IEEE 754 Il documento I E E E 7 5 4 prima di tutto classifica le rappresentazioni rispetto alla grandezza dei contenitori e quindi prevede: - rappresentazione con p r e c i s i o n e s e m p l i c e a 32 bit - rappresentazione con p r e c i s i o n e d o p p i a a 64 bit - rappresentazione con p r e c i s i o n e e s t e s a a 80 bit Naturalmente la rappresentazione dell’IEEE tratta numeri reali in virgola mobile normalizzata utilizzando la convenzione del bit nascosto della mantissa (primo bit della mantissa considerato di default a 1 e quindi non riportato nella rappresentazione), la quale, non essendo completa, viene chiamata s i g n i f i c a n t e . Inoltre la normalizzazione in base 2 adottata prevede sempre una cifra intera a 1 binario (cioè le normalizzazioni binarie adottate sono della forma 1,bbbbb.... invece che della forma standard 0,bbbbb...); pertanto il bit nascosto non sarà la prima cifra dopo la virgola ma il bit a 1 sempre presente nella parte intera. Esaminando lo standard in precisione semplice, la disposizione degli elementi all’interno dei 32 bit è la seguente: segno esponente significante
1
8
23
Esaminando lo standard in precisione doppia, la disposizione degli elementi all’interno dei 64 bit è la seguente: segno esponente significante
1
11
52
In precisione estesa i tre campi S,E,M valgono 1,15 e 64. Le convenzioni utilizzate sono le seguenti: • il bit di segno, che indica il segno globale del numero in virgola mobile, è 0 per i numeri positivi e 1 per quelli negativi; • l’esponente è espresso in eccesso 127 (precisione semplice) o in eccesso 1023 (precisione doppia) • il significante è quasi sempre in virgola mobile normalizzata con bit nascosto. • eccezioni: i. se l’esponente vale 0 e il significante vale 0, allora il numero complessivo vale 0 a prescindere dal bit di segno; ii. se l’esponente vale 0 e il significante è ≠ da 0, il numero complessivo è denormalizzato; iii. se l’esponente ha tutti i bit a 1, cioè è il massimo valore positivo concesso in eccesso 128 o 1024, e il significante vale 0, il numero complessivo vale infinito (∞); iv. se l’esponente ha tutti i bit a 1, cioè è il massimo valore positivo concesso in eccesso 128 o 1024, e il significante è ≠ 0, il numero complessivo è un NAN (Not a Number) cioè ad esempio ∞ diviso ∞. Va da sè che un numero in virgola mobile normalizzata non potrà avere esponente con tutti zeri o con tutti uno. Infatti si usa l’eccesso 127 (1023) e non l’eccesso 128 (1024) per evitare le due configurazioni di esponente con tutti i bit uguali. Un numero denormalizzato è tale quando l’esponente vale 0 e il significante è ≠ 0; in questo caso si da come convenzione che l’esponente vale 2127 (o 2-1023) e il significante ridiventa una mantissa non essendoci più il bit nascosto. In questo caso si ottengono rappresentazioni fino a 2-150 (o 21075 ) che allontanano l’underflow.
ESEMPIO: RAPPRESENTAZIONI
IN PRECISIONE SEMPLICE
IEEE 754
Vediamo la rappresentazione del numero 1,5 in precisione semplice. Trasformato in binario si ottiene: (1,1)B. Normalizzato: 1,1 100 (attenzione: 10B = 2D !) Mantissa: 11000000 Significante: 10000000 (tolto il primo bit) Esponente: (0)B = (0)D, quindi in eccesso 127: (0+127)D = (127)D = (01111111)B Segno: 0 (00111111110000000000000000000000)B IEEE754 = (1,5)D (3 F C 0 0 0 0 0 )H IEEE754 = (1,5)D Vediamo la rappresentazione per 128,5. Binario: 10000000,1 Normalizzato: 1,00000001 10111 Mantissa: 100000001 Significante: 00000001 Esponente: (111)B = (7)D, quindi in eccesso 127: (7+127)D = (134)D = (10000110)B Segno: 0 (01000011000000001000000000000000)B IEEE754 = (128,5)D (4 3 0 0 8 0 0 0 )H IEEE754 = (128,5)D Per 1 si ottiene 3F800000
S3Abacus – Architetture/Asm
per 0,5 si ottiene 3F000000.
.doc - 1684Kb - 08/mar/2011 - 19 / 82
.doc - 1684Kb - 08/mar/2011 - 20 / 82
S3Abacus – Architetture/Asm
CODICI, COMUNICAZIONE, INFORMAZIONE Le rappresentazioni numeriche su diverse basi che si sono studiate in precedenza sono necessarie per poter far funzionare le macchine digitali, le quali possiedono unità intelligenti basate solo sull’aritmetica binaria. Possiamo affermare che le rappresentazioni numeriche su diverse basi sono parte integrante e interna alle macchine digitali (i calcolatori). In questo capitolo invece si prendono in considerazione vari modi per rappresentare l’informazione, e quindi non necessariamente i numeri. Qualche malinteso potrebbe sorgere dato che anche i numeri sono informazione, ma, come ci insegna l’esperienza, ne rappresentano solo una parte. Ad esempio una qualsiasi frase tratta dall’esperienza quotidiana rappresenta informazione: “Dove ti rechi oggi pomeriggio?”; questa frase è informazione, ma non contiene numeri. Cionoindimeno molta informazione utilizza numeri al suo interno, senza i quali non significherebbe nulla: “Oggi vado a scuola alle 8 e 30”. Il fatto che avremmo potuto esprimere la stessa informazione con: “Oggi vado a scuola alle otto e trenta” per ora non sarà considerato. Fondamentale invece è la considerazione per cui l’informazione ha senso di esistere solo se può essere veicolata, cioè trasferita a qualcuno che sia diverso da chi la detiene. Ciò significa che l’informazione prevede necessariamente la comunicazione e viceversa. Il problema della codifica e dei codici per l’informazione è strettamente collegato alla comunicazione. La comunicazione è un processo che si instaura tra due o più entità, umane o materiali (es. le macchine digitali), che ha lo scopo di trasferire informazione. Il processo quindi prevede almeno due soggetti, che possono essere chiamati t r a s m e t t i t o r e e r i c e v i t o r e i quali possono scambiarsi vicendevolmente i ruoli: il trasmettitore può diventare ricevente e viceversa. L’oggetto della comunicazione invece rimane già individuato e coincide con l’informazione stessa. I due soggetti della comunicazione per poter comunicare devono possedere alcuni requisiti indispensabili: a. un linguaggio in comune (per rappresentare l’informazione) b. un dispositivo per realizzare il linguaggio c. alcune regole condivise per saper comunicare (per non interferirsi durante la comunicazione) d. un canale di trasmissione in comune Abbiamo omesso l’apparato ricevente dato che possiamo pensarlo assolutamente integrato nel dispositivo di realizzazione della trasmissione di cui al punto b. Questi requisiti sono sempre presenti in ogni forma di comunicazione, dalla comunicazione tramite il l i n g u a g g i o n a t u r a l e tra uomini, alla comunicazione automatizzata che avviene tra due calcolatori elettronici. Nel linguaggio naturale si possono individuare le componenti che abbiamo elencato in: - soggetti della comunicazione: due persone; - oggetto della comunicazione: un concetto, una frase, ecc...; - linguaggio della comunicazione: lingua in uso nella comunità a cui appartengono le persone; - dispositivo della comunicazione: l’insieme di cervello, orecchie, lingua e corde vocali posseduto dalle persone; - regole della comunicazione: es. chiedere la parola e non sovrapporsi, sincronizzando automaticamente ascolto e parlato; - canale della comunicazione: l’aria, che consente la propagazione delle onde sonore di cui è composto il suono emesso dalla voce. In una comunicazione automatizzata tra macchine invece potremmo individuare: - soggetti: due calcolatori; - oggetto: un qualsiasi insieme di concetti in genere detti dati ; - linguaggio: una convenzione qualsiasi su come organizzare i bit; - dispositivo: una scheda di interfaccia; - regole: un insieme di regole che sincronizza le fasi della comunicazione e che in gergo viene detto protocollo; - canale: un cavo oppure onde elettromagnetiche (radio), infrarosso, ecc... ARGOMENTI DI QUESTA SEZIONE SARANNO SOLTANTO: - il linguaggio di organizzazione dei dati, anche detto c o d i f i c a d e l l e i n f o r m a z i o n i - alcune regole di sincronizzazione della comunicazione; in particolare quelle volte a individuare gli errori nel processo di comunicazione, detto anche g e s t i o n e e r r o r i . Per quanto ci riguarda useremo come unità minima di informazione il bit, cioè la possibilità di un sistema di assumere solo due possibili stati che si indicano in genere con 0 e 1. L’informazione quindi sarà identificata con la rappresentazione di dati tramite sequenze di bit. Se si individuano allora un numero M di dati che saranno oggetto di informazione, la rappresentazione di questa informazione in formato binario cioè tramite sequenze di bit - potrà essere possibile se ad ogni dato tra gli M dati disponibili è associabile una differente sequenza di bit. Se ad esempio si dovesse gestire l’informazione basata sulle 26 lettere dell’alfabeto inglese, sarà necessario individuare 26 configurazioni di bit differenti per rappresentare ogni singola lettera. In questo caso la quantità di informazione M vale 26. La codifica delle M (=26) informazioni differenti (=“abcdefghijklmnopqrstuvwzxy”) avviene associando ad ogni dato (cioè ad ogni singola lettera) una sequenza di bit differente in modo che, data una lettera qualsiasi si possa immediatamente individuare la sua codifica binaria e, data una codifica binaria sia immediatamente possibile individuare il suo dato (la lettera). In altre parole, un insieme di potenza M può essere misurato (interscambiabile) con un insieme di M configurazioni binarie (e viceversa). Chiarito questo fatto generale, giunge spontaneo porsi il problema: assegnata una quantità di informazione da rappresentare e definita M, quante cifre binarie (bit) sono necessarie per effettuare le configurazioni che dovranno rappresentare gli M dati? Il passo successivo riguarda l'individuazione del numero di cifre binarie (N) necessarie per esprimere M informazioni. Ciò deriva dalla relazione seguente per la quale, assegnato M, il valore N si ricava: N = log2 M
(se M potenza esatta di 2) oppure
N = INT(log2M)+1 con INT() che significa: “parte intera di ( )”.
(se M non potenza esatta di 2)
S3Abacus – Architetture/Asm
ESEMPIO: RAPPRESENTAZIONE
.doc - 1684Kb - 08/mar/2011 - 21 / 82
OTTIMALE DI INFORMAZIONE
Si debba rappresentare l’informazione contenuta nell’insieme delle lettere dell’alfabeto inglese. Le lettere dell’alfabeto inglese sono 26, cioè M=26; M non è una potenza esatta di due, infatti (24=16≠ 26 e 25=32≠ 26); allora si utilizza la formula: N = INT(log2M)+1 con M=26; N = INT(log226)+1; N = INT(4.701)+1; N = 4+1 = 5; Quindi servono sequenze da 5 bit per rappresentare 26 informazioni differenti Una possibile codifica delle 26 informazioni potrebbe essere: a - 00000 f - 00101 k - 01010 p - 01111 u - 10100 x - 11001 b - 00001 g - 00110 l - 01011 q - 10000 v - 10101 c - 00010 h - 00111 m - 01100 r - 10001 w - 10110 d - 00011 i - 01000 n - 01101 s - 10010 z - 10111 e - 00100 j - 01001 o - 01110 t - 10011 x - 11000 Naturalmente rimangono 32-26 = 6 configurazioni da 5 bit inutilizzate. Si debba rappresentare l’informazione relativa ai nominativi degli abitanti di Parma, stimati in 175000 unità Il dato M quindi vale 175000; siccome 175000 non è una potenza esatta di 2 (), si usa la formula: N = INT(log2M)+1 con M=175000; N = INT(log2175000)+1; N = INT(17.41)+1; N = 17+1 = 18; Allora con un contenitore da 18 bit si possono rappresentare, con una codifica opportuna, tutti gli abitanti di Parma.
CODIFICHE
NUMERICHE
Come si diceva poco fa, l’informazione è composta variamente sia di concetti numerici, sia di concetti alfabetici; spesso è necessario effettuare comunicazioni il cui oggetto è solo informazione numerica, mentre in altri casi bisogna poter comunicare sia dati numerici che dati alfabetici che, in una parola, sono detti alfanumerici. Per questo motivo si distingue tra C o d i f i c h e N u m e r i c h e e C o d i f i c h e A l f a n u m e r i c h e . La codifica numerica riassume una serie di regole e proprietà per rappresentare con configurazioni di bit i simboli numerici decimali da 0 a 9 o, in qualche caso, i numeri esadecimali da 0 a F. Le proprietà generiche delle configurazioni di bit rappresentanti simboli numerici si possono riassumere: • Distanza di Hamming • Autocomplementazione (solo codifica numerica) • Pesatura (solo codifica numerica) • Ridondanza La D i s t a n z a d i H a m m i n g tra due configurazioni è data dal numero di bit differenti di ugual peso presenti nelle due configurazioni; La proprietà di A u t o c o m p l e m e n t a z i o n e tra due configurazioni si valuta solo quando i simboli numerici rappresentati sono complemento a 9 (o a F); in questo caso l’autocomplementazione è presente se le due configurazioni corrispondenti sono complemento a 1 (una la negazione dell'altra); La caratteristica della P e s a t u r a è rispettata se esiste un algoritmo che applicato ad una configurazione di bit consente di ottenere il simbolo rappresentato mediante somme di valori sempre uguali per ogni posizioni (sommati o meno a seconda del valore 0 o 1 del bit); La R i d o n d a n z a in una codifica si ottiene se il numero di configurazioni di bit ottenibili (N) per rappresentare M informazioni è superiore al numero di informazioni, cioè se 2N > M.
ESEMPI
DI PROPRIETÀ DEI CODICI
Esempio di distanza di Hamming codifica1: 10101010 codifica2: 10100001 ^ ^^ La distanza di Hamming tra le due codifiche vale 3 dato che i bit di ugual peso cambiano tre volte. Esempi di autocomplementazione codifica simbolo 3: 0011 codifica simbolo 6: 1100 Le due configurazioni sono complementanti infatti: 3+6=9 e le codifiche sono negate codifica simbolo 4: 0011 codifica simbolo 6: 1100 Le due configurazioni non sono complementanti infatti (4+6≠ 9); codifica simbolo 3: 0011 codifica simbolo 6: 1101 Le due configurazioni non sono complementanti infatti pur essendo 6+3=9, i bit non sono invertiti.
S3Abacus – Architetture/Asm
.doc - 1684Kb - 08/mar/2011 - 22 / 82
Esempio di pesatura codifica simbolo 3: 0011, se pesi b3=2, b2=4, b1=2,b0=1 si ottiene 0*2+0*4+1*2+1*1=3; codifica simbolo 6: 1100, se pesi b3=2, b2=4, b1=2,b0=1 si ottiene 1*2+1*4+0*2+0*1=6; codifica simbolo 1: 0000, non esiste algoritmo per ottenere 1 da 0000 ! Vediamo ora alcuni tra i più utilizzati codici numerici e le loro proprietà complessive
CODIFICA NUMERICA BCD La codifica numerica BCD (B i n a r y C o d e d D e c i m a l ) è quella che si ottiene trasformando in binario le 10 cifre decimali e considerandone la configurazione a 4 posti (siccome M=10, N=log2M+1, cioè 4). - Il codice BCD è sicuramente pesato (b0=1; b1=2; b2=4; b3=8); - è ridondante (infatti esistono 6 configurazioni inutilizzate); - la distanza di Hamming massima tra due configurazioni adiacenti è 4 (tra 7 e 8); - non è autocomplementante (es. 0 e 9 non hanno i bit invertiti) e così via
CODIFICA NUMERICA ECCESSO 3 La c o d i f i c a n u m e r i c a E c c e s s o 3 si ottiene da quella BCD iniziando a codificare lo 0 con il 3 BCD e proseguendo analogamente. - Il codice Eccesso 3 non è pesato; - è ridondante (infatti esistono 6 configurazioni inutilizzate); - la distanza di Hamming massima tra due configurazioni adiacenti è 4 (tra 9 e 0); - è autocomplementante (ogni configurazione di cifre complemento a 9 ha i bit corrispondenti invertiti)
CODIFICA NUMERICA AIKEN la c o d i f i c a n u m e r i c a A i k e n si ottiene considerando che il peso dei bit assegnato è b3=2, b2=4, b1=2, b0=1; - Il codice numerico Aiken è sicuramente pesato; - è ridondante (infatti esistono 6 configurazioni inutilizzate); - la distanza di Hamming massima tra due configurazioni adiacenti è 4 (tra 9 e 0); - è autocomplementante (ogni configurazione di cifre complemento a 9 ha i bit corrispondenti invertiti)
CODIFICA NUMERICA GRAY La c o d i f i c a n u m e r i c a G r a y si ottiene considerando che la distanza tra una configurazione e la successiva (e la precedente) è sempre e solo 1. In realtà il codice è usato per rappresentare 16 simboli a partire dalla prima configurazione per lo 0 che possiede la forma 0000; il codice Gray a 10 simboli è l'eccesso 3 del codice gray a 16 simboli (si scartano le configurazioni di 0,1,2,D,E,F). - Il codice numerico Gray non è pesato; - è ridondante (infatti esistono 6 configurazioni inutilizzate); - la distanza di Hamming massima tra due configurazioni adiacenti è 1; - non è autocomplementante.
BCD b2 b1 0 0 0 0 0 1 0 1 1 0 1 0 1 1 1 1 0 0 0 0
0 1 2 3 4 5 6 7 8 9
b3 0 0 0 0 0 0 0 0 1 1
b0 0 1 0 1 0 1 0 1 0 1
0 1 2 3 4 5 6 7 8 9
ECCESSO 3 b3 b2 b1 0 0 1 0 1 0 0 1 0 0 1 1 0 1 1 1 0 0 1 0 0 1 0 1 1 0 1 1 1 0
0 1 2 3 4 5 6 7 8 9
b3 0 0 0 0 0 1 1 1 1 1
AIKEN b2 b1 0 0 0 0 0 1 0 1 1 0 0 1 1 0 1 0 1 1 1 1
b0 0 1 0 1 0 1 0 1 0 1
0 1 2 3 4 5 6 7 8 9
b3 0 0 0 0 0 1 1 1 1 1
GRAY b2 b1 0 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 0 1
b0 0 0 1 1 0 0 1 1 0 0
b0 1 0 1 0 1 0 1 0 1 0
CODIFICHE ALFANUMERICHE CODIFICA ALFANUMERICA BAUDOT Il c o d i c e B a u d o t è stato uno dei primi codici alfanumerici ad essere implementato (serviva per gestire la comunicazione tra telescriventi); si tratta di un codice a 5 bit per una codifica totale di 32 informazioni; in realtà due configurazioni sono dedicate per la decodifica delle successive (11111=LTR; 11011=NUM) in modo tale da poter interpretare le configurazioni in un modo (LTR=lettera) se anticipate dal blocco LTR o in un altro (NUM=numero) se anticipate dal blocco NUM. In tutto quindi si codificano 60 informazioni.
CODIFICA ALFANUMERICA ASCII Si tratta del codice più utilizzato nel mondo dei calcolatori (A m e r i c a n S t a n d a r d C o d e I n t e r c h a n g e I n f o r m a t i o n ) e si tratta di un codice standard di 7 bit (128 informazioni) e pseudostandard a 8 bit (256 informazioni). Il codice ASCII esteso a 8 bit di norma usato sulle macchine PC è detto P C - 8 . Tra le categorie di informazioni codificate all’interno del codice ASCII ricordiamo: • simboli di controllo di trasmissione (da 0 a 6 e da 21 a 23)
S3Abacus – Architetture/Asm
• • • •
simboli di controllo di stampa (da 7 a 15) simboli di controllo dispositivi (dal 17 al 20) simboli numerici e alfanumerici (dal 32 al 127) simboli grafici e tipografici (dal 128 al 255)
.doc - 1684Kb - 08/mar/2011 - 23 / 82
.doc - 1684Kb - 08/mar/2011 - 24 / 82
S3Abacus – Architetture/Asm
CODICI ED ERRORI Una caratteristica tipica della comunicazione con codifica delle informazioni è la possibilità di gestire in fase di ricezione eventuali errori dovuti a disturbi incontrati dai dati durante la comunicazione. Infatti quando l’informazione si trova sul mezzo di trasmissione (cavi, aria, onde elettromagnetiche) può subire, in varie misure, interferenze che modificano il contenuto dei bit. Sintetizzando: succede piuttosto spesso che il trasmettitore trasmette il dato A correttamente, durante la comunicazione eventuali disturbi mutano il dato in B e quindi il ricevitore riceve B. Per affrontare politiche di controllo, prevenzione e verifica di errori di trasmissione si possono analizzare e studiare vari aspetti della costruzione dei codici e delle codifiche. In questo senso si può operare per costruire c o d i c i r i v e l a t o r i di errore, mediante i quali è possibile stabilire se un dato ricevuto è o non è corretto; oppure si possono costruire c o d i c i c o r r e t t o r i di errore mediante i quali si riesce ad individuare l'errore presente nel dato e a correggerlo automaticamente. In tutti i casi i codici costruiti in tal senso devono per forza di cose essere r i d o n d a n t i (cioè devono contenere più informazione di quella strettamente necessaria per il dato da trasmettere), in modo da trasmettere nell’informazione supplementare le informazioni utili per la rilevazione e/o la correzione dell'errore.
PARITÀ Una tecnica molto utilizzata nel campo delle comunicazioni è quella di dotare ogni codifica - eventualmente ogni gruppo specificato di codifiche - di un bit supplementare che vale: 0 se il numero di bit a 1 nella codifica è dispari o 1 se il numero di bit a 1 presenti all’interno della codifica è pari (p a r i t à D I S P A R I ) ovvero 0 se il numero di bit a 1 presenti all’interno della codifica è pari o 1 se il numero di bit all’interno della codifica è dispari (p a r i t à PA R I ).
ESEMPIO: PARITÀ Dato da trasmettere: 10000001; n. di bit a “1”: 2; il bit di parità
PARI è 0; in totale: 10000001(0).
Dato da trasmettere: 10000011; n. di bit a “1”: 3; il bit di parità
PARI è 1; in totale: 10000011(1). Oppure
Dato da trasmettere: 10000001; n. di bit a “1”: 2; il bit di parità DISPARI è 1; in totale: 10000001(1). Dato da trasmettere: 10000011; n. di bit a “1”: 3; il bit di parità DISPARI è 0; in totale: 10000011(0). Corredare di controllo di parità un codice significa che il trasmettitore e ricevitore si mettono d’accordo a priori sul tipo di parità - Pari o Dispari -, e il trasmettitore, una volta deciso di inviare una codifica (es. 10000001), calcola il bit di parità della codifica (in questo caso per parità Pari il bit di parità vale 0) e invece di inviare la codifica originale, invia una nuova codifica in cui compare al primo (o all’ultimo) posto il bit di parità. Il trasmettitore quindi spedirà: 100000010 (parità pari). Il ricevitore, che sa di questa convenzione, prende in considerazione la parte di codifica depurata dal bit di parità, ne calcola il bit di parità e lo confronta con il bit di parità ricevuto. Il ricevitore quindi potrà ricevere, a seconda dei casi: a) dato corretto, cioè 100000010. Egli considererà solo 10000001, ne calcolerà il bit di parità (0) e lo confronterà con quello ricevuto (0) deducendo che il dato ricevuto è corretto. b) dato errato, ad esempio 100000110. Egli considererà solo 10000011, ne calcolerà il bit di parità (1) e lo confronterà con quello ricevuto (0) deducendo che il dato ricevuto è errato. Naturalmente il metodo di semplice parità offre un sistema per rivelare un errore e non è esente da problemi di doppi errori (in cui la parità persiste) e nel caso in cui l’errore dovesse modificare proprio il bit di parità. In altri termini il ricevitore riesce solo a stabilire con certezza un errore (quando il controllo di parità non torna), ma non è in grado di essere certo della validità del dato (quando il controllo di parità risulta corretto).
PARITÀ
INCROCIATA
Con la p a r i t à i n c r o c i a t a si suggerisce un sistema per rivelare ed individuare (quindi correggere) eventuali errori. Si tratta di applicare la parità ad un gruppo di dati sia in senso orizzontale (come già visto prima) detta p a r i t à Tr a s v e r s a l e , sia in senso verticale, detta parità Longitudinale. Tali parità devono quindi essere spedite; la prima assieme ai bit della codifica a cui si riferisce, la seconda come blocco finale al termine della sequenza. Naturalmente tutto ciò è verificabile solo su gruppi specificati a priori di codifiche.
ESEMPIO: PARITÀ INCROCIATA Vediamo di spedire ad esempio l'informazione ERRORE!! utilizzando ASCII a 7 bit con parità pari incrociata: informazione
ascii 7 bit
parità trasversale
INFORMAZIONE TRASMESSA
.doc - 1684Kb - 08/mar/2011 - 25 / 82
S3Abacus – Architetture/Asm
E R R O R E ! !
1 0 0 0 1 1 0 1 0 0 1 0 1 0 0 1 0 0 1 1 1 0 1 0 0 1 0 0 0 1 0 1 0 0 0 0 1 0 0 0 0 0 1 1 1 parità longitudinale
0 1 1 1 1 0 0 0 0
1 0 0 1 0 1 1 1 1
1 1 1 1 1 1 0 0
1 1 1 1 1 1 0 0 0
1 1 1 1 1 1 0 0 0
0 0 0 0 0 0 1 1 0
0 1 1 0 1 0 0 0 1
0 0 0 1 0 0 0 0 1
1 0 0 1 0 1 0 0 1
0 1 1 1 1 0 0 0 0
1 0 0 1*
0 1 1 1 1
0
Se ad esempio ci fosse un errore di trasmissione nel b0 della lettera “O”, cioè se il ricevente ricevesse 0 invece di 1, facendo i propri conti scoprirebbe che il b0 di parità longitudinale ricevuto sarebbe dovuto essere 0 (mentre invece è 1) e contemporaneamente il b7 della lettera “O” (parità trasversale) dovrebbe essere a 0 (invece è a 1). Il ricevente, scoprendo l'incongruenza di b0 Parità Longitudinale e b7 Parità Trasversale, può dedurre che il bit errato è proprio il b0 della lettera “O”. Naturalmente anche in questo caso la parità incrociata fallisce se ci sono errori doppi o se l'errore riguarda proprio le informazioni di parità. Come prima il ricevitore ha solo certezza quando il controllo di parità incrociata fallisce (ha la certezza dell’errore).
CODICE
DI
HAMMING
Il c o d i c e d i H a m m i n g consente di aggiungere ad un codice dato, una serie di bit in ogni codifica che consente di rilevare errori singoli e multipli e, in qualche caso, di autocorreggerli. Naturalmente non è sempre possibile, anche con la codifica di Hamming, poter rilevare o correggere l’errore. In particolare, data la codifica originale, se la distanza minima di Hamming è 2, è possibile solo una codifica autorilevante; affinchè la codifica sia anche autocorrettiva, è necessario che la distanza minima di Hamming tra le configurazioni sia 3. Il numero di bit supplementari (k) necessari per dotare un codice a n bit con la codifica di Hamming, si calcola ricordando la r e l a z i o n e d i Hamming: n ≤ 2k-k-1 dove: n = numero di bit del codice dato; k = numero di bit da aggiungere ad ogni codifica per ottenere il nuovo codice rilevatore e correttore; In questo modo, a partire da un codice a n bit se ne otterrà uno a (n + k) bit. Le fasi per la costruzione del codice sono: 1. Creare una codifica valida con distanza di Hamming opportuna (esempio: 2 o 3) determinando quindi n; 2. Individuazione di k mediante la relazione di Hamming; 3. Scrivere una sequenza vuota di bit di lunghezza j=n+k numerando ogni posizione da sinistra a destra partendo da 1; trascrivere il valore della posizione riportata anche nella sua forma binaria. 4. Assegnare ai k bit aggiuntivi - chiamati Xi =1...k - le posizioni graduali a potenze di due; (Es. se k=3; X1 va in posizione 1; X2 va in posizione 2; X3 va in posizione 4); scrivere le descrizioni (o i contenuti) dei bit del codice (detti Bi =1...n) nelle posizioni lasciate libere dagli Xi; 5. Determinare i valori di controllo per ogni X i, individuando le posizioni in cui l'unico bit a 1 della posizione binaria di X i è presente nelle rimanenti posizioni; sommando tutti i bit delle posizioni di controllo (senza riporti) si ottengono i valori per Xi Per il controllo e la verifica: 7. Individuare k cifre binarie dette Gi =1...k formanti il numero binario G, dove ogni Gi è ottenuto come somma dei bit (senza riporto) di controllo per Xi compreso se stesso; l'indice i determina la posizione della cifra binaria in G; 8. Se G=0, il codice non contiene errori, se G≠ 0, esso indica, in binario invertito, la posizione del bit errato.
ESEMPIO
DI CODIFICA DI
HAMMING
Costruire il codice di Hamming per la codifica BCD dei simboli numerici. fase 1. n=4; infatti la codifica BCD ha bisogno di soli 4 bit: 0=0000; 1=0001; 2=0010; 3=0011; ... ;9=1001 fase 2. k=3 (infatti se k = 2, n >22-2-1 cioè 4 > 1, mentre con k = 3, n =23-3-1, cioè 4 = 8-3-1) fase 3. Il codice sarà formato da j bit: j = n+k = 4+3 = 7; posizioni decimali: 1 2 3 4 5 6 7 posizioni binarie: 001 010 011 100 101 110 111 fase 4. X1 X2 B1 X3 B2 B3 B4 - X1 controlla le posizioni 3,5,7 (X1 è in posizione 001; le altre posizioni che hanno quel bit a 1 sono 011 (pos. 3), 101 (pos. 5), 111 (pos. 7).
.doc - 1684Kb - 08/mar/2011 - 26 / 82
S3Abacus – Architetture/Asm
- X2 controlla le posizioni (pos. 3), 110 (pos. 6), 111 - X3 controlla le posizioni (pos. 5), 110 (pos. 6), 111 fase 5. Caso simbolo 1 2 3 X1 X2 0 X1 = 0+0+1 = X2 = 0+1+1 = X3 = 0+1+1 = pertanto: 1 0 0
3,6,7 (pos. 5,6,7 (pos.
(X2 è in posizione 010; le altre posizioni che hanno quel bit a 1 sono 011 7). (X3 è in posizione 100; le altre posizioni che hanno quel bit a 1 sono 101 7).
3=0011 (B1=0; B2=0; B3=1; B4=1) 4 5 6 7 X3 0 1 1 1; 0; 0; 0
0
1
1
Controllo: Ipotesi: spedito 3, ricevuto corretto (1000011) Determino G = G3 G2 G1 (k=3); G1 = bit pos1+ bit pos3+ bit pos5+ bit pos7 = 1+0+0+1 = 0; G2 = bit pos2+ bit pos3+ bit pos6+ bit pos7 = 0+0+1+1 = 0; G3 = bit pos4+ bit pos5+ bit pos6+ bit pos7 = 0+0+1+1 = 0; G=000; allora risultato corretto. Ipotesi: spedito 3, ricevuto sbagliato (1010011) Determino G = G3 G2 G1 (k=3); G1 = bit pos1+ bit pos3+ bit pos5+ bit pos7 = 1+1+0+1 G2 = bit pos2+ bit pos3+ bit pos6+ bit pos7 = 0+1+1+1 G3 = bit pos4+ bit pos5+ bit pos6+ bit pos7 = 0+0+1+1 G=011; allora risultato scorretto con errore in pos 3
= 1; = 1; = 0; (011=3).
CODICI CICLICI - CRC Il metodo dei C o d i c i C i c l i c i (C R C , ovvero C y c l i c R e d u n d a n t C o d e s ) consente di aggiungere a una codifica consueta un numero di bit supplementare i quali sono in grado di rilevare la presenza di eventuali errori. A differenza del controllo di parità, se il controllo CRC del ricevitore ha esito positivo si ha la certezza della correttezza del dato e, come prima, in caso di fallimento del controllo CRC si ha ancora la certezza della scorrettezza del dato. Data quindi una codifica a n bit, se il calcolo del CRC necessita di k bit, si ottiene una codifica totale composta da (n+k) bit. Di seguito vediamo le fasi per determinare la codifica dotata di CRC. Se la sequenza da considerare - cioè la codifica, che chiameremo A - contiene n bit: 1. Si considera una nuova sequenza a piacere di k bit (kdebug -r AX=0000 BX=0000 DS=0CED ES=0CED 0CED:0100 27 -r cx CX 0000 :12 -r AX=0000 BX=0000 DS=0CED ES=0CED 0CED:0100 27 -rf NV UP EI PL NZ NA -rf NV UP EI PL NZ NA -r AX=0000 BX=0000 DS=0CED ES=0CED 0CED:0100 27 -q
CX=0000 DX=0000 SS=0CED CS=0CED DAA
SP=FFEE IP=0100
BP=0000 SI=0000 DI=0000 NV UP EI PL NZ NA PO NC
CX=0012 DX=0000 SS=0CED CS=0CED DAA
SP=FFEE IP=0100
BP=0000 SI=0000 DI=0000 NV UP EI PL NZ NA PO NC
SP=FFEE IP=0100
BP=0000 SI=0000 DI=0000 NV UP EI PL ZR NA PO NC
PO NC
-pe
PE NC
-zr po
CX=0012 DX=0000 SS=0CED CS=0CED DAA
Si nota che oltre ai registri, il comando R mostra sempre l’indirizzo, l’op. code e il codice mnemonico dell’istruzione puntata dal Program Counter (CS:IP). Per quanto riguarda il registro Flags, vengono mostrati solo otto stati di otto bit significativi, con una coppia di caratteri. La decodifica delle coppie di caratteri è la seguente: Flag Overflow Direzione Interruzione Segno Zero Ausiliario Parità Carry
SCRIVERE
Codice Debug OV DN EI NG ZR AC PE CY
bit 1 1 1 1 1 1 1 1
Descrizione si decremento abilitato negativo si si pari si
Codice Debug NV UP DI PL NZ NA PO NC
Bit 0 0 0 0 0 0 0 0
Descrizione no incremento disabilitato positivo no no dispari no
UN PROGRAMMA
La sequenza di passi per scrivere un programma x-86 per Msdos in formato COM con Debug è la seguente: 1. 2. 3. 4. 5.
Comando A – scrivere le istruzioni (e d i t ) Terminare la fase di edit con un invio su una riga vuota Comando R – impostare la lunghezza del programma Comando N – Assegnare il nome al programma Comando W – Salvare il programma su disco
Il comando A deve essere specificato con l’[indirizzo] impostato a 100 (esadecimale), per rispettare la regola di MsDos circa il PSP (cfr. Formato degli eseguibili e Rilocazione): la prima istruzione di un programma .COM deve sempre trovarsi all’indirizzo 100 (h). Quindi si digiti A 100 per cominciare la fase di edit. Ad ogni pressione del tasto Enter (Invio) l’istruzione è confermata e si può scrivere la successiva.
.doc - 1684Kb - 08/mar/2011 - 49 / 82
S3Abacus – Architetture/Asm
Debug riporta sempre l’indirizzo delle istruzioni digitate. Per terminare l’editazione del programma, bisogna premere Enter su una riga vuota. Debug, a questo punto, mostra il prompt (il trattino). Controllando l’indirizzo dell’ultima istruzione scritta, sottraendo l’indirizzo iniziale (sempre 100h), si calcola facilmente la dimensione, in byte, del programma scritto. Questa dimensione va specificata nel registro CX tramite il comando R, nella forma R CX. Si digita la dimensione e la si conferma con Enter. Debug mostra di nuovo il prompt. Ora si può assegnare il nome al programma con il comando N. Il nome deve rispettare le regole per gli identificatori di MsDos, ovvero non può essere più lungo di 8 caratteri e non può contenere caratteri speciali (es. lo spazio), né iniziare con una cifra. Va sempre specificata l’estensione COM. Quindi il comando da utilizzare è il seguente (se si sceglie pippo come nome): N PIPPO.COM. Digitato Enter, Debug mostra di nuovo il prompt (il trattino). Per salvare il programma su disco – nella cartella corrente, basta usare il comando W senza argomenti. Debug risponde con la quantità di byte scritti su disco e mostra il prompt. Ora si può uscire (comando Q) e lanciare il programma dal prompt di MsDos.
IL
PROGRAMMA PIÙ CORTO DEL MONDO
Il programma più corto del mondo per x-86 è un programma eseguibile COM che si termina. Nel dettaglio, tutti i passi da usare con Debug per scrivere questo programma. C:\> C:\>debug -a 100 0CE1:0100 int 20 0CE1:0102 -r cx CX 0000 :2 -n corto.com -w Scrittura di 00002 byte in corso -q
Prompt di MsDos Lancio del programma Debug da Msdos Edit del programma a partire dall’indirizzo 100h Unica riga di codice: terminazione di file .COM Riga vuota: termine dell’edit Comando per l’impostazione della dimensione Visualizzazione del contenuto attuale del registro CX da parte di Debug Impostazione della dimensione (100h-102h=2h) Assegnazione del nome (corto.com) Comando per la scrittura del file di programma su disco Visualizzazione della quantità di byte scritti da parte di Debug Uscita da debug
C:\>corto
Lancio del programma corto.com da MsDos Nessun output del programma Prompt di MsDos
C:\>
Si noti come, su ogni riga digitata, Debug riporti l’indirizzo seg:ofs dell’istruzione, calcolando automaticamente l’ampiezza, in byte, dell’istruzione utilizzata. Il valore della parte segmento dell’indirizzo non è significativo, mentre la parte offset dell’indirizzo è l’esatta posizione in memoria che quell’istruzione avrà nel segmento scelto dal caricatore di MsDos una volta lanciato il programma.
Istruzione NOP Sintassi:
NOP
Scopo:
Non fa nulla. Usata per scopi di servizio, p.e. per allineare una sequenza di istruzioni.
Esempi:
NOP
Nota:
A volte viene inserita NOP se si vuole aumentare di una unità l’indirizzo del codice in esecuzione. In altri casi per rallentare l’esecuzione.
Per verificare il programma scritto ed eventualmente modificarlo, si usano i comandi L e U dopo aver specificato il nome del programma da manipolare. Verrà aggiunta una sola istruzione NOP al codice precedente, per mostrare il modo in cui Debug consente di modificare un programma: C:\> C:\>debug -n corto.com -l -u 100 L2 0D5C:0100 CD20
INT 20
-a 100 0D5C:0100 nop 0D5C:0101 int 20 0D5C:0103 -r cx CX 0002 :3 -w Scrittura di 00003 byte in corso -q C:\>
Prompt di MsDos Lancio del programma Debug da Msdos Assegnazione del nome (corto.com) Caricamento del file da disco (nella cartella corrente) Disassemblaggio, a partire dall’indirizzo 100h, per 2 byte (dimensione del file) Codice disassemblato, con indirizzo (0100h), op. code (CD20h) e codice mnemonico (INT 20) Modifica dell’istruzione all’indirizzo 100h Nuova istruzione (NOP = non fa nulla) Terminazione di file .COM Riga vuota: termine dell’edit Comando per l’impostazione della dimensione Visualizzazione del contenuto attuale del registro CX da parte di Debug Impostazione della dimensione (100h-103h=3h) Comando per la scrittura del file di programma su disco Visualizzazione della quantità di byte scritti da parte di Debug Uscita da debug Prompt di MsDos
In alternativa, il file di programma da disassemblare può essere caricato direttamente citandolo come argomento di Debug: C:\> C:\>debug corto.com -u 100 L2 0D5C:0100 CD20 INT 20
Prompt di MsDos Lancio del programma Debug da Msdos con caricamento del file corto.com
.doc - 1684Kb - 08/mar/2011 - 50 / 82
S3Abacus – Architetture/Asm
-a 100 0D5C:0100 nop 0D5C:0101 int 20 0D5C:0103 -r cx CX 0002 :3 -w Scrittura di 00003 byte in corso -q C:\>
VIDEO
E TASTIERA
Un programma che si limita a richiedere l’immissione di un carattere e a stampare la stringa “CIAO” può essere scritto nel seguente modo: C:\> C:\>debug -a 100 0CED:0100 mov ah,0 0CED:0102 int 16 0CED:0104 mov al,43 0CED:0106 mov ah,0e 0CED:0108 int 10 0CED:010A mov al,49 0CED:010C mov ah,0e 0CED:010E int 10 0CED:0110 mov al,41 0CED:0112 mov ah,0e 0CED:0114 int 10 0CED:0116 mov al,4f 0CED:0118 mov ah,0e 0CED:011A int 10 0CED:011C int 20 0CED:011E -n ciao.com -r cx CX 0003 :1e -w Scrittura di 0001e byte in corso -q C:\>
STRUTTURE
Prompt di MsDos Lancio del programma Debug da Msdos Edit del programma a partire dall’indirizzo 100h Sottofunzione 00h di INT 16h, Input di un carattere da Tastiera Lancio interruzione sw Bios 16h Codice Ascii da stampare (43h = ‘C’) Sottofunzione 0Eh di INT 10h, Stampa carattere sullo Schermo Lancio interruzione sw Bios 10h Codice Ascii da stampare (49h = ‘I’) Sottofunzione 0Eh di INT 10h, Stampa carattere sullo Schermo Lancio interruzione sw Bios 10h Codice Ascii da stampare (41h = ‘A’) Sottofunzione 0Eh di INT 10h, Stampa carattere sullo Schermo Lancio interruzione sw Bios 10h Codice Ascii da stampare (4Fh = ‘O’) Sottofunzione 0Eh di INT 10h, Stampa carattere sullo Schermo Lancio interruzione sw Bios 10h Terminazione di file .COM Riga vuota: termine dell’edit Assegnazione del nome (ciao.com) Comando per l’impostazione della dimensione Visualizzazione del contenuto attuale del registro CX da parte di Debug Impostazione della dimensione (100h-11Eh=1Eh) Comando per la scrittura del file di programma su disco Visualizzazione della quantità di byte scritti da parte di Debug Uscita da debug Prompt di MsDos
DI CONTROLLO
Le principali strutture di controllo utilizzate in assembler x-86, oltre alla sequenza, sono la c o n d i z i o n e (se-allora) e l’i t e r a z i o n e (ripeti n volte), rispettivamente implementate dalle istruzioni di s a l t o c o n d i z i o n a t o (J* indirizzo) e dall’istruzione l o o p (LOOP* indirizzo). Le istruzioni di salto condizionato, che spostano l’esecuzione all’indirizzo in esse specificato, sono numerose, e vanno utilizzate, di norma, subito dopo l’istruzione di confronto C M P , cosicchè i flag del registro omonimo risultano impostati in base al rapporto tra i due operandi dell’istruzione di confronto (che in realtà coincide con una sottrazione). In base allo stato dei singoli flag del registro dei Flags, infatti, le varie istruzioni di salto condizionato effettuano il salto all’indirizzo specificato o meno.
Istruzione CMP Sintassi:
CMP op1, op2
Scopo:
Viene eseguita la sottrazione op1 - op2 e impostati i Flags opportuni in base all’esito della sottrazione.
Esempi: CMP AL,2 CMP AX, BX MOV [BX], AL Nota: L’istruzione viene utilizzata in base alle regole generali della sintassi x-86 (cfr. Sintassi e indirizzamenti). Naturalmente op1 e op2 rimangono invariati dopo la CMP.
Istruzione J* Sintassi:
J* indirizzo
Scopo:
Il flusso dell’esecuzione si sposta su indirizzo se la condizione sui flags previste dalla J* sono verificate, altrimenti il flusso dell’esecuzione prosegue regolarmente in sequenza. Questo gruppo di istruzioni sono dette s a l t i c o n d i z i o n a t i , e si usano spesso dopo l’istruzione CMP per sfruttarne le modifiche di stato dei flag
Esempi: JE 109h JG 110h JB 112h Nota: Se l’esito del confronto CMP precedente ha impostato il flag di Zero, allora i due operandi di CMP sono uguali e la JE salta all’indirizzo 109h. Analogamente per gli altri due casi, se gli operandi sono, rispettivamente, il primo maggiore del secondo (numeri con segno), il primo minore del secondo (numeri senza segno).
.doc - 1684Kb - 08/mar/2011 - 51 / 82
S3Abacus – Architetture/Asm
Ricordare che indirizzo non può rappresentare una differenza maggiore di 128 rispetto all’indirizzo in cui si trova l’istruzione J*. Una tabella di riferimento per consultare velocemente il comportamento delle istruzioni di salto condizionato, è la seguente: Istruzione JE,JZ JNE, JNZ JA,JNBE JAE,JNB,JNC JB,JC,JNAE JBE,JNA JG,JNLE JGE,JNL JL,JNGE JLE,JNG JNO JNP,JPO JNS JO JP,JPE JS
ZeroF 1 0 0
CarryF
1 0
SegnoF
OF
PF
Operatore =
≠ 0 0 1 1
1
= = ≠ ≠ 0 1
= = ≠ ≠ 0
> >= < >= < Lancio del programma Debug da Msdos C:\>debug Edit del programma a partire dall’indirizzo 100h -a 100 Sottofunzione 00h di INT 16h, Input di un carattere da Tastiera 0d53:0100 mov ah,00 Lancio interruzione sw Bios 16h 0d53:0102 int 16 Confronto carattere in input (AL) con carattere zero (30h = ‘0’) 0d53:0104 cmp al,30 Se uguali, salta all’indirizzo 110h ove si stamperà ‘Z’ 0d53:0106 je 0110 Altrimenti si stampa il carattere ‘n’ (6eh = ‘n’) 0d53:0108 mov al,6e Sottofunzione 0Eh di INT 10h, Stampa carattere sullo Schermo 0d53:0100 mov ah,0e Lancio interruzione sw Bios 10h 0d53:010c int 10 Salta alla fine del programma 0d53:010e jmp 0116 Si stampa il carattere ‘Z’ (5ah = ‘Z’) 0d53:0110 mov al,5a Sottofunzione 0Eh di INT 10h, Stampa carattere sullo Schermo 0d53:0112 mov ah,0e Lancio interruzione sw Bios 10h 0d53:0114 int 10 Terminazione di file .COM 0d53:0116 int 20 Riga vuota: termine dell’edit 0d53:0118 Assegnazione del nome -n cmpj.com Comando per l’impostazione della dimensione -r cx Visualizzazione del contenuto attuale del registro CX da parte di Debug CX 001e Impostazione della dimensione (100h-118h=18h) :18 Comando per la scrittura del file di programma su disco -w Visualizzazione della quantità di byte scritti da parte di Debug Scrittura di 00018 byte in corso Uscita da debug -q Prompt di MsDos C:\>
Istruzione INC Sintassi:
INC dest
Scopo:
L’istruzione INC incrementa di una unità dest, che può essere un registro o una locazione di memoria.
Esempi:
INC AX
Nota:
INC [102]
INC DL
Non modifica il registro dei Flags, pertanto se servisse valutarli, si può usare l’equivalente ADD dest,1 (cfr. istruzione ADD). La sua duale è DEC, che ha la stessa sintassi e decrementa di una unità dest.
Istruzione LOOP Sintassi:
LOOP indirizzo
Scopo:
All’esecuzione di LOOP la CPU decrementa di una unità il registro contatore CX; se CX è diverso da zero, il flusso passa all’istruzione posta ad indirizzo, altrimenti il flusso dell’esecuzione prosegue regolarmente in sequenza.
.doc - 1684Kb - 08/mar/2011 - 52 / 82
S3Abacus – Architetture/Asm
Esempi: Nota:
LOOP 110h Naturalmente l’iterazione automatica di LOOP funziona solo se, prima del blocco da ripetere – chiuso da LOOP, si imposta il registro CX con il numero delle iterazioni desiderate. LOOP salta quasi sempre all’indietro, ovvero indirizzo è quasi sempre una locazione di memoria precedente a LOOP, ma seguente all’impostazione di CX.
Ecco un codice per debug che stampa le 26 lettere minuscole dell’elenco alfabetico inglese C:\> C:\>debug -a 100 0d53:0100 mov cx,1a 0d53:0103 mov dl,61 0d53:0105 mov ah,02 0d53:0107 int 21 0d53:0109 inc dl 0d53:010b loop 0105 0d53:010d int 20 0d53:010f -n alfabe.com -r cx CX 0018 :f -w Scrittura di 0000f byte in corso -q C:\>
EDITARE
Prompt di MsDos Lancio del programma Debug da Msdos Edit del programma a partire dall’indirizzo 100h Numero di iterazioni: 26 (1ah = 26) Codice Ascii della a minuscola (61h = ‘a’) Sottofunzione 02h di INT 21h, Stampa carattere sullo Schermo Lancio interruzione sw MsDos 21h Incrementa di una unità il codice Ascii Ripeti CX volte dall’indirizzo 105h, quindi prosegui Terminazione di file .COM Riga vuota: termine dell’edit Assegnazione del nome Comando per l’impostazione della dimensione Visualizzazione del contenuto attuale del registro CX da parte di Debug Impostazione della dimensione (100h-10Fh=Fh) Comando per la scrittura del file di programma su disco Visualizzazione della quantità di byte scritti da parte di Debug Uscita da debug Prompt di MsDos
UN PROGRAMMA
La scrittura di programmi con Debug è complicata, soprattutto per quanto riguarda il calcolo degli indirizzi, ad esempio, per le istruzioni di salto. Per rendere più agevole l’uso di Debug, si può utilizzarlo sfruttando i simboli di ridirezione di MsDos, ottenendo così la possibilità di poter agire su un codice sorgente in un file di testo. La scrittura di un file sorgente per Debug è semplice. •
Avviare la scrittura di un file di testo (es. alfabe.txt) tramite un editor di testo (es. notepad alfabe.txt) C:\>notepad alfabe.txt
• •
Ogni istruzione va riportata su una riga, e la prima riga deve essere sempre il comando di Assemblaggio (es. a 100). Dopodichè vanno specificate le righe di codice, magari scrivendo indirizzi temporanei (es. sempre 100) laddove non è possibile, in un primo tempo, sapere il valore preciso dell’indirizzo da specificare nel programma (p r i m a p a s s a t a ). Il codice va terminato, come al solito, con una riga vuota, quindi si riportano i comandi di Debug soliti per il salvataggio del file eseguibile COM, specificando una dimensione temporanea abbondante (es. 100). Ricordare di lasciare sempre una riga vuota al termine del file.
• •
Editando un file per debug, si può proficuamente dotare il testo di c o m m e n t i , cioè di righe che non verranno prese in considerazione dal programma Debug, e che contengono indicazioni di aiuto alla comprensione del listato. I commenti si possono porre su righe che abbiano come primo carattere il p u n t o e v i r g o l a (;) Il programma precedente, scritto su file di testo (es. alfabe.txt), appare nel seguente modo: a 100 mov cx,1a mov dl,61 mov ah,2 int 21 inc dl loop 100 int 20 n alfabe.com rcx 100 w q •
; 100h = indirizzo temporaneo
Nota: 100h = dimensione temporanea
Per ottenere il file eseguibile (es. alfabe.com), è ora sufficiente usare la seguente ridirezione al prompt di MsDos: C:\>debug < alfabe.txt > alfabe.lst Se il codice è corretto, su disco viene salvato regolarmente il file eseguibile (es. alfabe.com) e un secondo file di tipo l i s t i n g (es. alfabe.lst) che ha la seguente forma: -a 100 0CE2:0100 mov cx,1a 0CE2:0103 mov dl,61 0CE2:0105 mov ah,2
.doc - 1684Kb - 08/mar/2011 - 53 / 82
S3Abacus – Architetture/Asm
0CE2:0107 int 21 0CE2:0109 inc dl 0CE2:010B loop 100 0CE2:010D int 20 0CE2:010F -n alfabe.com -rcx CX 0000 :100 -w Scrittura di 00100 byte in corso -q •
Se nella prima passata si erano riportati indirizzi temporanei, consultando il file di listing si possono agevolmente desumere gli indirizzi reali, nonché la dimensione effettiva del programma. Con una s e c o n d a p a s s a t a sul file sorgente e una nuova operazione di ridirezione, il programma viene messo a punto definitivamente. Ecco come appare dopo la messa a punto: a 100 mov cx,1a mov dl,61 mov ah,2 int 21 inc dl loop 105 int 20
; 105h = indirizzo effettivo, desunto dal file di listing
n alfabe.com rcx f w q •
Nota: fh = dimensione effettiva, desunta dal file di listing
Infine, si ottiene il file eseguibile definitivo (es. alfabe.com) riutilizzando di nuovo la ridirezione al prompt di MsDos: C:\>debug < alfabe.txt > alfabe.lst
L’intera sequenza per editare, mettere a punto e generare il file eseguibile Com a partire da un file di testo TXT è la seguente: C:\> C:\>notepad alfabe.txt alfabe.txt C:\>debug < alfabe.txt > alfabe.lst alfabe.lst da consultare C:\>notepad alfabe.lst punto C:\>debug < alfabe.txt > alfabe.lst alfabe.com C:\>alfabe Abcdefghijklmnopqrstuvwxyz C:\>
AREA DATI
E AREA
OUTPUT ; creazione del file di testo contenente il programma ; prima passata: creazione del file di listing ; apertura del file di listing per consentire la messa a ; seconda passata: creazione del file eseguibile ; esecuzione del programma alfabe.com ; output del programma
CODICE
Naturalmente le API di MsDos forniscono gli strumenti per memorizzare dati singoli (variabili), array di caratteri (stringhe), array di numeri e l’I/O di stringhe sullo schermo e dalla tastiera. L’allocazione di dati in memoria avviene tramite una p s e u d o i s t r u z i o n e , ovvero una indicazione contenuta nel codice affinchè il dato da allocare sia semplicemente posto in memoria – per distinguerlo dall’ op. code di una istruzione. Le pseudoistruzioni non generano alcun codice macchina, ma servono solo per indicare come deve comportarsi un traduttore (nel nostro caso Debug). Le pseudoistruzioni per allocare dati in memoria sono:
PseudoIstruzione D* Sintassi:
D*
Scopo:
Alloca il dato all’indirizzo corrente
Esempi:
DB 0
Nota:
dato
DB 41h
DB ‘A’
DB ?
DB 10 DUP (0)
Nel primo caso viene allocato un byte in memoria (0), nel secondo un byte che rappresenta il codice Ascii della A maiuscola, nel terzo caso un modo equivalente al secondo e nel terzo si allocano due byte contigui. Nel caso DB ? si alloloca un byte senza inizializzarlo (il valore in quella cella sarà casuale), mentre in DB 10 DUP (0) si indica l’allocazione di 10 byte tutti inizializzati a 0. E’ disponibile anche DW (alloca due byte) DD (4 byte) DQ (8 byte) e DT (10 byte), queste ultime spesso usate per memorizzare numeri in virgola fissa per i calcoli con il coprocessore matematico.
S3Abacus – Architetture/Asm
.doc - 1684Kb - 08/mar/2011 - 54 / 82
Ovviamente l’area di memoria destinata a contenere i dati (variabili e array) non deve essere eseguita come codice. Essa è detta a r e a D a t i , e deve essere separata dall’a r e a C o d i c e , che contiene le istruzioni da eseguire. Nel modello COM, area Dati e area Codice risiedono nello stesso segmento, cioè in locazioni di memoria contigue. Se, come spesso si opta, l’area Dati viene posta all’inizio della zona della memoria del programma, è necessario porre una istruzione iniziale di salto incondizionato per saltare l’area Dati e avviare correttamente l’area Codice. In altri casi si può optare allocando l’area Dati immediatamente dopo l’area di Codice. Si veda il seguente esempio, in cui si allocano 8 byte contenenti i codici Ascii della stringa ‘HAL’, per stamparli sullo schermo: a 100 jmp 105 db 'H' db 'A' db 'L' mov bx,102 mov cx,3 mov dl,[bx] mov ah,2 int 21 inc bx loop 10b int 20
; Istruzione iniziale per saltare l’area dati e indirizzare la prima istruzione dell’area codice (mov bx,102) ; Area Dati. Si allocano tre byte, I tre codici Ascii della stringa ‘HAL’ ; ; ; ; ; ; ;
Area Codice. In BX l’indirizzo del primo byte dell’area Dati Contatore del ciclo a 3 (3 caratteri da stampare) Indirizzamento indiretto. In DL il codice Ascii che si trova in area Dati all’indirizzo specificato in BX Sottofunzione 02h di MsDos, stampa carattere Interruzione sw MsDos Incremento dell’indirizzo in area Dati: La prossima cella contiene il prossimo codice Ascii da stampare Iterazione a partire dall’istruzione mov dl,[bx]
n hal.com rcx 16 w q OUTPUT C:\>hal HAL C:\> Le tre locazioni di memoria così allocate, possono ospitare a tutti gli effetti delle variabili. Infatti in quelle locazioni i dati possono essere cambiati a runtime per memorizzare altri valori. Nell’esempio, le tre locazioni vengono manipolate, aggiungendo una unità ad ogni cella, ottenendo i tre cosici Ascii della stringa ‘IBM’, che poi verrà stampata a schermo: a 100 jmp 105 db 'H' db 'A' db 'L' mov bx,102 mov cx,3 mov al,[bx] inc al mov [bx],al inc bx loop 10b mov bx,102 mov cx,3 mov dl,[bx] mov ah,2 int 21 inc bx loop 11a int 20
; ; ; ; ; ; ; ;
contatore a 3 (3 locazioni di memoria da manipolare) lettura della variabile (locazione di memoria puntata da BX) incremento di una unità salvataggio della variabile (nella locazione di memoria puntata da BX) prossima variabile in memoria iterazione indirizzo della prima locazione di memoria, per stampare a schermo contatore a 3 (3 stampe da effettuare)
n ibm.com rcx 25 w q
OUTPUT
C:\>ibm IBM C:\>
ALLINEAMENTO Quando si alloca l’area Dati prima dell’area Codice, non sempre la prima istruzione di codice può essere collocata immediatamente dopo l’ultimo byte dell’area Dati. Infatti il microprocessore interpreta i dati come codice e raggruppa i byte dei dati in base alle combinazioni di op. code che essi formano ‘inconsapevolmente’. Per evitare questa situazione, che impedisce l’avvio del codice, bisogna allineare i dati, ovvero aggiungere in coda all’area Dati alcuni byte che consentano alla CPU di interpretare correttamente la prima istruzione di codice.
.doc - 1684Kb - 08/mar/2011 - 55 / 82
S3Abacus – Architetture/Asm
Si veda questa situazione nel codice che segue, che effettua l’input di tre caratteri, proponendo un prompt a forma di punto interrogativo, e indica a video il maggiore con la stringa ‘il max vale:‘. In una prima versione, i dati della stringa causano il disallineamento del codice: C:\>debug -u 100 0D54:0100 0D54:0102 0D54:0103 0D54:0104 0D54:0107 0D54:0109 0D54:010B 0D54:010C 0D54:010D 0D54:0111 0D54:0112 0D54:0114 0D54:0116
m3.com EB0A 49 6C 206D61 7820 7661 6C 65 3AB402B2 3F CD21 B401 CD21
JMP DEC DB AND JS JBE DB DB CMP AAS INT MOV INT
010E CX 6C [DI+61],CH 0129 016C 6C 65 DH,[SI+B202] 21 AH,01 21
JMP 10E 49 (=’i’) 6C (=’l’) 20 (=’ ‘) 6D (=’m’) 61 (=’a’) 78 (=’x’) 20 (=’ ‘) 76 (=’v’) 61 (=’a’) 6C (=’l’) 65 (=’e’) 3A (=’:’) B402 (=MOV AH,02) B2 3F (=MOV AL,3F) CD21 INT 21 B401 MOV AH,01 CD21 INT 21
Come si può notare, la sequenza di byte allocati in memoria che rappresentano la stringa ‘il max vale:’, non vengono correttamente allineati. Infatti all’indirizzo seg:010D il byte 3A (il codice Ascii dei due punti) viene compattato dell’op. code dell’istruzione CMP DH,[SI+B202], inglobando la prima effettiva istruzione di codice (MOV AH,02) e facendo fallire il salto iniziale ad essa (JMP 10E). Aggiungendo un solo byte al termine dell’area Dati, con la pseudoistruzione DB 0, il codice viene riallineato e il salto incondizionato deve essere aggiornato con una unità in più: C:\>debug -u 100 0D54:0100 0D54:0102 0D54:0103 0D54:0104 0D54:0107 0D54:0109 0D54:010B 0D54:010C 0D54:010D 0D54:010F 0D54:0111 0D54:0113 0D54:0115 0D54:0117
m3.com EB0A 49 6C 206D61 7820 7661 6C 65 3A00 B402 B23F CD21 B401 CD21
JMP DEC DB AND JS JBE DB DB CMP MOV MOV INT MOV INT
010F CX 6C [DI+61],CH 0129 016C 6C 65 AL,[BX+SI] AH,02 DL,3F 21 AH,01 21
JMP 10F 49 (=’i’) 6C (=’l’) 20 (=’ ‘) 6D (=’m’) 61 (=’a’) 78 (=’x’) 20 (=’ ‘) 76 (=’v’) 61 (=’a’) 6C (=’l’) 65 (=’e’) 3A (=’:’) 00 (=per l’allineamento) B402 MOV AH,02 B23F MOV AL,3F CD21 INT 21 B401 MOV AH,01 CD21 INT 21
Il programma completo: a 100 JMP 10f DB 'Il max vale:' db 0 MOV AH,02 MOV DL,3f INT 21 MOV AH,01 INT 21 MOV BX,10e MOV [BX],AL MOV AH,02 MOV DL,3f INT 21 MOV AH,01 INT 21 CMP AL,[BX] JL 12e MOV [BX],AL MOV AH,02 MOV DL,3f INT 21 MOV AH,01 INT 21 CMP AL,[BX] JL 13e MOV [BX],AL MOV CX,0d contiene il max MOV AH,02
; ; ; ; ; ; ; ; ; ; ;
Si salta l’area Dati per arrivare all’istruzione MOV AH,02 area Dati byte di allineamento. Usato anche come variabile per contenere il massimo sottofunzione per la stampa di un carattere codice Ascii del punto interrogativo (il prompt prima dell’input) Interruzione sw Msdos stampa un carattere sottofunzione per l’input di un carattere da tastiera Interruzione sw Msdos in BX l’indirizzo del byte di memoria che fungerà da variabile (il byte di allineamento) memorizzazione dell’input nella variabile secondo prompt per il secondo input
; secondo input ; ; ; ;
confronto tra l’input e la variabile in memoria se inferiore, non memorizzo (salto all’istruzione MOV AH,02) altrimenti salvo nella variabile il nuovo massimo terzo prompt per il terzo input
; terzo input ; ; ; ;
confronto tra l’input e la variabile in memoria se inferiore, non memorizzo (salto all’istruzione MOV CX,0d) altrimenti salvo nella variabile il nuovo massimo contatore a 13 (0dh=13) per i 12 caratteri della stringa da stampare più il byte della variabile che
; sottofunzione di stampa carattere
S3Abacus – Architetture/Asm
MOV BX,102 MOV DL,[BX] INT 21 INC BX LOOP 146 INT 20
; ; ; ; ;
.doc - 1684Kb - 08/mar/2011 - 56 / 82
indirizzo iniziale dei caratteri da stampare in DL il carattere da stampare, prelevato dalla memoria interruzione MsDos prossimo indirizzo per il prossimo carattere iterazione dalla istruzione MOV AH,02
n m3.COM rcx 4f w q C:\>m3 ?1?5?3Il max vale:5 C:\>m3 ?6?4?3Il max vale:6 C:\>m3 ?1?2?9Il max vale:9 C:\>
OUTPUT
S3Abacus – Architetture/Asm
.doc - 1684Kb - 08/mar/2011 - 57 / 82
ASSEMBLY CON TASM Naturalmente la programmazione dell’assembler x-86 con Debug ha senso solo per ragioni didattiche. Nella realtà, aldilà di brevi frammenti di codice di test, Debug non viene utilizzato per scrivere programmi, e i programmatori si affidano a veri e propri ambienti di sviluppo su linea di comando, i più noti dei quali sono T A S M (Borland) e M A S M (Microsoft). Un ambiente freeware molto apprezzato è anche N A S M , in ogni caso tutti prodotti ormai facilmente reperibili senza alcun costo. La scelta di TASM deriva dal fatto che la sintassi MASM è correttamente interpretata da TASM, mentre non è vero il viceversa. Come si è verificato utilizzando Debug, la principale difficoltà nello scrivere programmi in assembler è la gestione degli indirizzi e il loro ricalcolo ad ogni modifica del programma. Inoltre tali ambienti offrono la possibilità di utilizzare decine di pseudoistruzioni e direttive che rendono la programmazione assembler molto efficace e veloce. La gestione semplificata degli indirizzi viene ottenuta da questi ambienti tramite l’uso delle e t i c h e t t e (label) al posto degli indirizzi numerici, e del concetto di d o p p i a p a s s a t a dell’assemblatore, che è il programma che analizza il file sorgente contenente il codice assembler e le pseudoistruzioni. Con la doppia passata l’assemblatore traduce correttamente tutte le etichette nei corrispondendi indirizzi numerici senza che il programmatore debba più preoccuparsene, cosicchè nei files sorgenti scritti per questi ambienti, l’uso degli indirizzi numerici è praticamente abolito. L’etichetta, inoltre, rende il codice molto più comprensibile, dato che gli identificatopri di etichetta sono scelti dal programmatore e possono descrivere, tramite il loro nome, la funzione svolta dall’indirizzo simbolico che rappresentano.
STRUTTURA
DEI PROGRAMMI
Come si è visto in precedenza, i programmi contengono, generalmente, almeno due aree distinte di istruzioni: l’area del Codice e l’area Dati. Questo vale per qualsiasi programma scritto in qualsiasi linguaggio. Oltre a queste due aree, i programmi ne contengono altre, altrettanto impostanti: a r e a d i S t a r t u p , a r e a d e l l o S t a c k e a r e a dello Heap Area di Startup E’ la zona iniziale di ogni programma, generata da un programma speciale denominato l i n k e r (correlatore) che si incarica di scrivere le prime istruzioni da eseguire subito dopo che il Sistema Operativo avvia il processo a partire dal programma su disco. Questo codice deve essere coerente con le regole di esecuzione del Sistema Operativo, da cui riceve il controllo, e deve essere in grado di avviare la prima istruzione eseguibile scritta dal programmatore (e n t r y p o i n t ) in modo corretto, fornendo i dati di avvio ereditati dal Sistema Operativo, come ad esempio i p a r a m e t r i s u r i g a d i c o m a n d o . Solo i files eseguibili .COM non possiedono un’area di Startup. Area di Codice E’ la zona del programma che contiene le istruzioni da eseguire durante l’esecuzione, cioè durante il r u n t i m e del programma. L’area di codice è scritta espressamente dal programmatore tramite le regole della sintassi.di un linguaggio di programmazione, nel nostro caso le regole della programmazione assembler x-86. Essa si trova immediatamente dopo l’area di Startup, dalla quale eredita il controllo all’avvio dell’esecuzione del programma. Area Dati E’ la zona in cui il programmatore alloca i dati tramite istruzioni presenti nell’area Codice. Qui trovano posto le v a r i a b i l i g l o b a l i s t a t i c h e dei programmi, cioè quelle locazioni di memoria disponibili durante tutto il runtime. Questa area è sia di lettura che di scrittura, a differenza dell’area di Codice e di Startup che sono esclusivamente aree di lettura. Area dello Heap E’ una zona opzionale, in cui durante il runtime, il programmatore, tramite istruzioni ben precise, alloca temporaneamente della memoria per far posto a variabili la cui dimensione è accertabile solo durante l’esecuzione (es. la dimensione di una stringa in input). E’ anche detta m e m o r i a d i n a m i c a , dato che la sua dimensione non è prefissata e può essere anche allocata e deallocata più volte durante il runtime. La gestione dell’area di Heap viene ottenuta tramite istruzioni in area Codice, che richiedono i servizi di allocazione/deallocazione al Sistema Operativo. Area dello Stack E’ una zona gestita automaticamente dai compilatori su tipiche tecniche di programmazione scritte in area di Codice dal programmatore, come la dichiarazione di v a r i a b i l i l o c a l i e il p a s s a g g i o d i p a r a m e t r i a l l e p r o c e d u r e : a fronte di questi costrutti, i compilatori gestiscono l’area di Stack in modo trasparente al programmatore, allocando e deallocando le variabili locali e i parametri passati alle procedure. Solo nella programmazione in Assembler è possibile gestire direttamente l’area di Stack con opportune istruzioni.
CICLO
DI VITA DI UN PROGRAMMA
La creazione, lo sviluppo, l’esecuzione e la messa a punto di un programma, generalmente, segue un certo numero di fasi caratterizzate da attività specifiche, tempi specifici, applicazioni di supporto specifiche, errori specifici e file specifici. E’ possibile sintetizzare il ciclo di vita di un programma tramite un diagramma e una tabella che riportano fasi, tempi, applicazioni, files ed errori relativi ad ogni fase.
.doc - 1684Kb - 08/mar/2011 - 58 / 82
S3Abacus – Architetture/Asm
EDIT
ASSEMBLAGGIO
DESIGN
ASSEMBLING
DesignTim e Editor Sorgenti
MAIN.ASM LIB1.ASM LIB2.ASM
CORRELAZIONE Von Neumann
CARICAMENTO
ESECUZIONE
MESSA A PUNTO
LOADING
RUNTIME
DEBUGGING
LINKING
Com pileTim e Assem blatore Linker Oggetto Eseguibile
MAIN.O BJ LIB1.OBJ LIB2.OBJ
LoadTime SistemaOp erativo Processo
RunTim e
DebugTime Debugger
MAIN.EXE
Fase di Edit Il programmatore scrive il testo del programma (sorgente) con la sintassi di un linguaggio di programmazione. Spesso i programmi sono costituiti da più sorgenti, ma solo uno contiene l’e n t r y p o i n t del programma. I rimanenti sono denominati l i b r e r i e d i c o d i c e . Il programma usato in questa fase è un E d i t o r , spesso integrato in un I D E (Integrated Development Equipment). L’attività del programmatore in questa fase è detta D e s i g n T i m e . Gli errori più frequenti a Design Time riguardano il formato dei files (es. i files devono essere rigorosamente files di testo), la loro irreperibilità o la loro corruzione. Fase di Compilazione Una volta completato un modulo sorgente, esso deve essere assemblato, ovvero le istruzioni e le pseudoistruzioni presenti nei sorgenti in linguaggio simbolico ad alto livello, devono essere trasformate in assembler, a basso livello. Ogni file sorgente quindi viene ridotto, da un programma di supporto denominato a s s e m b l a t o r e , a un file binario corrispondente (detto anche file o g g e t t o ). Dopo l’assemblaggio, è necessaria la c o r r e l a z i o n e o l i n k i n g , ad opera di un secondo programma a supporto, denominato l i n k e r . Il linker collega tutti i files oggetto in uno solo, e genera il file eseguibile (t a r g e t della compilazione), aggiungendovi, nella sua parte iniziale, la porzione di Startup (cfr. Struttura dei programmi) e l’eventuale Header (cfr. Formato degli eseguibili e Rilocazione). Le due fasi di assemblaggio e linking sono spesso riunite in un unico passo, denominato C o m p i l e T i m e . Durante il Compile Time si possono verificare i tipici errori di sintassi (del linguggio scelto) che sono sempre segnalati dai programmi compilatori, sottoforma di errori e/o warning. Fase di Caricamento Una volta su disco, il programma eseguibile deve essere caricato in memoria dal Sistema Operativo per essere poi trasformato in processo. Il SO legge lo header del file eseguibile e carica in memoria il programma, dopodichè cede il controllo al codice di Startup dell’eseguibile. Spesso i SO creano una zona di memoria di collegamento tra programma e Sistema Operativo (cfr. PSP in Formato degli eseguibili e Rilocazione) prima di cedere il controllo. Tipici errori della fase di L o a d T i m e sono errati formati degli header, o l’impossibilità del caricamento per scarsità di memoria. Fase di Esecuzione Il R u n t i m e è il tempo durante il quale il processo opera in CPU, dalla prima istruzione di codice all’ultima prevista dal programmatore. Tipici errori di RunTime sono le divisioni per zero, i loop infiniti, le terminazioni anomale per mancanza o incongruenza delle risorse richieste dal programma. Fase di messa a Punto Il Runtime può anche essere avviato tramite un programma speciale, denominato d e b u g g e r . In questo caso il debugger carica ed esegue il programma nelle modalità impostate dal programmatore, ad esempio p a s s o p a s s o (per verificare il flusso dell’esecuzione) o tramite b r e a k p o i n t , ovvero sospensioni dell’esecuzione su istruzioni critiche, per eplorare lo stato di registri, variabili e memoria (w a t c h ). Tutto ciò al fine di individuare le cause di eventuali malfunzionamenti riscontrati al Runtime.
CICLO
DI VITA DI UN PROGRAMMA
TASM
Allo stesso modo, l’ambiente di sviluppo TASM prevede le fasi tipiche del ciclo di vita di un programma, fornendo i relativi programmi di supporto. Bisogna tener presente che, malgrado si sia scelto di sviluppare direttamente in Assembler, e quindi in apparenza potrebbe sembrare inutile una fase di compilazione, l’uso delle fondamentali due passate per risolvere il problema degli indirizzi numerici, rende necessaria la presenza di un programma assemblatore e un programma linker. I sorgenti per TASM possono essere scritti con un qualsiasi editor di testo (es. Notepad di Windows), avendo cura di usare sempre l’estensione .ASM per ogni modulo sorgente; il programma assemblatore si chiama TASM.EXE e il programma linker si chiama TLINK.EXE. Il programma debugger, infine, si chiama Turbo Debugger (TD.EXE). In particolare, per usare TASM, sono sufficienti i seguenti files, reperibili anche all’interno delle cartelle dei compilatori Borland C 3.1 e/o Turbo Pascal 7.0: TASM.EXE, TLINK.EXE, TD.EXE, DPMILOAD.EXE, DPMIMEM.DLL Limitandoci per ora a programmi composti da un unico file sorgente (es. PIPPO.ASM), scritto per ottenere un eseguibile di tipo COM (es. PIPPO.COM), assemblaggio, linking e debugging si avviano nei seguenti modi:
.doc - 1684Kb - 08/mar/2011 - 59 / 82
S3Abacus – Architetture/Asm
; l’assemblatore tasm.exe genera il file oggetto pippo.obj dal file
C:\>tasm pippo sorgente pippo.asm C:\>tlink pippo /t pippo.obj C:\>pippo l’esecuzione… C:\>td pippo.com la messa a punto
; il linker tlink.exe genera il file eseguibile pippo.com dal file oggetto ; MsDos carica in memoria il file eseguibile pippo.com per ; … oppure il debugger td.exe carica il file eseguibile pippo.com per
Si noti come tasm e tlink non abbiano bisogno che venga specificata l’estensione dei file in ingresso, rispettivamente pippo.asm per tasm, pippo.obj per tlink. La fase di correlazione operata da tlink viene eseguita con il parametro /t per indicare a tlink di non creare lo header del file. Infatti gli eseguibili di tipo COM sono gli unici che mancano sia di Header che di area di Startup. Se il file sorgente contiene errori di sintassi, l’assemblatore mostra un messaggio esplicito che indica il tipo di errore e la riga su cui è stato rilevato. E’ ovvio che in presenza di errori, il file oggetto non viene creato, interrompendo la fase di creazione del file eseguibile. Allo stesso modo, ma più raramente, il linker mostra un messaggio d’errore nel caso di incongruenza nella correlazione.. Nell’esempio, una sequenza corretta di creazione e lancio di un programma assembler con TASM (il programma che calcola il massimo di tre caratteri, riscritto per TASM): OUTPUT C:\>tasm m3 Turbo Assembler Version 3.1 Copyright (c) 1988, 1992 Borland International Assembling file: Error messages: Warning messages: Passes: Remaining memory:
m3.asm None None 1 452k
C:\>tlink m3 /t Turbo Link Version 5.1 Copyright (c) 1992 Borland International C:\>m3 ?1?5?3Il max vale:5 C:\> In quest’altro caso, una esempio che riporta una tipica videata di errore riscontrato durante l’assemblaggio, causato da un errato uso dell’istruzione MOV DL,0a3fh (in DL possono essere memorizzati solo byte, non word): C:\>tasm m3 Turbo Assembler
OUTPUT Version 3.1
Copyright (c) 1988, 1992 Borland International
Assembling file: m3.asm **Error** m3.asm(22) Constant too large Error messages: 1 Warning messages: None Passes: 1 Remaining memory: 452k C:\> Si noti che l’errore si trova sulla riga 22 del codice sorgente, come indicato dal messaggio d’errore. Infine, una tipica videata del debugger TD, applicato al file m3.com: C:\>td m3.com
OUTPUT
File Edit View Run Breakpoints Data Options Window Help READY ╔═[■]═CPU 80486═══════════════════════════════════════════════╤═══════1════[↕]═╗ ║ cs:0100►EB0E jmp 0110 ↓ │ ax 0000 │c=0║ ║ cs:0102 90 nop │ bx 0000 │z=0║ ║ cs:0103 49 dec cx │ cx 0000 │s=0║ ║ cs:0104 6C insb │ dx 0000 │o=0║ ║ cs:0105 206D61 and [di+61],ch │ si 0000 │p=0║ ║ cs:0108 7820 js 012A │ di 0000 │a=0║ ║ cs:010A 7661 jbe 016D │ bp 0000 │i=1║ ║ cs:010C 6C insb │ sp FFFE │d=0║ ║ cs:010D 653A00 cmp al,gs:[bx+si] │ ds 547F │ ║ ║ cs:0110 B402 mov ah,02 │ es 547F │ ║ ║ cs:0112 B23F mov dl,3F │ ss 547F │ ║ ║ cs:0114 CD21 int 21 │ cs 547F │ ║ ║ cs:0116 B401 mov ah,01 │ ip 0100 │ ║ ║ cs:0118 CD21 int 21 │ │ ║ ║ cs:011A BB0F01 mov bx,010F │ │ ║ ╟─────────────────────────────────────────────────────────────┼────────────┴───╢ ║ ds:0100 EB 0E 90 49 6C 20 6D 61 Ù♫ÉIl ma ▲ ss:0000 20CD ║
.doc - 1684Kb - 08/mar/2011 - 60 / 82
S3Abacus – Architetture/Asm
║ ds:0108 78 20 76 61 6C 65 3A 00 x vale: ■ ss:FFFE►0000 ║ ║ ds:0110 B4 02 B2 3F CD 21 B4 01 ┤☻▓?═!┤☺ ▒ ss:FFFC 0000 ║ ║ ds:0118 CD 21 BB 0F 01 88 07 B4 ═!╗☼☺ê•┤ ▒ ss:FFFA 0000 ║ ║ ds:0120 02 B2 3F CD 21 B4 01 CD ☻▓?═!┤☺═ ▼ ss:FFF8 0000 ║ ╚═════════════════════════════════════════════════════════════╧═══════════════─┘ F1-Help F2-Bkpt F3-Mod F4-Here F5-Zoom F6-Next F7-Trace F8-Step F9-Run F10-Menu C:\>
SCRIVERE
UN PROGRAMMA
EXE
Come si è visto, l’assembler x-86 prevede che I programmi siano sviluppati, attraverso le aree tipiche di un programma quali area di Codice, area Dati e area di Stack, all’interno di aree di memoria contenute in segmenti, cioè zone di memoria ampie 64 Kbytes. Per gli eseguibili di tipo COM, tutte le tre aree tipiche risiedono nello stesso segmento, che è anche l’unico segmento possibile per questo tipo di programmi; inoltre gli eseguibili di tipo COM non possiedono l’area di Startup, essendo caricati in memoria dal loader di Sistema Operativo con caricamento rilocante statico (cfr. Formato degli eseguibili e Rilocazione: EXE e COM). Gli eseguibili di tipo EXE, i più comuni e diffusi, hanno invece la necessità che le tre aree siano assegnate a rispettivi segmenti in memoria, potendo, anche in questo caso, essere lo stesso segmento di memoria, ma tuttavia con la necessità di essere esplicitati espressamente nel codice così da poter permettere il caricamento rilocante dinamico. Ecco allora come si presenta un generico programma sorgente x-86 per MsDos destinato a diventare un eseguibile di tipo EXE (il solito programma per il calcolo del massimo tra tre caratteri in input). Il codice presenta numerose e t i c h e t t e (in blu, per evitare l’uso di indirizzi numerici) e p s e u d o i s t r u z i o n i (in verde, per indicare all’assemblatore le intenzioni del programmatore). SEG_DATI segment
; definizione del segmento per l’area Dati
MSG DB "Il max vale:" VAR DB 0 ends SEG_STACK segment stack
; definizione del segmento per l’area di Stack
ends SEG_CODICE segment
; definizione del segmento per l’area Codice
assume cs:SEG_CODICE, ss:SEG_STACK, ds:SEG_DATI, es:SEG_DATI
; associazione dei nomi dei segmenti ai registri di segmento
START:
; etichetta che segnala l’inizio dell’area Codice ; impostazioni a runtime dei valori di segmento ai registri di segmento
mov mov mov mov mov mov mov int mov int lea mov mov mov int mov int cmp jl mov SALTA1: mov mov int mov int cmp jl mov SALTA2:
ax, SEG_DATI ds,ax es, ax ax, SEG_STACK ss, ax ah,02 dl,3fh 21h ah,01 21h bx,VAR [bx],al ah,02 dl,3fh 21h ah,01 21h al,[bx] SALTA1 [bx],al ah,02 dl,3fh 21h ah,01 21h al,[bx] SALTA2 [bx],al
; impostazioni a runtime dei valori di segmento ai registri di segmento ; codice del programma
; riferimento di una etichetta contenente un indirizzo di memoria in area Dati
; riferimento di una etichetta contenente un indirizzo di memoria in area Codice
.doc - 1684Kb - 08/mar/2011 - 61 / 82
S3Abacus – Architetture/Asm
mov cx,0dh lea bx,MSG CICLO:
mov ah,02 mov dl,[bx] int 21h inc bx loop CICLO mov ax, 4c00h int 21h
ends ; chiusura dell’etichetta che segnalava l’inizio dell’area Codice e chiusura del file
end START
Le tre etichette principali SEG_DATI, SEG_STACK e SEG_CODICE danno il nome ai tre segmenti di memoria di questo programma, segmenti inizializzati dalla pseudoistruzione segment/ends. In effetti questo programma non usa una area dello Stack, quindi in linea di principio non sarebbe necessario specificarla. Nell’area Codice, infine, la pseudoistruzione assume cs:,ds:,ss:,es: consente di associare le tre aree ai registri di segmento appropriati. Si noti come i registro Extra (ES) sia impostato sullo stesso valore del registro dati DS. Infine, come istruzioni effettive, bisogna impostare i registri di segmento con i valori indicati tramite le pseudoistruzioni. Si noti come non sia possibile impostare un registro di segmento direttamente, ma solo tramite un registro d’appoggio (in questo caso AX). La direttiva end a fine file è necessaria affinchè l’assemblatore sappia con precisione quando il file sorgente è terminato. Molto interessante l’uso delle etichette VAR, MSG, SALTA1, SALTA2 e CICLO esse consentono di evitare l’uso di indirizzi numerici, lasciando il compito di calcolarli adeguatamente all’assemblatore. Le etichette che segnalano indirizzi all’interno dell’area codice sono dette l a b e l , e devono terminare con i due punti (:). Quando le etichette sono usate nel codice, invece, si parla di r i f e r i m e n t o all’etichetta. Attenzione A. Il nome di fantasia di una label può apparire una sola volta nel codice (mentre i riferimenti sono liberi), altrimenti l’assemblatore non saprebbe a quale indirizzo associarne il nome. B. La distanza tra l’etichetta e ogni suo riferimento tramite un salto condizionato rimane vincolata a un massimo di 128 byte. Ciò deriva dal limite progettuale delle istruzioni di salto condizionato. Entrambi questi limiti possono essere risolti dall’assemblatore attraverso speciali direttive LOCALS e JUMPS (cfr. Direttive per la programmazione e Librerie) Infine, l’etichetta START, con la sua chiusura a fine sorgente, è necessaria affinchè l’assemblatore possa considerare terminata l’area di Codice del programma (si tratta di una specifica che indica il termine del modulo principale che contiene il punto di ingresso dell’area Codice di un programma) Le etichette sono identificativi ideati dal programmatore, ovvero nomi di fantasia. Esse non possono né cominciare con un numero, né riportare spazi o caratteri speciali. Va da sé che il programmatore usi nomi significativi per esse, magari in base a indicazioni precise che vengono dette regole di naming. La compilazione di un sorgente destinato a diventare un file eseguibile di tipo EXE: C:\>tasm pippo sorgente pippo.asm C:\>tlink pippo pippo.obj
MODELLI
; l’assemblatore tasm.exe genera il file oggetto pippo.obj dal file ; il linker tlink.exe genera il file eseguibile pippo.exe dal file oggetto
DI MEMORIA
Questo sorgente genera quindi un file EXE in cui area Dati, area Codice e area dello Stack risiedono su tre segmenti separati. Questo programma, pertanto, potrà essere ampio al più 64KB + 64KB = 128 KB, di cui 64 KB riservati al codice, e 64KB riservati ai dati. Per superare questi limiti, il programmatore deve usare in modo spesso anche complesso le direttive segment/ends e assume all’interno del codice, così da ottenere programmi che possano operare su più segmenti di codice e più segmenti di dati. Per evitare la gestione esplicita di queste direttive di segmento, l’assemblatore TASM mette a disposizione una pseudoistruzione molto efficace che semplifica l’uso delle direttive di segmento: con la pesudoistruzione M O D E L , infatti, il programmatore può decidere il modello di memoria desiderato per il proprio programma, senza più preoccuparsi di gestire i segmenti.
Direttiva
MODEL
Sintassi:
.MODEL tipo
Scopo:
L’assemblatore regola la generazione dei segmenti di Codice, Dati e Stack in base al tipo indicato T I N Y , un solo segmento comune per area Codice, Dati e Stack. Dedicato ai programmi eseguibili di tipo COM S M A L L , un segmento per area Codice, un segmento per area Dati e Stack, solo per eseguibili di tipo EXE M E D I U M , più segmenti per l’area Codice, un solo segmento per area Dati e area Stack. C O M PA C T , più segmenti per l’area Dati, un solo segmento per area Codice e area Stack. L A R G E , più segmenti per l’area Codice, più segmenti per l’area di Dati, più segmenti per l’area di Stack. H U G E , come LARGE, con la possibilità che un dato contiguo possa essere maggiore di un segmento (es. un array > 64KB).
Esempi:
.MODEL TINY
.MODEL SMALL
.MODEL LARGE
.doc - 1684Kb - 08/mar/2011 - 62 / 82
S3Abacus – Architetture/Asm
Nota:
In realtà esiste un sesto modello di memoria, denominato FLAT, che non prevede segmentazione e usato solo nelle piattaforme a 32bit.
Ecco come diventa lo stesso sorgente di poco fa in sintassi semplificata: ; direttiva di segmento per il modello di memoria desiderato ; direttiva per la definizione del segmento dell’area di Stack ; direttiva per la definizione del segmento dell’area Dati
.MODEL SMALL .STACK .DATA MSG DB "Il max vale:" VAR DB 0
; direttiva per la definizione del segmento dell’area Codice
.CODE mov ax,@DATA mov ds,ax mov ah,02 mov dl,3fh int 21h mov ah,01 int 21h lea bx,VAR mov [bx],al mov ah,02 mov dl,3fh int 21h mov ah,01 int 21h cmp al,[bx] jl SALTA1 mov [bx],al SALTA1: mov ah,02 mov dl,3fh int 21h mov ah,01 int 21h cmp al,[bx] jl SALTA2 mov [bx],al SALTA2: mov cx,0dh lea bx,MSG CICLO: mov ah,02 mov dl,[bx] int 21h inc bx loop CICLO mov ax, 4c00h int 21h end
; impostazioni a runtime del valore di segmento per l’area Dati ; codice del programma
; uso di una etichetta contenente un indirizzo di memoria in area Dati
; uso di una etichetta contenente un indirizzo di memoria in area Codice
Le direttive .MODEL, .DATA, .STACK e .CODE sono dette d i r e t t i v e d i s e g m e n t o s e m p l i f i c a t e , e rendono i sorgenti assembly molto più semplici da gestire, evitando al programmatore lo sforzo di definire i vari segmenti del programma in modo esplicito. L’unica accortezza da ricordare è il caricamento esplicito del segmento Dati tramite l’etichetta di sistema @DATA
SCRIVERE
UN PROGRAMMA
COM
Per imparare la programmazione assembly x-86 è più che sufficiente sviluppare programmi eseguibili di formato COM, che sono anche più semplici nella struttura, evitando di usare pseudoistruzioni e istruzioni per la definizione e il caricamento dei registri di segmento. Spesso, infatti, anche i programmi eseguibili di formato EXE vengono ridotti a COM, per sfruttarne la semplicità e la velocità di caricamento, con un apposito applicativo del SO, denominato E X E 2 B I N . Ecco i codici sorgenti del solito programma che calcola il massimo di tre caratteri in input, nella versione con direttive di segmento e direttive di segmento semplificate: Sorgente per file COM
Sorgente per file COM
Con direttive di segmento
Con direttive di segmento semplificate
SEG_UNICO segment assume CS:SEG_UNICO assume DS:SEG_UNICO
; unico segmento per Area Dati e Codice
.MODEL TINY .CODE
; modello di memoria per files COM ; definizione area Codice e Dati
.doc - 1684Kb - 08/mar/2011 - 63 / 82
S3Abacus – Architetture/Asm
ORG 100H
; il codice inizia a 100h, e non a zero
ORG 100h
; il codice inizia a 100h e non a zero
START: jmp MAIN
; si salta l’area Dati
START:
; si salta l’area Dati
MSG DB "Il max vale:" VAR DB 0
; area Dati
MSG DB "Il max vale:" VAR DB 0
; area Dati
MAIN:
; area Codice
MAIN:
; area Codice
SALTA1:
SALTA2: CICLO:
mov mov int mov int lea mov mov mov int mov int cmp jl mov
ah,02 dl,3fh 21h ah,01 21h bx, VAR [bx],al ah,02 dl,3fh 21h ah,01 21h al,[bx] SALTA1 [bx],al
mov mov int mov int cmp jl mov
ah,02 dl,3fh 21h ah,01 21h al,[bx] SALTA2 [bx],al
SALTA1:
SALTA2:
mov cx,0dh lea bx,MSG
CICLO:
mov ah,02 mov dl,[bx] int 21h inc bx loop CICLO int 20h ; fine segmento unico ; fine programma
ends end START
jmp MAIN
mov mov int mov int lea mov mov mov int mov int cmp jl mov
ah,02 dl,3fh 21h ah,01 21h bx, VAR [bx],al ah,02 dl,3fh 21h ah,01 21h al,[bx] SALTA1 [bx],al
mov mov int mov int cmp jl mov
ah,02 dl,3fh 21h ah,01 21h al,[bx] SALTA2 [bx],al
mov cx,0dh lea bx,MSG
mov ah,02 mov dl,[bx] int 21h inc bx loop CICLO int 20h end START
; fine programma
Si noti che, dovendo risiedere dati e codice nello stesso segmento, come sempre per i files eseguibili COM, la prima istruzione di codice deve saltare l’area Dati con un salto incondizionato.
Direttiva
ORG
Sintassi:
ORG valore
Scopo:
La fondamentale direttiva O R G (Origine) indica all’assemblatore che l’origine degli indirizzi del segmento deve valere valore e non zero.
Esempi:
ORG 100h
Nota:
Si impone che il Location Counter dell’assemblatore inizi il conteggio degli indirizzi a partire dal valore 100h e non dal valore 0. Tipico caso dell’assemblaggio dei files .COM.
Con la direttiva ORG 100h si impone che gli indirizzi dell’eseguibile di tipo COM comincino dal 256 byte (=100h) del segmento di codice, per saltare l’area del PSP (cfr. Formato degli eseguibili e Rilocazione) La compilazione di un sorgente destinato a diventare un eseguibile di tipo COM deve ricordare al linker di non immettere l’area di startup nell’eseguibile tramite l’o p z i o n e / t , pertanto i sorgenti di questo tipo devono essere compilati nel seguente modo: C:\>tasm pippo sorgente pippo.asm C:\>tlink pippo /t pippo.obj
; l’assemblatore tasm.exe genera il file oggetto pippo.obj dal file ; il linker tlink.exe genera il file eseguibile pippo.com dal file oggetto
S3Abacus – Architetture/Asm
.doc - 1684Kb - 08/mar/2011 - 64 / 82
ASSEMBLY X-86 AVANZATO
.doc - 1684Kb - 08/mar/2011 - 65 / 82
S3Abacus – Architetture/Asm
MACRO Come sempre accade nella programmazione, speciali valori sono così importanti da meritarsi un nome proprio, così da poterli velocemente individuare all’interno del codice sorgente. Assegnare il nome ad un valore è altresì fondamentale per questioni di manutenzione del codice. Infatti, se il valore dovesse essere modificato, l’uso di un nome simbolico consente di modificare il valore solo una volta, avendo usando solo il nome del valore all’interno del codice. Assegnare un nome a un valore significa definire una m a c r o c o s t a n t e .
MACRO
COSTANTI
Si veda questo breve codice che stampa a schermo la cifra 0 e, a capo, la cifra 1: .MODEL TINY .CODE ORG 100h START: mov ah,02 mov dl,'0' ‘0’ int 21h mov ah,02 mov dl,0dh corrente int 21h mov dl,0ah int 21h mov ah,02 mov dl,'1' int 21h int 20h end START
; il codice Ascii dello zero (30h) può essere scritto con questa sintassi derivata dal C: 30h = ; stampa a video il carattere zero ; il codice Ascii 0dh (=13d) è il Carriage Return (CR). Sposta il cursore all’inizio della riga ; il codice Ascii 0ah (=10d) è il Line Feed (LF). Sposta il cursore nella riga sottostante
; stampa a video il carattere uno
OUTPUT C:\>acapo 0 1 C:\> La stampa a schermo dei caratteri Ascii speciali 0dh (=13d) e 0ah (=10d), detti rispettivamente CR (C a r r i a g e R e t u r n ) e LF (L i n e F e e d ), provoca l’effetto dell’ “andare a capo”. Siccome si tratta di valori speciali, usati per un compito dedicato, è buona norma nominarli e usare, nel codice sorgente, il loro nome. Nominare un valore, significa creare una costante Macro, cioè un nome simbolico associato ad un valore: quando l’assemblatore incontra quel nome simbolico nel sorgente, sostituisce il simbolo con il valore corrispondente (e s p a n s i o n e d e l l a m a c r o ).
PseudoIstruzione EQU Sintassi:
nome EQU espressione
Scopo:
Crea il nome che sarà sostituito con espressione durante l’assemblaggio.
Esempi:
CR EQU 0dh
Nota:
RIGA EQU 80
COLONNA EQU 25
SCHERMO EQU RIGA*COLONNA
Normalmente i simboli delle costanti macro sono scritti in maiuscolo. Si noti che l’assemblatore, durante la prima passata, può ricalcolare valori costanti tramite operatori aritmetici (+,-,*,/) e sostituire, al simbolo, il valore costante ricalcolato. La pseudoistruzione EQU è del tutto equivalente alla direttiva #define del linguaggio C.
Ovviamente la pseudoistruzione EQU non genera alcuna riga di codice macchina, essendo una direttiva. L’assemblatore si limita a sostituire ai simboli individuati nel sorgente, i rispettivi valori costanti durante il compile time. Per questo motivo le costanti EQU vanno citate prima dell’inizio del codice. Il programma sottostante è equivalente al precedente; usa EQU per indicare costanti speciali. L’output rimane invariato ; Direttive EQU per CR e LF
CR EQU 13 LF EQU 10 .MODEL TINY .CODE ORG 100h START:
mov mov int mov mov
ah,02 dl,'0' 21h ah,02 dl,CR
; il simbolo CR sarà sostituito con il valore 13 (=0dh)
.doc - 1684Kb - 08/mar/2011 - 66 / 82
S3Abacus – Architetture/Asm
int mov int mov mov int int end START
MACRO
21h dl,LF 21h ah,02 dl,'1' 21h 20h
; il simbolo LF sarà sostituito con il valore 10 (=0ah)
; stampa a video il carattere uno
DI CODICE
Dovendo stampare diverse righe di zeri e uni, nel codice dovremmo usare varie volte le sei righe di codice che stampano a schermo un ‘acapo’. Il codice ripetuto appesantisce il sorgente e lo rende meno leggibile, cosicchè è possibile riunire un blocco di codice sorgente e assegnargli un nome simbolico, citando il solo nome nel codice. In questo caso si parla di m a c r o d i c o d i c e . Come prima, durante la prima passata, l’assemblatore, quando incontra il nome simbolico di una macro di codice, sostituisce ad essa l’intero blocco di codice corrispondente (e s p a n s i o n e d e l l a m a c r o ), operazione di nuovo eseguita a compile time.
PseudoIstruzione MACRO / ENDM Sintassi:
nome MACRO (codice) ENDM
Scopo:
Crea il blocco di (codice) che sarà sostituito al simbolo nome durante l’assemblaggio.
Esempi:
BEEP MACRO mov ah,2 mov dl, 7 int 21h ENDM
Nota:
ACAPO MACRO mov ah,2 mov dl, 13 int 21h mov dl, 10 Int 21h ENDM
Negli esempi, una macro BEEP che emette un suono (infatti il codice Ascii speciale 7 non emette simboli sullo schermo, ma un breve beep). Quindi una macro ACAPO che emette un acapo sullo schermo.
Si veda il seguente codice, che stampa una sequenza di zeri e uni su quattro righe: ACAPO mov mov int mov int ENDM
MACRO ah,2 dl, 13 21h dl, 10 21h
; Definizione della macro, con nome simbolico ACAPO
; terminazione del blocco macro
.MODEL TINY .CODE ORG 100h START:
mov ah,02 mov dl,'0' int 21h ACAPO compongono mov ah,02 mov dl,'1' int 21h ACAPO mov ah,02 mov dl,'0' int 21h ACAPO mov ah,02 mov dl,'1' int 21h int 20h end START C:\>macapo 0 1 0 1
; Uso della macro. In questo punto la macro ACAPO verrà espansa nelle 5 istruzioni che la
; Uso della macro. Altre 5 istruzioni espanse
; Uso della macro. Altre 5 istruzioni espanse
OUTPUT
.doc - 1684Kb - 08/mar/2011 - 67 / 82
S3Abacus – Architetture/Asm
C:\>
MACRO
CON PARAMETRI E ETICHETTE
Le macro di codice diventano veramente interessanti se utilizzate con parametri, ovvero se dotate di argomenti che possono essere variabili nel momento dell’uso. La seguente è una macro che stampa un carattere sullo schermo, indicato al momento dell’uso: STAMPACAR MACRO carattere mov ah, 2 mov dl, carattere int 21h ENDM Il suo uso e’ intuibile: STAMPACAR ‘P’ Se invece una macro dovesse contenere una o più etichette, si presenterebbe il problema dell’uso ripetuto dell’etichetta (cfr. Scrivere un programma EXE), dato che la macro viene espansa nel codice del programma e il nome dell’etichetta verrà ripetuto tante volte quante volte la macro è usata. Per ovviare a questo problema si usa una direttiva dedicata L O C A L che consente di dichiarare le etichette utilizzate nella macro, lasciando il compito all’assemblatore di gestirne correttamente la ripetizione.
Direttiva
LOCAL
Sintassi:
LOCAL nome
Scopo:
Impone all’assemblatore di trasformare il simbolo nome usato in una macro in un simbolo univoco per ogni espansione della macro.
Nota:
La direttiva va posta all’inizio del blocco di codice della macro.
Si osservi questo codice che acquisisce un carattere in input dopo aver mostrato un rudimentale prompt (‘?’), e presenta una macro che stampa un carattere, ma solo se numerico: STAMPACARNUM MACRO regchar LOCAL NONOK cmp regchar, '0' jl NONOK cmp regchar, '9' jg NONOK mov ah, 2 mov dl, regchar int 21h NONOK: della macro ENDM
; regchar è il parametro della macro, un registro a 8bit ; l’etichetta NONOK deve essere dichiarata con la direttiva LOCAL
; la dichiarazione dell’etichetta impedisce l’errore di duplicazione, nel caso di più usi
.MODEL TINY .CODE ORG 100h START: mov ah,2 mov dl, '?' int 21h mov ah,0 int 16h STAMPACARNUM al int 20h end START
; stampa a video del carattere ? come prompt per l’input ; input di un carattere da tastiera, senza echo. Il carattere digitato ritorna in AL ; AL è il valore del parametro della macro
C:\>mparam ?1 C:\> (si è digitato il carattere 1, che viene regolarmente stampato a schermo)
OUTPUT
.doc - 1684Kb - 08/mar/2011 - 68 / 82
S3Abacus – Architetture/Asm
STACK Solo con la programmazione assembly il programmatore può utilizzare espressamente la zona di memoria dello Stack. Ricordiamo che tutti i linguaggi ad alto livello usano lo Stack, ma in modo trasparente al programmatore, per allocare/deallocare le variabili locali, far transitare i parametri alle procedure e gestire gli indirizzi di andata e ritorno delle subroutines. Per velocizzare tutti questi processi, lo Stack assume la forma di una s t r u t t u r a d a t i a P i l a (o L I F O , Last In, First Out). L’immissione di un valore nello stack si appoggia sull’ultimo valore presente nello stack, in modo tale che l’ultimo valore immesso, sempre in cima alla pila, sia immediatamente accessibile. Per raggiungere i valori sotterrati nella pila è necessario scaricare quelli che lo ricoprono, come quando si vuole prendere un piatto in mezzo ad una pila di piatti. Per gestire velocemente le operazioni di scrittura (inserimento) e lettura (prelevamento) dallo stack, l’ISA x-86 prevede istruzioni specifiche (rispettivamente P U S H e P O P ) e automatismi specifici su alcuni registri: lo Stack Pointer SP contiene sempre e automaticamente l’indirizzo dell’ultimo elemento sulla cima dello stack. Lo stack x-86 è organizzato a word (due byte), ovvero ogni elemento in pila è sempre ampio due byte. Lo stack x-86 inizia (ha la base) sempre alla fine di un segmento di memoria, ovvero l’indirizzo del primo elemento di uno stack ha sempre valore di offset pari a FFFEh. In altre parole, all’avvio di un qualsiasi programma eseguibile (EXE o COM) il registro SP contiene sempre il valore FFFEh. Ciò significa che la pila dello stack x-86 cresce diminuendo gli indirizzi (dello Stack Pointer SP) di due unità alla volta per ogni elemento. Questa scelta è opportuna, dato che lo stack si amplia a runtime senza controllo: se si perde il controllo dello stack e lo si riempie indefinitamente (S t a c k O v e r f l o w ), vengono sovrascritte locazioni di memoria del programma, ma non del Sistema Operativo. Le istruzioni per la gestione esplicita dello stack sono:
Istruzione PUSH Sintassi:
PUSH sorgente
Scopo:
Decrementa SP di due unità e pone sorgente sullo Stack all’indirizzo contenuto in SP.
Esempi:
PUSH AX
Nota:
PUSH 7 ; non per l’8086, dal 80186+
PUSH [BX]
Nel primo caso il contenuto di AX viene posto sulla cima dello stack. Sorgente non può essere un valore immediato, almeno nell’8086/88, ma può essere una locazione di memoria, purchè ampia due byte
Istruzione POP Sintassi:
POP destinazione
Scopo:
Preleva una word dallo Stack, dall’indirizzo contenuto in SP, e la deposita in destinazione, quindi incrementa SP di due unità.
Esempi:
POP AX
Nota:
POP [BX]
POP VAR
Nel primo caso, il valore a due byte in cima allo stack viene posto in AX. Negli altri casi, il valore in cima allo stack viene posto direttamente in memoria, occupando due celle contigue a partire dagli indirizzi contenuti, rispettivamente, in BX e VAR.
Altre istruzioni oramai necessarie per lo sviluppo del codice d’esempio, sono le principali istruzioni aritmetiche:
Istruzione ADD Sintassi:
ADD destinazione, sorgente
Scopo:
Effettua la somma (anche con segno) tra destinazione e sorgente. Il risultato viene collocato in destinazione. destinazione non può essere un immediato, ma può essere una zona di memoria.
Esempi:
ADD BX, 256
ADD BX, CX
ADD VAR, 12
Istruzione SUB Sintassi:
SUB minuendo, sottraendo
Scopo:
Sottrae da minuendo il sottraendo (anche con segno). Il risultato viene collocato in minuendo. minuendo non può essere un immediato, ma può essere una zona di memoria.
Esempi:
SUB BX, 256
SUB BX, CX
SUB VAR, 12
.doc - 1684Kb - 08/mar/2011 - 69 / 82
S3Abacus – Architetture/Asm
Istruzione MUL Sintassi:
MUL moltiplicatore
Scopo: Effettua la moltiplicazione senza segno tra: AL e moltiplicatore, se moltiplicatore è a 8 bit, oppure tra AX e moltiplicatore, se moltiplicatore è a 16 bit. Nel primo caso colloca in AX il risultato, nel secondo caso in DX:AX moltiplicatore non può essere un immediato, ma può essere una cella di memoria. Esempi: Nota:
MUL CH
MUL VAR
MUL SI
Attenzione: se il risultato è maggiore del contenitore, saranno impostati i flag di Overflow o di Carry, altrimenti azzerati.
Istruzione DIV Sintassi:
DIV divisore
Scopo:
Effettua la divisione senza segno tra: AX e divisore, se divisore è a 8 bit, oppure tra DX:AX e divisore, se divisore è a 16 bit. Nel primo caso colloca in AL il quoziente e in AH il resto, nel secondo caso in AX il quoziente e in DX il resto. divisore non può essere un immediato, ma può essere una cella di memoria.
Esempi:
DIV BL
Nota:
DIV VAR
DIV SI
Attenzione: se il quoziente non sta nel contenitore, avviene un errore di overflow o di divisione per zero. Es., MOV AX,0A100; MOV BL,2; DIV BL; genera un errore perché A100h / 2 = 5080h, che non sta in un byte.
Si consulti ora questo codice, che stampa in binario il valore memorizzato nella variabile VAR allocata in memoria. Si usa l’istruzione DIV (divisione) per memorizzare i resti delle divisioni per due, memorizzarli sullo stack, quindi riprenderli per stampare le cifre binarie. .MODEL TINY .CODE ORG 100h START:
jmp MAIN
VAR DW 00A1h
; area Dati; il valore VAR (A1h) sarà convertito in binario
MAIN: mov ax,VAR mov bl,2 mov cx,0
; il dividendo in AX ; il divisore in BL ; conterà il numero di divisioni, cioè il numero di cifre binarie
div bl push ax inc cx mov ah,0 cmp al,0 jne ANCORA
; ; ; ; ; ;
ANCORA:
STAMPA: pop dx mov dl,dh add dl,'0' corrispondente mov ah,2 int 21h loop STAMPA int 20h end START
divisione per 2, in AH il resto, in AL il risultato salvataggio del resto e del risultato sullo stack conteggio del numero delle cifre binarie annullamento del resto, rimarrà solo il risultato per la prossima divisione il risultato è zero? se no, si continua la divisione per due
; si preleva dallo stack il valore, tra cui il resto della divisione per due ; si mette il resto (0 o 1) in DL per la stampa a schermo ; si aggiunge il codice Ascii dello zero per ottenere il codice Ascii del numero (‘0’ o ‘1’) ; stampa a schermo della cifra binaria ; ancora cifre da stampare?
OUTPUT C:\>bin 10100001 C:\> Lo stack di questo programma si riempie nel seguente modo, a seguito di otto chiamate PUSH AX. Nella colonna in grigio, i resti, a fianco i risultati Stack SP valori ss:FFEE ►
0100
Descrizione 8va divisione: 01h / 2 = 00h, resto 1
.doc - 1684Kb - 08/mar/2011 - 70 / 82
S3Abacus – Architetture/Asm
ss:FFF0 ss:FFF2 ss:FFF4 ss:FFF6 ss:FFF8 ss:FFFA ss:FFFC ss:FFFE
0001 0102 0005 000A 0014 0028 0150 0000
7ma divisione: 02h / 2 = 01h, resto 6ta divisione: 05h / 2 = 02h, resto 5ta divisione: 0Ah / 2 = 05h, resto 4ta divisione: 14h / 2 = 0Ah, resto 3za divisione: 28h / 2 = 14h, resto 2da divisione: 50h / 2 = 28h, resto 1ma divisione: A1h / 2 = 50h, resto Valore iniziale dello Stack Pointer
0 1 0 0 0 0 1
Bisogna ricordare che le operazioni sullo stack devono sempre essere b i l a n c i a t e , ovvero lo Stack Pointer (SP) deve sempre tornare al valore di partenza alla fine del programma. Le istruzioni PUSH e POP, pertanto, devono essere eseguite lo stesso numero di volte.
.doc - 1684Kb - 08/mar/2011 - 71 / 82
S3Abacus – Architetture/Asm
PROCEDURE L’uso delle macro di codice semplifica notevolmente la scrittura dei programmi assembly ed è vivamente consigliata. Un effetto collaterale dell’uso delle macro di codice è l’espansione del codice sorgente e del codice eseguibile, cioè il suo incremento in quantità. Ciò significa anche maggior memoria principale utilizzata. Le macro di codice, inoltre, rallentano il tempo di compilazione e, soprattutto, non possono adeguarsi circa situazioni che avvengono solo a run time. Una soluzione a questi problemi è l’uso di p r o c e d u r e (o subroutines) che solo apparentemente svolgono un compito analogo alla macro. Una procedura è ancora un blocco di codice con un nome simbolico, ma stavolta il nome della procedura non è un simbolo ma l’indirizzo della sua prima istruzione in memoria. Le procedure, infatti, sono allocate in memoria, in uno spazio privato, e devono essere chiamate a runtime dal codice del programma (o da altre procedure). Ciò significa che il blocco di codice di una procedura non viene ripetuto nel sorgente ad ogni occorrenza del suo nome, ma solo usato dal chiamante: il blocco di codice di una procedura è unico e allocato in memoria, cioè le procedure operano a runtime. Le procedure, pertanto, non incrementano il codice sorgente ed eseguibile del programma, e quindi risparmiano anche nell’uso della memoria principale, rispetto alle macro di codice. Inoltre, operando a runtime, possono crearsi veri e propri ambienti autonomi di elaborazione, es. mediante la creazione, sempre a runtime, di zone di memoria private, dette v a r i a b i l i l o c a l i . L’unico effetto collaterale di una procedura, rispetto alle macro di codice, è una maggior lentezza nell’esecuzione, dato che il codice chiamante deve preparare la memoria (di solito lo stack) per avviare la procedura, e la procedura, a sua volta, deve ripristinare la memoria al suo termine e prima di ritornare al chiamante. Queste operazioni sono dette m e c c a n i s m o d i c h i a m a t a e ritorno della procedura.
DEFINIZIONE
DI PROCEDURA
Le procedure vanno definite con una sintassi molto simile a quella delle macro di codice, anche se la collocazione delle procedure deve essere posta necessariamente nell’area codice, prima del programma principale, o dopo.
PseudoIstruzione PROC / ENDP Sintassi:
nome PROC (codice) (RET) ENDP
Scopo:
Definisce il blocco di (codice) che sarà chiamato tramite l’istruzione CALL nome. Al termine bisogna ridare il controllo al chiamante con (RET)
Esempi:
BEEP PROC mov ah,2 mov dl, 7 int 21h ret ENDP
Nota:
ACAPO PROC mov ah,2 mov dl, 13 int 21h mov dl, 10 Int 21h ret ENDP
I due esempi sono simili a quelli riportati nella sintassi delle direttive MACRO/ENDM, ma il blocco di codice termina con l’istruzione RET per completare il meccanismo di chiamata. Di fianco a PROC si può aggiungere il modificatore FA R se la chiamata avviene da un segmento di codice differente da quello che contiene la procedura (per i modelli di memoria MEDIUM, LARGE e HUGE)
Una procedura deve essere chiamata dal codice del programma e quindi deve ritornare al chiamante per consentirgli il regolare flusso di esecuzione. L’Isa x-86 preve due istruzioni caratteristiche per gestire il meccanismo di chiamata:
Istruzione CALL Sintassi:
CALL target
Scopo:
L’istruzione CALL esegue le seguenti operazioni: 1) salva nello stack l’i n d i r i z z o d i r i t o r n o ; 2) trasferisce il controllo all’operando target tramite un salto incondizionato. L’indirizzo di ritorno è l’indirizzo dell’istruzione successiva a quella di CALL.
Esempi:
CALL ACAPO
Nota:
CALL word ptr [BX]
Nel primo caso la CALL ACAPO può essere vista come l’unione delle due PUSH IP; JMP ACAPO, ricordando che il nome di una procedura è il suo indirizzo in memoria. Nel secondo caso un esempio di chiamata dinamica, ovvero una chiamata che assume valore solo a runtime (in base al valore attuale di BX). Word ptr serve per indicare all’assemblatore che la locazione puntata da BX riguarda due byte contigui a partire dall’indirizzo contenuto in BX.
Istruzione RET Sintassi:
RET
Scopo:
L’istruzione RET assume che l’indirizzo di ritorno si trovi attualmente in cima allo stack. Essa esegue le seguenti operazioni: 1) preleva dallo stack dell’indirizzo di ritorno 2) salto all’indirizzo di ritorno.
.doc - 1684Kb - 08/mar/2011 - 72 / 82
S3Abacus – Architetture/Asm
Esempi: Nota:
RET La RET, che va sempre posta come ultima istruzione di un blocco di procedura, esegue, praticamente, le seguenti istruzioni: POP indirizzoritorno/JMP indirizzoritorno, oppure, con una sola istruzione logica: POP IP
In entrambi i casi, se la procedura è di tipo FAR – cioè si trova in un segmento di codice differente da quello del chiamante, sia CALL che RET, invece di salvare/rileggere solo la parte offset dell’indirizzo del program counter (indirizzo di ritorno su due byte), salvano e rileggono sia la parte seg che la parte offset dell’indirizzo (indirizzo di ritorno su quattro byte) in modo del tutto trasparente al programmatore.
MECCANISMO
DI CHIAMATA
Si veda il seguente esempio che illustra il meccanismo di chiamata e ritorno di una procedura tramite lo stack. La colonna in grigio mostra gli indirizzi effettivi delle righe di codice. .MODEL TINY .CODE ORG 100h cs:0100
START: jmp MAIN
ACAPO salta l’area Dati cs:0103 mov cs:0105 mov cs:0107 int cs:0109 mov cs:010B int cs:010D ret ENDP
PROC ah,2 dl, 13 21h dl, 10 21h
MAIN: cs:010E mov ah,02 cs:0110 mov dl,'0' cs:0112 int 21h cs:0114 call ACAPO garantire il ritorno cs:0117 mov ah,02 cs:0119 mov dl,'1' cs:011B int 21h cs:011D int 20h end START
; All’avvio si deve saltare il codice delle procedure ; La procedura deve stare nell’area di codice, ma deve essere saltata all’avvio, così come si
; L’istruzione RET è necessaria per far funzionare il meccanismo di ritorno
; La procedura ACAPO deve essere chiamata esplicitamente con l’istruzione CALL, per
OUTPUT
C:\>pacapo 0 1 C:\> Lo stack del programma subisce il seguente movimento (tre passi, compreso lo stato iniziale): Stack SP valori
Descrizione
ss:FFFA ss:FFFC ss:FFFE ►
.... .... 0000
Valore iniziale dello Stack Pointer
ss:FFFA ss:FFFC ► ss:FFFE
.... 0117 0000
Indirizzo di ritorno, sulla CALL ACAPO Valore iniziale dello Stack Pointer
ss:FFFA ss:FFFC ss:FFFE ►
.... .... 0000
Dopo la RET nella procedura ACAPO
PRESERVARE
I REGISTRI
L’utilizzo delle procedure comporta un effetto collaterale abbastanza grave, detto i n t e r f e r e n z a : i registri usati dalla procedura sovrascrivono il contenuto precedentemente salvato in quei registri dal chiamante, con l’effetto che al ritorno della procedura il chiamante non ritrova più i valori precedentemente salvati nei registri. Per evitare l’interferenza, la procedura deve preservare i registri in ingresso, ovvero salvare il contenuto dei registri che essa stessa userà al suo interno, salvandoli ordinatamente sullo stack, per poi ripristinarli ordinatamente appena prima di ritornare il controllo al chiamante (appena prima dell’istruzione RET). La preservazione dei registri può essere effettuata puntualmente, salvando sullo stack solo i registri usati dalla procedura, o in modo completo sfruttando due apposite istruzioni x-86, PUSHA e POPA che, rispettivamente, salvano sullo stack e riprendono dallo stack tutti i registri (ma solo per l’x-86 a partire dall’80186, con l’esclusione, quindi, dell’8086/88).
S3Abacus – Architetture/Asm
.doc - 1684Kb - 08/mar/2011 - 73 / 82
Il codice precedente, dotato di preservazione dei registri, appare come segue (l’output non cambia): .MODEL TINY .CODE ORG 100h START:
jmp MAIN
ACAPO PROC push ax push dx mov ah,2 mov dl, 13 int 21h mov dl, 10 int 21h pop dx pop ax ret ENDP
; si preservano i soli registri AX e DX, gli unici usati dalla procedura, inviandoli sullo stack
; si ricaricano i registri preservati, in ordine inverso, per restituirli invariati al chiamante
MAIN:
mov ah,02 mov dl,'0' int 21h call ACAPO mov ah,02 mov dl,'1' int 21h int 20h end START C:\>pacapo 0 1 C:\>
OUTPUT
.doc - 1684Kb - 08/mar/2011 - 74 / 82
S3Abacus – Architetture/Asm
PASSAGGIO DI PARAMETRI Le procedure diventano realmente fondamentali quando permettono il passaggio dei parametri, ovvero possono svolgere il proprio compito sulla base di valori che il chiamante decide a runtime. In realtà si è già usato un sistema di passaggio di parametri, ad esempio durante l’uso delle interruzioni sw: valorizzare un registro prima della chiamata all’istruzione INT significa passare – t r a m i t e r e g i s t r o – un parametro alla routine dell’interruzione sw. Il passaggio dei parametri tramite registri è molto veloce e semplice, ma ha molte limitazioni, prima di tutto la quantità dei registri disponibili. Le procedure, per linguaggi ad alto e a basso livello come l’assembly, usano in realtà lo stack per passare i parametri e, quando serve, per ritornarli al chiamante. L’idea è semplice: il chiamante, prima di chiamare la procedura con la consueta istruzione CALL, deposita sullo stack i valori che intende passare alla procedura. La procedura, prima di iniziare il suo compito, preleva dallo stack i parametri e li usa al suo interno. Per ritornare valori dalla procedura al chiamante, si usa lo stesso meccanismo. In questo caso il passaggio di parametri si dice t r a m i t e l o s t a c k . Il passaggio di parametri tramite lo stack deve tener presente che, sullo stack, come ultimo valore, verrà sempre posto l’indirizzo di ritorno della procedura– ad opera dell’istruzione CALL. Pertanto la procedura dovrà prelevare i parametri senza eliminare dalla cima dello stack l’indirizzo di ritorno, che dovrà essere usato dall’istruzione RET per ritornare correttamente al chiamante. Esistono varie tecniche per passare i parametri sullo stack. Le più diffuse prendono il nome di c d e c l (usata dal linguaggio C e derivati) e s t d c a l l (usata dal linguaggio Pascal e dalle API di alcuni SO). In questa sezione vedremo un passaggio di parametri alle procedure abbastanza simile allo stile del C o cdecl, che usa il registro BP (Base Pointer) per prelevare i dati sullo stack senza modificare il registro SP (Stack Pointer). Si ricorda che il registro BP ha la proprietà di indirizzare in memoria, cioè di contenere indirizzi di memoria. 1. 2. 3. 4. 5.
6. 7. 8.
Prima di tutto il chiamante deve porre nello stack i parametri richiesti dalla procedura. L’operazione si effettua con la consueta istruzione PUSH, ripetuta tante volte quanti sono i parametri da passare. Quindi si effettua la chiamata normalmente, con l’istruzione CALL. Essa immetterà sulla cima dello stack, come di consueto, l’indirizzo di ritorno. La procedura, a sua volta, deve immediatamente salvare sullo stack il registro BP, dato che verrà usato e sovrascritto per prelevare i parametri. Quindi il registro BP deve essere impostato con il valore dello Stack pointer SP, mediante una istruzione MOV: in questo modo BP punta alla cima dello stack. Ora i parametri possono essere prelevati uno a uno tramite BP, avendo cura di ricordare che il primo parametro è profondo 4 byte nello stack: infatti i primi due byte in cima alla pila riportano il valore di BP (appena memorizzato), e i successivi due byte riportano il valore dell’indirizzo di ritorno. Ogni parametro si scosta di due byte, pertanto a BP+4 corrisponde il valore del primo parametro, a BP+6 il valore del secondo parametro, a BP+8 il valore del terzo parametro, e così via. Ora può essere scritto il codice della procedura, comprese le eventuali istruzioni per preservare i registri. Infine, appena prima dell’istruzione RET, va ripristinato il registro BP, che se tutto è stato svolto correttamente, si trova attualmente in cima allo stack. Una volta prelevato il valore originale di BP, l’indirizzo di ritorno è disponibile in cima alla pila per l’istruzione RET. Il chiamante, quando riprende il controllo, si ritrova i parametri ancora sullo stack, per cui deve ripristinare lo stato dello stack deallocandoli, cioè facendo tornare lo Stack Pointer SP al valore originario. Ciò è semplice, tramite una istruzione ADD: si aggiungono allo Stack Pointer tante ‘doppiette’ quanti sono i parametri (es., per 3 parametri: ADD SP,6). Una delle maggiori differenze tra la tecnica cdecl e stdcall consiste nel fatto che cdecl impone che sia il chiamante a deallocare i parametri dallo stack, mentre in stdcall è la procedura a farlo.
Bisogna ricordare che il salvataggio immediato di BP – e il suo successivo ripristino, è fondamentale benchè BP non sia di norma usato dai moduli che chiamano le procedure. Infatti una procedura può – e spesso lo fa, chiamarne un’altra al suo interno (c h i a m a t a a n n i d a t a ), alla quale passare parametri. Se BP non fosse preservato, le chiamate annidate non funzionerebbero. Il seguente codice usa una procedura a cui viene passato sullo stack il codice Ascii da stampare a schermo. Siccome lo stack usa elementi a 16 bit, il codice Ascii (8bit) viene enucleato nella parte bassa del registro AX, che e’ a 16 bit. .MODEL TINY .CODE ORG 100h cs:0100 cs:0103 cs:0104 parametro cs:0106 cs:0109 cs:010B cs:010D dovere cs:010E
cs:010F cs:0112 cs:0113
START: jmp MAIN STAMPACAR PROC push bp mov bp,sp mov mov int pop
dx,[bp+4] ah,2 21h bp
; si preserva BP sullo stack, come prima istruzione della procedura ; si memorizza lo stack Pointer in BP, in modo che BP possa servire pre reperire il ; ecco il parametro, profondo 4 byte dentro lo stack (il codice Ascii del ?) ; ripristino di BP. Ora sullo stack c’è l’indirizzo di ritorno, così che RET funzioni a
ret ENDP MAIN: mov al,'?' push ax call STAMPACAR
; passaggio del parametro sullo stack (il codice Ascii del ?, in AL all’interno di AX)
.doc - 1684Kb - 08/mar/2011 - 75 / 82
S3Abacus – Architetture/Asm
cs:0116 cs:0119
add sp,2 int 20h end START
; deallocazione dello stack. Un parametro, una “doppietta” OUTPUT
C:\>pparam ? C:\> Seguendo il listato del programma, si può seguire l’andamento dello stack per ogni istruzione che lo modifica implicitamente (come CALL e RET) o esplicitamente come PUSH, POP e ADD SP,2. Stack SP valori
Descrizione
ss:FFF8 ss:FFFA ss:FFFC ss:FFFE ►
.... .... .... 0000
Valore iniziale dello Stack Pointer
ss:FFF8 ss:FFFA ss:FFFC ► ss:FFFE
.... .... 003F 0000
PUSH AX; 3Fh è il codice Ascii del carattere ? Valore iniziale dello Stack Pointer
ss:FFF8 ss:FFFA ► ss:FFFC ss:FFFE
.... 0116 003F 0000
CALL STAMPACAR; 116h è l’indirizzo di ritorno PUSH AX Valore iniziale dello Stack Pointer
ss:FFF8 ► ss:FFFA ss:FFFC ss:FFFE
0000 0116 003F 0000
PUSH BP; in BP c’era il valore 0 CALL STAMPACAR PUSH AX Valore iniziale dello Stack Pointer
Nella procedura ora si pone in BP lo Stack Pointer, con mov bp,sp, cioè BP = FFF8h. All’indirizzo BP + 4 = FFFCh, c’è l’indirizzo del parametro sullo stack, cosicchè mov dx,[bp+4] pone in DX il valore 003Fh, cioè in DL il codice Ascii (3Fh) del punto interrogativo. ss:FFF8 ss:FFFA ► ss:FFFC ss:FFFE
0000 0116 003F 0000
ss:FFF8 ss:FFFA ss:FFFC ► ss:FFFE
0000 0116 003F 0000
ss:FFF8 ss:FFFA ss:FFFC ss:FFFE ►
.... .... .... 0000
POP BP; ripristinato BP (0000h)
RET; caricato l’indirizzo di ritorno (0116h)
ADD SP, 2 e valore iniziale dello Stack Pointer
.doc - 1684Kb - 08/mar/2011 - 76 / 82
S3Abacus – Architetture/Asm
VARIABILI LOCALI Una delle proprietà fondamentali delle procedure è la possibilità di crearsi un ambiente di memoria privato con il quale interagire per completare compiti anche abbastanza articolati. L’area di memoria privata di una procedura è allocata sullo stack e deallocata appena prima del ritorno al chiamante. Le variabili che prendono posto nell’area privata delle procedure sono dette v a r i a b i l i l o c a l i o v a r i a b i l i a u t o m a t i c h e . Come visto in precedenza, una volta preso il controllo, una procedura memorizza la cima dello stack in BP per poter prelevare eventuali parametri sotterrati nella pila. Per creare memoria alle variabili locali, bisogna invece estendere lo stack al di sopra della cima, di tante “doppiette” quante sono le variabili locali da creare. Così, utilizzando sempre BP come base, si raggiungeranno le variabili locali con sottrazioni di “doppiette”: in BP-2 ci sarà l’indirizzo della prima variabile locale, in BP-4 l’indirizzo della seconda, in BP-6 l’indirizzo della terza, e così via. Al termine, l’area delle variabili locali deve essere deallocata dalla procedura, riportando lo stack Pointer al suo valore originale. 1. 2. 3. 4.
La procedura, dopo aver memorizzato in BP la cima dello stack, lo amplia opportunamente sottraendo allo Stack Pointer SP tante doppiette quante sono le variabili locali da usare, es. SUB SP,4, alloca due variabili locali da due byte l’una (o quattro variabili locali da un byte l’una). Ora la procedura può scrivere nella variabile locale con la consueta MOV, indicando l’indirizzo della variabile tramite BP, es. MOV [BP-2], AX, mette nella prima variabile locale il valore del registro AX Allo stesso modo la procedura può leggere le variabili locali, usando sempre BP per indirizzarle, es. MOV DL, byte ptr [BP-4] pone nel registro DL la variabile locale di ampiezza un byte dalla seconda area di memoria allocata sullo stack. Al termine, la zona delle variabili locali viene deallocata riportando lo Stack pointer SP al valore originale che ora è contenuto in BP (es. MOV SP, BP).
Come esempio vediamo una versione di listato molto simile a quello usato per il passaggio di un parametro. In questo caso si passa alla procedura una cifra ed essa ne stamperà il simbolo Ascii sullo schermo, dopo aver usato una variabile locale per memorizzare la base dei codici Ascii numerici, cioè il codice Ascii di zero (30h): .MODEL TINY .CODE ORG 100h START: jmp MAIN
cs:0100
STAMPANUM PROC cs:0103 push bp cs:0104 mov bp,sp cs:0106 mov dx,[bp+4] cs:0109 sub sp,2 cs:010C mov byte ptr [bp-2],30h cs:0110 add dx,[bp-2] zero per ottenere il simbolo cs:0113 mov ah,2 cs:0115 int 21h cs:0117 mov sp,bp cs:0119 pop bp cs:011A ret cs:0113 ENDP MAIN: mov al,9
cs:011B Ascii) cs:011E cs:011F cs:0122 cs:0125
push ax call STAMPANUM add sp,2 int 20h end START
; consueta predisposizione dello stack frame per il prelevamento del parametro ; allocazione della variabile locale ; scrittura della variabile locale, con il valore 30h ; lettura della variabile locale, tramite l’istruzione ADD. Si somma il codice Ascii dello
; deallocazione della variabile locale
; preparazione del parametro, in questo caso il numero nove (e non il suo codice ; passaggio del parametro sullo stack ; deallocazione dello stack
OUTPUT C:\>varloc 9 C:\> Seguendo il listato del programma, si può seguire l’andamento dello stack all’atto dell’allocazione e deallocazione dell’area di memoria locale: Stack SP valori ss:FFF6 ss:FFF8 ss:FFFA ss:FFFC ss:FFFE ►
.... .... .... .... 0000
ss:FFF6
....
Descrizione
Valore iniziale dello Stack Pointer
.doc - 1684Kb - 08/mar/2011 - 77 / 82
S3Abacus – Architetture/Asm
ss:FFF8 ss:FFFA ss:FFFC ► ss:FFFE
.... .... 0009 0000
PUSH AX; 9h è il codice Ascii del carattere zero Valore iniziale dello Stack Pointer
ss:FFF6 ss:FFF8 ss:FFFA ► ss:FFFC ss:FFFE
.... .... 0122 0009 0000
CALL STAMPANUM; 122h è l’indirizzo di ritorno PUSH AX Valore iniziale dello Stack Pointer
ss:FFF6 ss:FFF8 ► ss:FFFA ss:FFFC ss:FFFE
.... 0000 0122 0009 0000
PUSH BP; in BP c’era il valore 0 CALL STAMPANUM PUSH AX Valore iniziale dello Stack Pointer
ss:FFF6 ► ss:FFF8 ss:FFFA ss:FFFC ss:FFFE
.... 0000 0122 0009 0000
SUB SP, 2; spostando SP di due unità, si alloca un elemento sullo stack PUSH BP; in BP c’era il valore 0 CALL STAMPANUM PUSH AX Valore iniziale dello Stack Pointer
ss:FFF6 ► ss:FFF8 ss:FFFA ss:FFFC ss:FFFE
..30 0000 0122 0009 0000
MOV byte ptr [bp-2],30h; si scrive nella variabile locale PUSH BP; in BP c’era il valore 0 CALL STAMPANUM PUSH AX Valore iniziale dello Stack Pointer
ss:FFF6 ► ss:FFF8 ss:FFFA ss:FFFC ss:FFFE
..30 0000 0122 0009 0000
ADD DX,[bp-2]; si legge nella variabile locale PUSH BP; in BP c’era il valore 0 CALL STAMPANUM PUSH AX Valore iniziale dello Stack Pointer
ss:FFF6 ss:FFF8 ► ss:FFFA ss:FFFC ss:FFFE
..30 0000 0122 0009 0000
questa locazione ora non è più valida MOV SP, BP; si dealloca la variabile locale ripristinando lo Stack Pointer CALL STAMPANUM PUSH AX Valore iniziale dello Stack Pointer
ss:FFF6 ss:FFF8 ss:FFFA ► ss:FFFC ss:FFFE
..30 0000 0122 0009 0000
ss:FFF6 ss:FFF8 ss:FFFA ss:FFFC ► ss:FFFE
..30 0000 0122 0009 0000
ss:FFF6 ss:FFF8 ss:FFFA ss:FFFC ss:FFFE ►
.... .... .... .... 0000
NOTAZIONI
POP BP; ripristinato BP (0000h)
RET; caricato l’indirizzo di ritorno (0116h)
ADD SP, 2 e valore iniziale dello Stack Pointer
PER IL PASSAGGIO DI PARAMETRI E LE VARIABILI LOCALI
Per rendere il codice assembly più leggibile e semplice da utilizzare, è spesso conveniente utilizzare uno stile che fa uso di qualche macro costante per poter servirsi di nomi simbolici al posto delle notazioni che indirizzano brutalmente lo Stack, sia per quanto riguarda la gestione dei parametri, che la gestione delle variabili locali. In questo modo il listato precedente assume la seguente forma (il programma eseguibile è assolutamente identico): .MODEL TINY .CODE ORG 100h START: jmp MAIN STAMPANUM PROC Parametro EQU word ptr [BP+4] ; Il nome simbolico Parametro equivale alla zona dello stack che contiene il primo parametro Variabile EQU byte ptr [BP-2] ; Il nome simbolico Variabile equivale alla zona dello stack che contiene la prima variabile locale
S3Abacus – Architetture/Asm
push bp mov bp,sp mov dx,Parametro sub sp,2 mov Variabile,30h add dx,Variabile mov ah,2 int 21h mov sp,bp pop bp ret ENDP MAIN:
mov al,9 push ax call STAMPANUM add sp,2 int 20h end START
.doc - 1684Kb - 08/mar/2011 - 78 / 82
; Uso del nome simbolico Parametro per recuperare il parametro ; Uso del nome simbolico Variabile per scrivere la variabile locale ; Uso del nome simbolico Variabile per leggere la variabile locale
.doc - 1684Kb - 08/mar/2011 - 79 / 82
S3Abacus – Architetture/Asm
DIRETTIVE PER LA PROGRAMMAZIONE E LIBRERIE Per una efficiente programmazione assembly, è necessario utilizzare alcune direttive all’assemblatore per superare alcuni limiti architetturali – come il problema della distanza tra etichetta e riferimento per i salti condizionati o per rendere più agevole la scrittura dei programmi – come ad esempio evitare di pianificare l’uso univoco dei nomi delle etichette. Inoltre è fondamentale conoscere il modo in cui più moduli sorgenti concorrono per generare un file eseguibile, tecnica necessaria per i progetti sw che intendono avvalersi di moduli di libreria.
SALTI
LUNGHI, DIRETTIVA
JUMPS
Per evitare di incorrere nel problema del salto lungo, cioè quando la distanza tra riferimento e etichetta supera i 128 bytes, è sufficiente citare una direttiva iniziale all’assemblatore, la direttiva J U M P S :
Direttiva
JUMPS
Sintassi:
JUMPS
Scopo:
Impone all’assemblatore di trasformare il codice di eventuali salti a distanze superiori di 128 bytes, in un codice equivalente in grado di superare tale limite ed effettuare anche salti lunghi.
Nota:
La direttiva va posta all’inizio del modulo sorgente, subito dopo la direttiva che indica l’inizio dell’area Codice (.CODE). Spesso si usa anche quando non si è certi della presenza di salti lunghi nel codice. La direttiva vale solo per l’assemblatore TASM.
La direttiva JUMPS si limita a trasformare il salto condizionato in una struttura di salto che utilizza un salto incondizionato JMP come supporto per raggiungere l’etichetta distante più di 128 byte dal suo riferimento. Infatti l’istruzione di salto incondizionato JMP non ha limiti di distanza tra riferimento e etichetta.
DUPLICAZIONE
DI ETICHETTE, DIRETTIVA
LOCALS
All’interno delle procedure spesso si vorrebbero usare etichette con nomi uguali in procedure diverse, soprattutto per indicare zone logiche del codice equivalenti (es. FINE, OK, ecc.). Questo genera un errore dell’assemblatore, che necessita di nomi univoci per le etichette in tutta l’area di Codice. Per evitare di pianificare uno schema di naming univoco per le etichette da usare nelle procedure, si può usare una direttiva speciale (L O C A L S ) e una notazione che rendono libero il programmatore nella scelta dei nomi:
Direttiva
LOCALS
Sintassi:
LOCALS
Scopo:
Impone all’assemblatore di trasformare le etichette scritte con un prefisso speciale @@nome univoche aldilà della rimanente parte del nome
Nota:
La direttiva va posta all’inizio del modulo sorgente, subito dopo la direttiva che indica l’inizio dell’area Codice (.CODE). La direttiva vale solo per l’assemblatore TASM.
In definitiva, un codice che usa tali direttive, e che stampa due stringhe con due procedure analoghe, è il seguente: .MODEL TINY .CODE JUMPS LOCALS ORG 100h START:
; Direttiva per evitare il limite del salto lungo (in questo codice però non ce ne sono) ; Direttiva per usare etichette con nomi uguali (tramite il prefisso @@)
jmp MAIN
MSG_1 DB "Sistemi Abacus",0 MSG_2 DB "Classe 3a$"
; Stringa ASCIIZ (termina con uno zero) ; Stringa che termina con il carattere speciale $ (come nel servizio MsDos)
PROC STAMPAASCIIZ push bp mov bp,sp mov bx,[bp+4] @@ANCORA: LOCALS mov dl,[bx] cmp dl,0 je @@FATTO mov ah,2 int 21h inc bx jmp @@ANCORA @@FATTO:
; procedura che stampa a schermo stringhe ASCIIZ (indirizzo passato sullo stack)
; ecco le etichette con il prefisso @@ che consentono nomi uguali in accordo con
.doc - 1684Kb - 08/mar/2011 - 80 / 82
S3Abacus – Architetture/Asm
pop bp ret ENDP ; procedura che stampa a schermo stringhe terminanti con $ (indirizzo passato sullo
PROC STAMPADOLLARO stack) push bp mov bp,sp mov bx,[bp+4] @@ANCORA: LOCALS mov dl,[bx] cmp dl,'$' je @@FATTO mov ah,2 int 21h inc bx jmp @@ANCORA @@FATTO: pop bp ret ENDP
; ecco le etichette con il prefisso @@ che consentono nomi uguali in accordo con
MAIN: lea ax,msg_1 push ax call STAMPAASCIIZ add sp,2 mov ah,2 mov dl, 10 int 21h lea ax,msg_2 push ax call STAMPADOLLARO add sp,2 int 20h end START OUTPUT C:\>jumpslcl Sistemi Abacus Classe 3a C:\>
LIBRERIE,
DIRETTIVE
INCLUDE, PUBLIC
ED
EXTRN
Come per i linguaggi ad alto livello, programmare in assembly diventa veramente proficuo quando si possono usare moduli di l i b r e r i a , cioè files sorgenti o binari che contengono procedure o definizioni di utilità generale, utilizzabili nei programmi senza dover, ogni volta, riscrivere la soluzione di problemi già risolti. Lo sviluppo dei programmi con lo stile del p r o g e t t o e tramite moduli di libreria è una pratica oramai consolidata nel mondo della programmazione. Un progetto è l’insieme di più moduli sorgenti (a volte anche moduli binari), di cui uno solo contiene il punto di ingresso del programma e, tutti gli altri, sono detti moduli di libreria. La compilazione di un progetto è la compilazione di ogni modulo, e la loro unione tramite linker nel t a r g e t del progetto, solitamente un file eseguibile. Naturalmente un progetto deve affrontare il problema dei rapporti tra i moduli i quali possono essere, alternativamente, sia c l i e n t che s e r v e r di funzioni presenti in altri moduli: sono client se citano elementi presenti in altri moduli; sono server se contengono definizioni citate da altri moduli. Il modulo principale, invece, è l’unico che è sempre e solo un modulo client. Il modo più semplice per realizzare il rapporto tra il modulo principale e altri moduli server è tramite la direttiva I N C L U D E . Con questa direttiva, usata dal modulo principale, si indica all’assemblatore di aprire da disco il file argomento della direttiva (modulo server) ed espanderlo nel modulo principale (modulo client) “così com’è” a partire dalla posizione in cui si trova la direttiva INCLUDE nel modulo principale. Il processo è del tutto paragonabile a quello di una macro di codice. In questo caso la libreria (modulo server) è detta l i b r e r i a d i c o d i c e .
Direttiva
INCLUDE
Sintassi:
INCLUDE nomefile
Scopo:
Impone all’assemblatore di cercare il file nomefile, aprirlo ed espanderlo riga per riga nella posizione corrente.
Nota:
La direttiva può essere posta in qualsiasi zona del sorgente; il nome del file può essere indicato anche con il percorso. Solitamente i files assembly d’inclusione hanno estensione .INC. mainincl.asm modulo client (principale)
acapo.inc modulo server (libreria di codice)
.doc - 1684Kb - 08/mar/2011 - 81 / 82
S3Abacus – Architetture/Asm
.MODEL TINY .CODE ORG 100h
CR EQU 13 LF EQU 10
START: jmp MAIN INCLUDE acapo.inc
; qui sarà espanso il file acapo.inc
MAIN: mov ah,02 mov dl,'0' int 21h call ACAPO mov ah,02 mov dl,'1' int 21h int 20h end START
ACAPO mov mov int mov
PROC ah,2 dl, CR 21h dl, LF
int 21h ret ENDP
Il progetto si compila come se fosse composto da un unico file, il file principale (mainincl.asm). Il file server acapo.inc deve essere raggiungibile (nell’esempio, è nella cartella corrente): C:\>tasm mainincl C:\>tlink mainincl /t C:\>mainincl 0 1 C:\> Più spesso il programmatore usa librerie binarie, ovvero moduli server che vengono assemblati autonomamente e collegati ai moduli client durante la fase di linking. I moduli client devono dichiarare in testa al codice quali simboli tratti da moduli esterni verranno usati (direttiva E X T R N ), in modo che l’assemblatore non cada in errore incontrando simboli mai definiti. A sua volta il server deve dichiarare quali simboli possono essere utilizzati da altri moduli (direttiva P U B L I C ) in modo che l’assemblatore e il linker sappia come effettuare il collegamento.
Direttiva
EXTRN
Sintassi:
EXTRN nome:tipo
Scopo:
Indica all’assemblatore che un certo simbolo nome non è definito nel modulo sorgente attuale, bensì in uno esterno. tipo può essere NEAR o FAR se nome è il nome di una procedura; può essere BYTE o WORD se nome è l’etichetta in un’area dati.
Nota:
La direttiva può essere posta in testa al modulo client, per mettere in evidenza la lista di simboli esterni al sorgente, detti anche dipendenze. Per quanto riguarda i nomi delle procedure, il tipo è sempre NEAR se il modello di memoria scelto è TINY, SMALL e COMPACT; FAR negli altri casi. Ogni direttiva EXTRN dovrebbe essere associata ad una duale direttiva PUBLIC contenuta in un modulo esterno.
Direttiva
PUBLIC
Sintassi:
PUBLIC nome
Scopo:
Indica all’assemblatore che un certo simbolo nome può essere utilizzato da moduli esterni.
Nota:
La direttiva può essere posta in testa al modulo server, per mettere in evidenza la lista di simboli pubblici che il modulo offre ai moduli client. Naturalmente ogni nome in ogni direttiva PUBLIC del modulo deve corrispondere ad una effettiva etichetta nel modulo (funzione o dato).
Lo stesso progetto di poco fa, implementato con libreria binaria: mainlib.asm (modulo client principale)
libreria.asm (modulo server di libreria)
EXTRN ACAPO: NEAR
PUBLIC ACAPO
.MODEL TINY .CODE ORG 100h
.MODEL TINY .CODE
START:
ACAPO PROC mov ah,2
.doc - 1684Kb - 08/mar/2011 - 82 / 82
S3Abacus – Architetture/Asm
mov ah,02 mov dl,'0' int 21h call ACAPO mov ah,02 mov dl,'1' int 21h int 20h end START
mov int mov int ret ACAPO end
dl, 13 21h dl, 10 21h ENDP
In questo caso il processo di compilazione è radicalmente differente rispetto all’uso delle libreire sorgenti tramite la direttiva INCLUDE. I due moduli sono assemblabili autonomamente, e danno luogo a due files oggetto .OBJ. Sarà il linker a effettuare il collegamento tra i due moduli binari, come dalla seguente sintassi: C:\>tasm mainlib mainlib.obj C:\>tasm libreria libreria.obj C:\>tlink mainlib libreria /t mainlib.exe C:\>mainlib 0 1 C:\>
; assemblaggio modulo client (principale). Genera ; assemblaggio modulo server (libreria). Genera ; correlazione (linking) dei moduli. Genera
MAKEFILE Nel caso della compilazione di progetti con librerie binarie, risulta molto utile utilizzare l’utility M A K E in dotazione con Borland C (file MAKE.EXE). Il programma Make accetta in input un file di testo provvisto delle regole di compilazione di un progetto, ed esegue ordinatamente tutti i passi necessari per la sua compilazione, assemblando i vari moduli sorgenti (client e server) e linkandoli adeguatamente. Un makefile quindi è un file di testo scritto con una determinata sintassi, spesso di nome makefile (senza estensione), che viene dato in input al programma make.exe. Se il processo è esente da errori, l’output di make sarà il file target (solitamente un file eseguibile) e tutti i files intermedi del caso (solitamente files .obj). Il makefile mainlib.mak per il progetto precedente, risulterebbe quindi come il seguente (le righe che iniziano con # sono commenti): #uso: make –f mainlib.mak .AUTODEPEND mainlib.exe: TLINK mainlib.obj libreria.obj /t libreria.obj: libreria.asm TASM libreria.ASM,libreria.OBJ mainlib.obj: mainlib.asm TASM mainlib.ASM,mainlib.OBJ Il processo di make, infine, si avvia nel seguente modo: C:\>make -f mainlib.mak MAKE Version 3.6 Copyright (c) 1992 Borland International Available memory 15728640 bytes TLINK mainlib.obj libreria.obj /t Turbo Link Version 5.1 Copyright (c) 1992 Borland International C:\> Non è il caso di approfondire il discorso sui makefile, che non rientra negli obiettivi di questo testo. In ogni caso si tratta di un argomento di grande importanza per tutti i linguaggi di programmazione, anche ad alto livello.
View more...
Comments