Descrizione dettagliata dei registri per Assembly



REGISTRI

L’8086 dispone di alcuni elementi di memorizzazione ad alta velocità inseriti nel chip stesso, denominati registri. È possibile pensare ai registri come a locazioni di memoria che l’8086 utilizza con un tempo di accesso inferiore alla memoria tradizionale, ma questa è solo una delle caratteristiche che rendono i registri veramente particolari. Ciascun registro ha una sua propria caratteristica e dispone di particolarità che nessun altro registro o locazione di memoria possiede.

I registri possono essere classificati in quattro categorie: il registro dei flag, i registri di utilizzo generale, il puntatore alle istruzioni e i registri di segmento.

Il registro dei flag

Il registro dei flag è un registro a 16 bit che contiene tutte le informazioni relative allo stato dell’8086 e i risultati delle ultime operazioni.

Bit Number

O

D

I

T

S

Z

A

P

C

Flag Bits

O=Overflow Flag T=Trap Flag A=Auxiliary Carry Flag

D=Direction Flag S=Sign Flag P=Parity Flag

I=Interrupt Flag Z=Zero Flag C=Carry Flag

Per esempio, se si desidera sapere se una sottrazione ha prodotto un risultato uguale a zero, è necessario leggere il flag di zero (il bit Z del registro dei flag) immediatamente dopo l’esecuzione dell’operazione; se tale bit ha valore 1, si è ottenuto un risultato uguale a zero. Gli altri flag come il flag di carry e il flag di overflow riportano in maniera analoga i risultati di operazioni aritmetiche e logiche.

Gli altri flag controllano le modalità delle operazioni dell’8086. Il flag di direzione controlla la direzione di spostamento delle istruzioni che agiscono sulle stringhe, e il flag di interrupt controlla se un dispositivo esterno, come una tastiera o un modem, può interrompere il programma in esecuzione per eseguire funzioni più prioritarie. Il flag di trap viene utilizzato soltanto da programmi che servono per eseguire il debugging di altri programmi.

Il registro dei flag non viene modificato o letto direttamente. Al contrario, il registro dei flag viene gestito attraverso speciali istruzioni (come CLD, STI e CMC) e mediante alcune istruzioni logiche e aritmetiche che possono modificare alcuni flag. In modo analogo, alcuni bit del registro dei flag influenzano i risultati di alcune istruzioni come JZ, RCR e MOVSB. Il registro dei flag non viene utilizzato per la memorizzazione dei dati, ma è, piuttosto, il sistema per controllare lo stato dell’8086.

I registri di utilizzo generale

Gli otto registri di utilizzo generale dell’8086 (ciascuno di 16 bit) sono coinvolti nell’esecuzione di molte istruzioni, come punto di partenza o di arrivo di dati per le operazioni aritmetiche, come puntatori alla memoria e come contatori. Ciascuno dei registri di utilizzo generale può memorizzare qualsiasi valore a 16 bit, può essere utilizzato per operazioni aritmetiche e logiche. Per esempio, le seguenti istruzioni

mov ax,5

mov dx,9

add ax,dx

caricano il valore 5 in AX, caricano il valore 9 in DX e sommano i due valori, memorizzando il risultato, 14, nel registro AX. CX, SI o qualunque altro registro possono essere utilizzati allo stesso modo e tutti danno il medesimo risultato.

Tuttavia, oltre alla capacità di memorizzare valori e servire come sorgente e destinazione per le istruzioni che gestiscono dati, ciascuno dei registri di utilizzo generale possiede caratteristiche proprie. Ora vengono illustrati i singoli registri.

Il registro AX

Il registro AX viene anche chiamato accumulatore. Viene sempre utilizzato durante le operazioni di moltiplicazione e divisione ed è il registro più efficace nell’utilizzo di alcune operazioni aritmetiche, logiche e di spostamento di dati.

Gli 8 bit bassi del registro AX vengono chiamati registro AL (per A-Low), e gli 8 bit alti del registro AX vengono chiamati AH (per A-High). Questa distinzione risulta utile nella gestione di valori che sono di tipo byte, poiché è possibile utilizzare il registro AX come due registri separati. Le istruzioni seguenti impostano AH a 0, copiano il valore in AL, quindi sommano 1 al valore di AL:

mov ah,0

mov al,ah

inc al

Come risultato finale, il registro AX vale 1. i registri BX, CX e DX possono essere utilizzati nello stesso modo sia come registri a 16 bit che come registri a 8 bit.

Il registro BX

Il registro BX può puntare a locazioni di memoria. Un valore a 16 bit memorizzato in BX può essere utilizzato come parte dell’indirizzo a una locazione di memoria da utilizzare. Per esempio, le seguenti istruzioni caricano il registro AL con il contenuto della locazione di memoria che ha indirizzo 9:

mov ax,0

mov ds,ax

mov bx,9

mov al,[bx]

Si noti che è stato caricato nel registro DS il valore 0 (mediante AX) prima di cercare di accedere alla locazione di memoria a cui punta da BX. Ciò dipende dalla natura dei segmenti in cui è suddivisa la memoria dell’8086, come abbiamo precedentemente menzionato. Di norma, quando il registro BX viene utilizzato come puntatore alla memoria, punta all’indirizzo relativo al contenuto del registro di segmento DS.

Il registro CX

Il registro CX ha la funzione specifica di contatore. Si supponga di voler ripetere dieci volte un blocco di istruzioni. È possibile eseguire tale operazione con

mov cx,10

BeginningOfLoop

<istruzioni da ripetere>

sub cx,1

jnz BeginningOfLoop

In questo breve programma sono presenti istruzioni per il momento sconosciute. Il punto principale è che le istruzioni comprese fra label (etichetta) BeginningOfLoop e l’istruzione JNZ vengono ripetute fino a quando il registro CX non assume il valore 0. Si osservi che le due istruzioni SUB CX,1 e JNZ BeginningOfLoop sono necessarie per decrementare CX e saltare alla label BeginningOfLoop se il valore di CX non ha ancora assunto il valore 0.

Decrementare un contatore ed eseguire un ciclo è uno dei blocchi di programma più frequenti; quindi l’8086 mette a disposizione una istruzione apposita per rendere i cicli più veloci e il programma più compatto. Naturalmente tale istruzione è stata chiamata LOOP. L’istruzione LOOP sottrae 1 a CX e riesegue il ciclo se il valore di CX non è 0. Le seguenti istruzioni sono equivalenti all’esempio precedente:

mov cx,10

BeginningOfLoop:

<istruzioni da ripetere>

loop BeginningOfLoop

Il registro CX viene utilizzato come contatore e nei cicli.

Il registro DX

Il registro DX è l’unico registro che può essere utilizzato come puntatore agli indirizzi di input e output nelle istruzioni IN e OUT. Infatti non esiste alcun modo per indirizzare le porte di I/O da 256 a 65.535 senza utilizzare il registro DX. Per esempio, le seguenti istruzioni scrivono il valore 62 sulla porta di I/O che ha indirizzo 1000:

mov al,62

mov dx,1000

out dx,al

L’altra peculiarità del registro DX è relativa alle divisioni e alle moltiplicazioni. Quando si divide un dividendo a 32 bit con un divisore a 16 bit, i 16 bit più alti del dividendo devono essere memorizzati in DX; dopo l’esecuzione delle divisione, il resto della divisione viene memorizzato in DX. (I 16 bit più bassi del dividendo devono essere memorizzati in AX, e il quoziente viene restituito in AX. Analogamente, quando si moltiplicano fattori a 16 bit, i 16 bit più alti del prodotto sono memorizzati in DX (i 16 bit più bassi del prodotto vengono memorizzati in AX).

Il registro SI

Esattamente come il registro BX, il registro SI può essere utilizzato come puntatore alla memoria. Per esempio,

mov ax,0

mov ds,ax

mov si,20

mov al,[si]

carica in AL il valore a 8 bit memorizzato all’indirizzo 20. Il registro SI diventa un potente strumento per l’indirizzamento di locazioni di memoria utilizzate dall’8086 per le istruzioni di gestione di stringhe. Per esempio,

cld

mov ax,0

mov ds,ax

mov si,20

lodsb

non solo carica in AX il valore il valore all’indirizzo di memoria a cui punta SI, ma aggiunge anche 1 a SI.

Tale operazione risulta molto efficace quando è necessario accedere a locazioni successive di memoria, come una stringa di testo. Ancora meglio, le istruzioni su stringhe possono essere eseguite automaticamente un certo numero di volte; in tal modo è possibile eseguire con una sola istruzione centinaia o migliaia di operazioni.

Il registro DI

Il registro DI è molto simile al registro SI infatti può essere utilizzato come puntatore alla memoria e possiede caratteristiche particolari se utilizzato con istruzioni su stringhe. Per esempio,

mov ax,0

mov ds,ax

mov di,1024

add bl,[di]

somma a BL il valore a 8 bit memorizzato all’indirizzo 1024. il registro DI è leggermente differente da SI quando viene utilizzato in istruzioni su stringhe; se SI ha la funzione di puntatore alla memoria origine per le istruzioni di gestione delle stringhe, DI ha sempre la funzione di puntatore alla destinazione. Inoltre, con le istruzioni su stringhe, SI, di solito, punta alla memoria relativa al registro DS, mentre DI punta sempre alla memoria relativa al registro ES. (Quando SI e DI vengono utilizzati come puntatori di memoria in istruzioni che non coinvolgono stringhe, puntano sempre alla memoria relativa a DS). Per esempio,

cld

mov dx,0

mov es,dx

mov di,2048

stosb

utilizza la funzione STOBS per memorizzare il valore di AL nell’indirizzo di memoria a cui punta DI e contemporaneamente per aggiungere 1 a DI. È necessario, tuttavia, esaminare in primo luogo i segmenti e i relativi registri, prima di illustrare le caratteristiche delle istruzioni su stringhe.

Il registro BP

Come BX, SI e DI il registro BP può essere utilizzato come puntatore alla memoria, ma con una differenza. Mentre i registri BX, SI e DI agiscono normalmente come puntatori alla memoria relativamente al registro DS (o, nel caso di DI utilizzato in istruzioni su stringhe, relativamente a ES), BP punta relativamente a SS, il registro di segmento STACK.

Non sono ancora stati esaminati i segmenti, ma il concetto fondamentale è il seguente: un modo utile per passare i parametri a subroutine è quello di memorizzarli nello stack. Il C e il Pascal adottano tale tecnica.

Lo stack risiede nel segmento a cui punta SS, o segmento STACK. I dati, d’altra parte, risiedono generalmente nel segmento a cui punta DS, o segmento dei dati. Poiché normalmente BX, SI e DI puntano al segmento dei dati, non è efficace utilizzare BX, SI o DI per puntare ai parametri passati nello stack poiché lo stack normalmente si trova in un segmento diverso.

Il registro BP risolve tale problema indirizzando direttamente il segmento STACK. Per esempio,

push bp

mov bp,sp

mov ax,[bp+4]

accede al segmento STACK per caricare in AX il primo parametro passato da una chiamata in Turbo C ad una routine assembly.

In breve, BP è progettato appositamente per gestire i parametri, le variabili locali ed altre necessità di indirizzamento della memoria nello stack.

Il registro SP

Il registro SP, altrimenti chiamato puntatore allo stack, è il meno generale dei registri di utilizzo generale, poiché è quasi completamente dedicato a uno scopo preciso: gestire lo stack. Lo stack è un’area di memoria in cui è possibile memorizzare e leggere valori in modalità last-in, first-out (l’ultimo che è entrato è il primo ad uscire). La classica analogia con lo stack è una pila di piatti. Poiché è possibile aggiungere piatti solo in cima alla pila e rimuoverli da essa, ciò rende evidente il motivo per cui il primo piatto che è stato messo è anche l’ultimo che viene rimosso.

Il registro SP punta in ogni momento alla cima dello stack; come una pila di piatti, la cima dello stack è la locazione in cui il valore successivo da memorizzare verrà inserito. L’azione che consiste nell’inserire un valore nello stack viene chiamata pushing, e, infatti viene utilizzata un’istruzione PUSH per memorizzare valori nello stack. In maniera analoga l’azione che consente di recuperare valori nello stack viene chiamata popping e viene utilizzata l’istruzione POP.

Anche se l’8086 permette di memorizzare valori in SP, aggiungere o sottrarre valori memorizzati in SP, esattamente come per tutti gli altri registri di utilizzo generale, quest’ultima è un’operazione da evitare, a meno di non sapere esattamente ciò che si sta facendo. Se si modifica il registro SP, varia la locazione della cima dello stack, e ciò può provocare gravi malfunzionamenti.

Il motivo è semplice: le istruzioni di push e pop sono l’unico modo per utilizzare lo stack. Tutte le volte che si attiva o si termina l’esecuzione di una routine (una procedura, o una funzione), viene utilizzato lo stack. Inoltre, alcune risorse di sistema, come la tastiera e il clock di sistema, utilizzano lo stack per interrompere l’8086 per poter eseguire le proprie funzioni. Ciò significa che lo stack viene utilizzato sempre. Se il valore di SP viene modificato, anche per poche istruzioni, può capitare che lo stack non possa essere disponibile quando una risorsa di sistema intende utilizzarlo.

In definitiva non si deve modificare il registro SP a meno che non sia indispensabile. È possibile eseguire le istruzioni di push, di pop, di routine, ma non si deve modificare direttamente il valore di SP, al contrario di tutti gli altri registri di utilizzo generale.

Il puntatore alle istruzioni

Il puntatore alle istruzioni (IP) contiene sempre l’offset (scostamento) della memoria in cui è memorizzata la successiva istruzione da eseguire. Non appena viene eseguita un’istruzione il puntatore alle istruzioni viene incrementato, in modo da puntare all’indirizzo di memoria successivo. Generalmente, l’istruzione nell’indirizzo successivo di memoria è l’istruzione da eseguire successivamente, ma alcune istruzioni, come le chiamate e i salti, possono fare in modo che venga caricato un valore diverso, per attivare un’istruzione diversa.

Il puntatore alle istruzioni non può essere letto o modificato direttamente; solo le istruzioni di salto come quelle descritte possono modificare il valore del puntatore alle istruzioni.

Il puntatore alle istruzioni non è sufficiente per specificare l’indirizzo in cui è memorizzata l’istruzione da eseguire successivamente. Ancora una volta la natura segmentata dell’indirizzamento della memoria dell’8086 complica il quadro. Per il fetch (prelievo) delle istruzioni, il registro CS contiene un indirizzo base e il puntatore alle istruzioni, quindi, contiene un offset rispetto a quell’indirizzo base.

Tutte le volte che si è citato l’indirizzamento di memoria si è parlato di segmenti e ogni volta è stata rimandata la spiegazione completa. È giunto il momento di analizzare i segmenti di memoria.

I registri di segmento

Verrà qui analizzata una caratteristica abbastanza insolita dell’8086: la segmentazione della memoria. L’8086 è in grado di indirizzare 1 Mbyte di memoria. Sono necessari 20 bit per indirizzare tutte le locazioni di memoria in un Mbyte. Tuttavia, l’8086 utilizza puntatori a 16 bit, come, per esempio, il registro BX. Come può l’8086 utilizzare tutta la memoria con registri a 16 bit, mentre ne servirebbero a 20 bit?

La risposta è che l’8086 utilizza uno schema di indirizzamento in due parti. È vero che vengono utilizzati puntatori a 16 bit, ma essi costituiscono solo una parte dell’indirizzamento completo. Ciascuno dei puntatori a 16 bit, o offset, viene combinato con il contenuto di un registro a 16 bit per formare un indirizzo di memoria a 20 bit.

I segmenti e gli offset vengono combinati nel seguente modo: il valore del segmento viene spostato a sinistra di 4 bit (moltiplicato per 16) e quindi addizionato all’offset.

Si considerino, per esempio le seguenti istruzioni:

mov ax,1000h

mov ds,ax

mov si,201h

mov dl,[si]

Il registro di segmento DS assume il valore 1000h, e SI vale 201h, che possono essere rappresentati come segmento:offset,cioè 1000:201. (i calcoli sui segmenti e gli offset vengono generalmente eseguiti in base 16, un’altra buona ragione per familiarizzarsi con la notazione esadecimali). In DL viene caricato il contenuto della memoria all’indirizzo ((DS*16) + SI), o ((1000h*16) + 201h):

1000h

* 16

_______

10000h

+ 201h

_______

10201h

Un’altro modo per ottenere il medesimo risultato è quello di spostare il valore segmento a sinistra di 4 bit, o una cifra esadecimali, che equivale a moltiplicare per 16:

10000

+ 201

_____

10201

È facile capire ora che i programmi possono accedere esclusivamente alla memoria si 1 Mbyte dell’8086 utilizzando la coppia di valori segmento:offset. In effetti, è sempre necessario utilizzare la coppia segmento:offset per indirizzare la memoria; tutte le istruzioni e i modi di indirizzamento dell’8086 fanno riferimento a uno dei registri di segmento, anche se per alcune istruzioni è possibile forzare l’utilizzo di un altro registro di segmento, se necessario.

Raramente verrà caricato un numero in un registro di segmento. Al contrario, verranno caricati i registri con i nomi dei segmenti, che verranno tradotti in numeri durante il processo di compilazione, linking ed esecuzione di un programma. Tale operazione è necessaria, in quanto non è possibile stabilire a priori dove verrà posizionato in memoria un determinato segmento; infatti, ciò dipende dalla versione del DOS, dal numero e dall’occupazione di eventuali programmi residenti e dalle necessità di memoria della parte restante del programma. Utilizzando i nomi dei segmenti tali definizioni vengono domandate al DOS e a Turbo Assembler.

Il nome di segmento più comune è @data, che fa riferimento al segmento dei dati predefinito, quando vengono utilizzate direttive semplificate. Per esempio,

.MODEL small

.DATA

Var1 DW 0

.CODE

mov ax,@data

mov ds,ax

END

fa in modo che DS punti al segmento dei dati, in cui è memorizzata Var1.

L’utilizzo dei segmenti nell’8086 ha interessanti implicazioni. Da un lato, soltanto 64K di memoria possono essere indirizzati da un registro di segmento poiché 64K è la massima quantità di memoria che può essere indirizzata utilizzando un offset di 16 bit. Ciò significa che non è molto semplice l’indirizzamento di grandi blocchi di dati (superiori a 64K), poiché sia il registro di segmento che l’offset devono essere modificati frequentemente.

L’indirizzamento di grandi blocchi di memoria nell’8086 è ancora più difficile, in quanto, a differenza degli altri registri di utilizzo generale, i registri di segmento non possono essere usati come sorgente e destinazione per operazioni aritmetiche e logiche. In effetti, l’unica operazione che può essere eseguita sui registri di segmento riguarda la copia di valori fra i registri di segmento e i registri di utilizzo generale o la memoria. Per esempio, addizionare 100 al registro ES richiede le seguenti istruzioni:

mov ax,es

add ax,100

mov es,ax

Ne deriva quindi, che l’8086 è progettato per gestire blocchi di memoria non superiori a 64K.

Una seconda conseguenza dell’utilizzo dei segmenti è che ogni locazione di memoria può essere indirizzata con molte coppie possibili segmento:offset. Per esempio, l’indirizzo di memoria 100h è indirizzabile da valori segmento:offset di 0:100h, 1:F0h, 2:E0h e così via.

Come i registri di utilizzo generale, ciascun registro di segmento ha una funzione ben specifica. Il registro CS punta alle istruzioni del programma (Codice), il registro DS punta ai Dati, il registro SS punta allo Stack e il registro ES è un jolly (“Extra”), e può puntare dove è necessario. Quanto segue è una descrizione più approfondita sui registri di segmento.

Il registro CS

Il registro CS punta all’inizio del blocco di 64K di memoria o segmento CODE, in cui è memorizzata la successiva istruzione da eseguire. La successiva istruzione da eseguire risiede a un offset specificato da IP nel segmento CODE; cioè all’indirizzo segmento:offset CS:IP. L’8086 non è in grado di eseguire un fetch a un’istruzione memorizzata in un segmento diverso da CS.

Il registro CS può essere modificato da alcune istruzioni, compresi alcuni jump, call e return, e non può essere caricato direttamente in alcun modo.

Nessuna modalità di indirizzamento o puntatori alla memoria diversi da IP vengono utilizzati con il registro CS.

Il registro DS

Il registro DS punta all’inizio del segmento dei dati, che è il blocco di 64K in cui è memorizzata la maggior parte degli operandi. Generalmente, gli offset che coinvolgono i registri BX, SI o DI sono relativi a DS, poiché eseguono indirizzamento diretti alla memoria. Il segmento dei dati, come è implicito nel suo nome, è il segmento in cui risiedono i dati.

Il registro ES

Il registro ES punta all’inizio del blocco di memoria di 64K chiamato segmento EXTRA. Come indica il nome, il segmento EXTRA non ha una funzione precisa, ma è disponibile per qualunque necessità. Talvolta, il segmento EXTRA viene utilizzato come un blocco di 64K addizionali di memoria per i dati, ma l’accesso è di solito meno efficiente rispetto al segmento dati.

L’utilizzo principale del segmento EXTRA è nella gestione delle stringhe. Tutte le istruzioni su stringhe che scrivono in memoria utilizzano ES:DI come indirizzo della memoria nella quale scrivere. Ciò significa che ES è molto utile come segmento di destinazione per la copia di blocchi, confronti fra stringhe, analisi della memoria e azzeramento di blocchi di memoria.

Il registro SS

Il registro SS punta all’inizio del segmento STACK, che è un blocco di memoria di 64K che contiene lo stack. Tutte le istruzioni che utilizzano lo stack, come le istruzioni di push, pop, call e return, utilizzano il segmento STACK poiché SP è l’unico registro in grado di indirizzare la memoria nel segmento STACK.

Come illustrato precedentemente il registro, anche il registro BP lavora relativamente al segmento STACK. Ciò permette di utilizzare BP per indirizzare parametri e variabili che sono memorizzate nello stack.

Annunci sponsorizzati:

Ricerche effettuate:

  • registro assembly si
Condividi su Facebook Condividi su Twitter!
Pinterest