assembly 8086 -...
TRANSCRIPT
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 1
Assembly 8086 - Introduzione Rev. Digitale 1.0 del 01/09/2016
E‟ un linguaggio mnemonico in corrispondenza 1 : 1 con le istruzioni binarie riconosciute dalla CPU 8086. Cioè
codifica con un nome mnemonico tutte le possibili istruzioni binarie riconosciute dalla CPU.
Assemblatore
Un particolare programma, detto assemblatore (assembler), traduce il file testuale assembly in linguaggio macchina,
producendo un file binario che contiene il codice binario corrispondente al programma tradotto. L‟assemblatore, oltre
al file OBJ, produce in genere anche un interessante file LST contenente l‟informazione binaria espressa in formato
esadecimale con a fianco l‟istruzione testuale assembly.
Linker
Molto spesso una applicazione è costituita da più file sorgenti, ognuno dei quali contiene specifiche procedure, scritte
spesso da persone diverse all‟interno di un team di lavoro. Uno di questi file può far riferimento a funzioni o simboli
definiti all‟interno di un altro file. L‟assemblatore, che traduce ogni singolo file in binario, deve prevedere un
meccanismo che consenta di gestire queste situazioni senza generare errori. A tal fine l‟assembly prevede l‟utilizzo di
pseudoistruzioni che avvisano l‟assemblatore su simboli e procedure che si trovano in altri file. In corrispondenza di
queste pseudoistruzioni, l‟assemblatore crea delle „note‟ all‟interno del file binario, che dunque non è più un file
eseguibile, ma un file detto file oggetto, sempre binario, ma contenente delle note che verranno interpretato da un
programma detto linker, che provvede ad integrare i vari file oggetto in un unico eseguibile risolvendo tutte le varie
situazioni lasciate in sospeso dall‟assemblatore.
Loader
In realtà il codice macchina generato dal linker è un codice rilocabile, cioè che potrà essere caricato in memoria
centrale a partire da un qualunque indirizzo. Ciò è possibile mediante l‟utilizzo dei Segment Register il cui contenuto
viene assegnato dal loader durante il caricamento in memoria dell‟applicazione. Il loader è un componente del
sistema operativo che viene automaticamente richiamato nel momento in cui l‟utente digita sulla riga di comando il
nome di un file eseguibile (o fa doppio click sulla sua icona). Il loader provvede a trasferire il codice dell‟eseguibile
all‟interno della memoria centrale, provvedendo ad assegnare un valore ai vari segmenti definiti dal linker.
Caratteristiche di MASM 6.0 (Macro Assembler)
Ogni riga deve essere terminata con un INVIO (ASCII 10).
Uno statement può occupare più righe. La precedente deve terminare con \. La seguente deve iniziare con &
E‟ case unsensitive: si può scrivere indifferentemente in minuscolo o maiuscolo
Normalmente si usa il minuscolo, riservando il maiuscolo per PSEUDOISTRUZIONI e COSTANTI
I commenti sono introdotti dal punto virgola
Gli identificatori hanno lunghezza massima 31 chr (senza spazi) e devono iniziare con una lettera.
Sono ammessi i seguenti 4 caratteri speciali _ ? @ $
Principali Direttive Assembly (Pseudoistruzioni)
Non corrispondono ad una precisa istruzione binaria di codice, ma sono direttive che consentono di :
Definire dimensioni e tipologia dei vari segmenti (l‟unico ad essere dimensionato automaticamente è il Code
Segment). Queste informazioni vengono salvate dal linker all‟interno dell‟header del programma exe
secondo un preciso formato che è quello utilizzato dal loader del SO per caricare il programmi.
Assegnare un nome alle celle di memoria del Data Segment. In questo modo quando il programma dovrà
accedere ad una cella di memoria potrà accedere molto più comodamente attraverso il nome piuttosto che
attraverso l‟indirizzo.
Assegnare un nome (etichetta) a particolari righe di codice, in modo che quando il programma dovrà eseguire
un salto a quella riga potrà utilizzare l‟etichetta anziché l‟indirizzo.
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 2
Title
La direttiva TITLE consente di assegnare un titolo descrittivo al programma assembly. TITLE *** Calcolo di Area e Perimetro di un Rettangolo ***
Costanti
Subito dopo il titolo si possono definire, mediante la direttiva EQU delle costanti, cioè assegnare una etichetta
identificativa ad un valore, in modo da aumentare la leggibilità del programma.
Le costanti possono essere :
Decimali: 13, 13D
Binarie 00001111B
Esadecimali 72H, 0DH, 0A1H
devono iniziare con un numero. Se iniziano con una lettera occorre premettere uno zero 0A1H
Floating Point 2.345, 715E-3
Stringhe ASCII „CIAO‟, „2‟ ; lunghe uno o più caratteri. Apice Singolo e Apice Doppio sono indifferenti
Esempi: COLUMNS EQU 80
ROWS EQU 25
E‟ possibile anche creare degli alias, cioè simboli che rappresentano con altri nomi simboli già definiti in precedenza.
COLONNE EQU COLUMNS
RIGHE EQU ROWS
SCREEN EQU COLONNE * RIGHE
In alternativa ad EQU per definire delle costanti è anche possibile utilizzare l‟operatore di assegnazione = .
Direttive di Segmento
I programmi Assembly sono costituiti tipicamente da 3 segmenti: CODICE, DATI e STACK.
Per definire un segmento occorre utilizzare la seguente direttiva:
nomeSeg SEGMENT contenuto del segmento
nomeSeg ENDS
Definizione di variabili
All‟interno del Data Segmenti si possono “definire” delle celle di memoria, assegnando loro una etichetta
identificativa ed un valore. L‟etichetta potrà poi essere utilizzata dal programma per accedere alle celle medesime.
Dopo l‟etichetta i due punti di suddivisione dell‟etichetta dalla parte successiva non sono in questo caso accettati.
db definisce un byte
num1 db 60
num2 db ? ; definisce un byte senza assegnare nessun valore iniziale
num3 db 00001110b
Può essere anche utilizzata per memorizzare uno o più caratteri ASCII
var1 db „A‟, „B‟, „C‟ ; definisce tre byte contenenti i codici ASCII dei caratteri indicati
var2 db “ABC” ; equivalente alla precedente
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 3
dw definisce una word (2 bytes)
num1 dw 260
num2 dw ? ; definisce una word senza assegnare nessun valore iniziale
num3 dw AAFFH
Nota: dw non può essere utilizzata per memorizzare una sequenza di caratteri ASCII, ma solo per uno o due caratteri chr dw „A‟ ; viene memorizzato 0 nel byte alto e 65 nel byte basso
chr dw “AB” ; viene memorizzato 65 nel byte alto e 66 nel byte basso
Vettori
Per i nomi dei vettori è consigliato anteporre l‟underscore davanti.
_vect db 1,2,3,4,5,6,7,8,9,0 ;definisce un vettore di 10 bytes inizializzandolo con i valori indicati
_vect dw 1200, 2400, 3600 ;definisce un vettore di 3 word
_vect db 10 dup (0) ;definisce un vettore di 10 bytes tutti con valore 0
_vect dw 10 dup (?) ;definisce un vettore di 10 word non inizializzate
Nota: Per visualizzare il contenuto di una variabile NUM1 su code view utilizzare il comando W NUM1
Per visualizzare invece un vettore _vett lungo 10 su code view utilizzare il comando W _vett L A
Nota: dw può anche essere utilizzata per memorizzare un offset:
lista DB 100 DUP (?) ; 100 celle non inizializzate
listaOffset DW lista ; offset di lista
dd definisce una double word (4 bytes)
num1 dd 125000 ; maggiore di 65000
num2 dd 13.76 ; floating point
dq definisce una quad word (8 bytes). Numeri Double. Non prevista nelle versioni base di Assembly 8086
Nota: dd può anche essere utilizzata per memorizzare un indirizzo completo (segmento + offset) :
lista DB 100 DUP (?) ; 100 celle non inizializzate
listaOffset DD lista ; i due bytes alti contengono il segmento, i due byte bassi contengono l’offset
La direttiva END
Terminata la dichiarazione di tutti i segmenti, la direttiva END termina il modulo di compilazione. Il suo scopo è
quello di poter specificare una etichetta per indicare il punto di partenza del programma (entry point) .
In caso di unico segmento di codice, l‟entry point può essere omesso, nel qual caso il programma partirà dalla 1°
istruzione dell‟unico segmento di codice. Il loader provvede automaticamente a caricare CS e inizializzare PC a 0
In caso di più segmenti di codice, l‟entry point deve essere necessariamente specificato ed il loader caricherà
automaticamente all‟interno di CS l‟indirizzo di partenza del segmento contenente l‟entry point e dentro PC l‟offset
dell‟Entry Point rispetto all‟inizio del segmento. Esempio: END main
Etichette relative alle istruzioni
Davanti alle istruzioni si può aggiungere una etichetta alfanumerica (con primo carattere una lettera) che rappresenta
l‟indirizzo simbolico dell‟istruzione. I due punti devono separare l‟etichetta dall‟istruzione. Queste etichette sono
utilizzate principalmente per gestire le istruzioni di salto:
RIT: mov AX, 0
. . . . . . . . . . . . .
jmp RIT
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 4
Modello di un programma Assembly
.model small ; un solo segmento di codice ed un solo segmento di dati
.stack ; definisce uno stack segment di nome @stack grande 1024 bytes
; stack ends
.data ; definisce un data segment di nome @data
num1 dw 61
num2 dw 4
ris dw ?
; data ends
.code ; definisce un code segment di nome @code
; CS ed SS sono inizializzati automaticamente ai rispettivi segmenti
; DS non viene caricato in automatico in quanto possono esistere più dichiarazioni di DATA
SEGMENT all‟interno di file differenti. Queste dichiarazioni, nell‟ambito del MODEL
SMALL, verranno poi combinate in un unico segmento in un ordine che solo il linker conosce mov ax, @data
mov ds, ax
mov ax,num1 ; AX <- DS:NUM1
add ax,num2
mov ris, ax
mov ah, 4ch ; ritorno al DOS
int 21h
; code ends
end
Modelli di mappatura dei segmenti : La pseudoistruzione .MODEL
Consente di definire il modello di memoria che dovrà essere utilizzato nella compilazione del file. Modelli possibili:
Code Seg Data Seg Data e Code Combinati TINY Near Near Sì
SMALL Near Near No
MEDIUM Far Near No
COMPACT Near Far No
LARGE Far Far No
Dichiarare i segmenti di codice di tipo NEAR significa che tutti i segmenti di codice appartenenti ai vari moduli
verranno mappati su uno stesso segmento fisico di codice. Ciò implica che tutte le procedure e tutte le etichette
di salto saranno indirizzate come NEAR, cioè indirizzate utilizzando soltanto offset a 16 bit. Dichiarare invece i
segmenti di codice di tipo FAR significa che ogni segmento (eventualmente anche per segmenti appartenenti ad uno
stesso modulo) verrà mappato su un segmento di codice fisico diverso. Ciò implica che tutte le procedure e tutte le
etichette saranno indirizzate come FAR, cioè indirizzate utilizzando segmento + offset.
Dichiarare i data segment come NEAR significa che variabili sono indirizzate come NEAR, cioè soltanto mediante
un offset a 16 bit, mentre FAR significa che saranno tutte indirizzate mediante segmento + offset, cioè posso
indirizzare direttamente una variabile non contenuta nei segmenti attivi: MOV AX, FAR PTR _DATA2:NUM1
Nel modello TINY codice e dati vengono mappati in uno stesso segmento (vecchi file .COM più compatti degli
EXE). Il modello SMALL (utilizzato di default) prevede un unico segmento di codice con dim max 64 K e
l‟accesso soltanto ai dati memorizzati nei due data segment attivi . Per le applicazioni più grandi si utilizza il
modello LARGE che può gestire più segmenti di codice e più segmenti di dati paralleli.
Nei sistemi IA32 (che operano in modalità protetta), si utilizza un nuovo tipo di modello che è il modello FLAT.
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 5
Formato ed avvio di un programma eseguibile
Nel momento in cui il linker provvede a creare il file eseguibile, provvede anche a generare all‟inizio del file stesso
una testata lunga 512 bytes contenente le informazioni necessarie al caricamento del programma in memoria, cioè
sostanzialmente un elenco dei segmenti definiti all‟interno del programma stesso, ciascuno con il proprio indirizzo
virtuale di partenza (partendo da 0) e lunghezza espressa in byte.
Il linker, a richiesta, può generare un file .MAP contenente le informazioni che verranno salvate in testa all‟eseguibile
Start Stop Length Type (Name)
00000H 00003H 00004H DATA @DATA
00010H 0008FH 00080H STACK @STACK
00090H 000BCH 0002DH CODE @CODE
Program entry point at 0009:0000
Il Name è un identificativo mostrato all‟interno del file .MAP. In fase di avvio del programma, il loader provvede ad
allocare in memoria i segmenti necessari, assegnando ai vari segmenti un ben preciso indirizzo fisico.
Inoltre provvede automaticamente a:
inizializzare il registro CS al punto di partenza del segmento di codice contenente l‟Entry Point del programma
inizializzare il registro SS al registro unico di stack dichiarato con l‟opzione di classe „STACK‟
Riservare, al di sopra del segmento di codice, un‟area di 256 bytes (100H) detta PSP (Program Segment
Prefix, cioè Prefisso del Segmento di Programma) utilizzato dal DOS per memorizzare le informazioni
necessarie a ritrasferire il controllo al Sistema Operativo una volta terminata l‟esecuzione del programma.
L‟istruzione finale INT21H (4CH) esegue sostanzialmente un salto all‟indirizzo _CODE – 100H. DS ed ES
vengono entrambi utilizzati per scrivere quest‟area per cui, al termine del caricamento punteranno entrambi a
_CODE – 100H e dovranno essere inizializzati manualmente dal programma andando a scrivere al loro interno
l‟indirizzo effettivo di partenza del Data Segment.
L’istruzione MOVE e le modalità di Indirizzamento
MOV Dest, Source
E‟ l‟istruzione principale del linguaggio Assembly e consente di spostare dati tra una sorgente ed una destinazione,
dove sorgente e destinazione possono essere i registri della CPU o le celle di memoria. Il codice binario è il seguente:
100010dw (in esadecimale 88 o 89 o 8A o 8B) dove:
il bit w indica se deve essere trasferito un byte (w=0) oppure, più frequentemente, una word (w=1)
il bit d indica la direzione del trasferimento. d = 1 significa from MFIELD to REGister e viceversa
Modalità di Indirizzamento
Le modalità fondamentali sono 5 :
(1) Registro – Registro
(2) Indirizzamento diretto
(3) Indirizzamento indiretto
(4) Indirizzamento indiretto con displacement
(5) Indirizzamento immediato (in un registro o in memoria)
Insieme all‟istruzione MOV c‟è sempre un secondo byte che indica quale tipo di indirizzamento utilizzare e quali
sono i registri coinvolti. Il secondo byte è strutturato nel modo seguente:
MM RRR FFF dove i tre gruppi di bit sono indicati rispettivamente come MOD REG MFIELD
MOD(2 bit) indicano la modalità di trasferimento da adottare fra le prime 4 modalità precedenti
REG (Register su 3 bit ) indicano il registro coinvolto nel trasferimento (8 registri possibili)
MFIELD (Memory Field su 3 bit) indicano il registro o cella di memoria coinvolti nel trasferimento.
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 6
Il significato di MFIELD dipende dalla modalità di trasferimento adottata.
Il campo REG ha sempre il seguente significato :
REG W=0 W=1
000 AL AX
001 CL CX
010 DL DX
011 BL BX
100 AH SP
101 CH BP
110 DH SI
111 BH DI
(1) Modalità di trasferimento Registro – Registro (MOD = 11 )
In questo caso MFIELD utilizza la stesa codifica di REG, e contiene il codice del secondo registro coinvolto
nell‟operazione. Nella modalità REGISTER – REGISTER il bit d è sempre 1, cioè :
MFIELD rappresenta sempre il registro sorgente, mentre REG rappresenta sempre il registro destinatario.
mov CX, BX ; d = 1, w = 1, REG = 001, MFIELD = 011 8B CB
mov CL, BL ; d = 1, w = 0, REG = 001, MFIELD = 011 8A CB
mov CH, CL ; d = 1, w = 0, REG = 101, MFIELD = 001 8A E9
(2) Modalità di trasferimento diretto da memoria a registro e viceversa (MOD = 00 e MFIELD = 110)
Il campo MFILED assume il valore fisso 110.
Il campo REG indica il registro destinatario (d=1) oppure il registro sorgente (d=0).
num1 db 60
num2 dw 600
mov DH, num1 ; d = 1, w = 0, REG = 110, MFIELD = 110 + offset 2 bytes
mov num1, DH ; d = 0, w = 0, REG = 110, MFIELD = 110 + offset 2 bytes
mov DX, [num2] ; d = 1, w = 1, REG = 010, MFIELD = 110 + offset 2 bytes
mov [num2], DX ; d = 0, w = 1, REG = 010, MFIELD = 110 + offset 2 bytes 89 16
Oltre ai due bytes relativi a codice ed indirizzamento ci sono questa volta anche 2 bytes contenenti l‟offset della
variabile di memoria. Le parentesi quadre intorno al nome della variabile sono opzionali. Generalmente sono omesse.
L‟offset si intende sempre riferito a DS, salvo indicazione esplicita di segment override. mov DH, ES:num1
Le parentesi quadre diventano obbligatorie quando al posto dell‟etichetta si utilizza un offset scritto in modo diretto
(per non confondere l‟offset con un dato immediato).
mov DS:[3], DX ; copia DX nella quarta cella del Data Segment
(3) Modalità di trasferimento indiretto senza displacement (MOD = 00 e MFIELD != 110)
Effettuare un trasferimento indiretto significa non specificare direttamente il nome della cella di memoria da leggere
o scrivere, ma scrivere il suo offset all’interno di un apposito registro (normalmente SI o DI).
. Es MOV AL, [SI] copia dentro AL il contenuto della cella puntata da SI.
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 7
Esempio: Somma dei numeri contenuti in un vettore
.data
0 num1 db ?
1 num2 db ?
2 ris dw ?
4 vect db 10 dup (12, 5, 6, 8, 7, 15, 6, 21, 5, 11)
.code
mov AX @data
MOV DS, AX
MOV SI, 4
MOV AX, 0
RIT: ADD AL, [SI] ; somma AL + la cella puntata da SI rispetto all‟inizio del Data Segment
INC SI
CMP SI, 14
JB RIT
MOV RIS, AX
(4) Modalità di trasferimento indiretto con displacement (MOD = 01 e 10)
MOD 01 indica un displacement a 8 bit, mentre MOD = 10 indica un displacement a 16 bit.
Il displacement sarà memorizzato di seguito all‟istruzione. Viene automaticamente scelto il displacement a 8 bit per
offset inferiori a 255, mentre viene automaticamente scelto un displacement a 16 bit per offset maggiori di 255
Esempio: Riscrittura del codice precedente con utilizzo di un displacement
.data
0 num1 db ?
1 num2 db ?
2 ris dw ?
4 vect db 10 dup (12, 5, 6, 8, 7, 15, 6, 21, 5, 11)
.code
mov AX @data
MOV DS, AX
MOV SI, 0
MOV AX, 0
RIT: ADD AL, vect[SI] ; somma AL + la cella puntata da SI rispetto a VECT
INC SI
CMP SI, 10
JB RIT
MOV RIS, AX
(5a) Trasferimento di un dato immediato in un registro o in memoria
L‟istruzione di trasferimento di un dato immediato è una istruzione MOV con codifica differente rispetto alla
codifica precedente. Trasferire un dati immediato in un registro (o in memoria) significa che il dato è contenuto
all’interno dell’istruzione stessa. Il trasferimento diretto può essere solo TO register o TO memory (non FROM)
MOV AX, 70 MOV NUM, 70 MOV [SI], 70 MOV [SI+5], 70 MOV VECT[SI], 70
Combinazioni non ammesse dall’istruzione MOV
il registro IP non può essere utilizzato né in scrittura né in lettura
il registro CS non può essere destinazione
memoria – memoria. Si deve passare attraverso un registro
segment register – segment register.
Caricamento di un numero immediato in un segment register
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 8
Breve panoramica sulle Istruzioni Assembly
Istruzioni Aritmetiche
ADD op1, op2 op1 op1 + op2
SUB op1, op2 op1 op1 - op2
MUL op2 AX AL * OP 8bit
DX : AX AX * OP 16bit
DIV op2 AX / OP 8bit => AL quoziente, AH resto
DX : AX / OP 16bit => AX quoziente, DX resto
L’istruzione JMP
L‟istruzione JMP esegue un salto incondizionato all‟etichetta indicata:
_RIT : mov AX, 0
. . . . . . . . . .
. . . . . . . . . .
JMP _RIT
L‟etichetta rappresenta un offset a 16 bit (± 32000 bytes rispetto alla posizione corrente) all‟interno del segmento
corrente. La codifica binaria dell‟istruzione JMP occupa normalmente 3 byte. L‟assembler può generare
automaticamente una istruzione su 2 soli byte nel momento in cui si accorge che l‟offset può essere memorizzato
all‟interno di un singolo byte (± 127 bytes rispetto alla posizione corrente).
In corrispondenza del jump, il processore provvede automaticamente a caricare all’interno del Program
Counter l’indirizzo dell’etichetta (offset) a cui si richiede di eseguire il salto. Il valore corrente del Program
Counter viene sovrascritto e dunque perso definitivamente.
Nota 1: Salto ad un offset contenuto in un registro
Oltre ad una etichetta diretta, il jump può anche essere eseguito alla cella puntata da un registro indicato nel
successivo byte di indirizzamento così strutturato:
JMP [SI] MOD 100 MFIELD
dove MOD e MFIELD sono gli stessi della MOV (non è consentito MOD = 11 indicante Register Register).
Nota 2: Salto ad una label di un altro segmento
L’indirizzo a cui viene trasferito il controllo può essere nello stesso segmento (jump NEAR) oppure può anche
appartenere ad un segmento diverso (jump FAR), nel qual caso occorrerà definire davanti al punto di salto,mediante
la pseudoistruzione LABEL FAR, una etichetta a 4 byte.
seg1 SEGMENT
ETICHETTA1 LABEL FAR : . . . . . . . . . . . .
seg1 ENDS
seg2 SEGMENT
JMP FAR PTR Seg1:ETICHETTA1
seg2 ENDS
Nel caso di un JUMP FAR, l‟assemblatore provvede a
scrivere, dopo il codice dell‟istruzione jump, sia il
segmento sia l‟offset a cui deve essere eseguito il salto.
L‟indirizzo di segmento verrà caricato dentro CS, mentre
l‟offset verrà caricato nel Program Counter
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 9
L’istruzione CMP
L‟istruzione CMP (compare) supporta all‟incirca tutte le stesse modalità di indirizzamento dell‟istruzione MOV.
CMP esegue la differenza tra il primo operando meno il secondo operando, senza memorizzare alcun risultato,
ma provvedendo soltanto a settare i flag, in particolare
ZF se il risultato della differenza è zero
SF contenente il segno del risultato
CF carry flag
OF overflow flag
Istruzioni di salto condizionato
Le istruzioni di salto condizionato sono di solito utilizzate immediatamente dopo la COMPARE, che esegue un
confronto fra due numeri. A seconda dell‟esito del confronto (valore dei bit di stato) il salto potrà essere eseguito
oppure non eseguito.
Le istruzioni di salto condizionato possono comunque essere utilizzate anche dopo ADD, SUB o altre istruzioni,
sempre sulla base del valore corrente dei bit di stato.
Operandi Unsigned
Se gli operandi del CMP sono numeri unsigned, si possono utilizzare le seguenti istruzioni di salto condizionato, che
utilizzano un offset di salto memorizzato su un solo byte (per cui il salto può avere una distanza massima di ± 127
bytes). Dunque sono istruzioni più veloci ma:
Possono essere utilizzate per confrontare soltanto numeri positivi
Possono eseguire al max un salto pari a ± 127 bytes
JA jump if above. Salta se NUM1 > NUM2.
JAE jump if above or equal
JB jump if below. Salta se NUM1 < NUM2.
JBE jump if below or equal
Nota: Tecnicamente JA esegue il salto se CF e ZF sono entrambi uguali a zero cioè se non c’è prestito nella
sottrazione (cioè se NUM1 > NUM2) e se i due numeri non sono uguali.
JE salta se i due numeri confrontati dalla CMP sono uguali, cioè se il risultato della CMP è zero (ZF == 1).
JNE salta se i due numeri confrontati dalla CMP non sono uguali (ZF == 0)
JZ stessa cosa di JE (cambia soltanto il nome mnemonico)
JNZ stessa cosa di JNE (cambia soltanto il nome mnemonico)
Queste ultime 4 istruzioni possono ovviamente essere utilizzate anche per i numeri signed, ma usano offset di 8 bit
Operandi Signed
Le seguenti istruzioni di salto condizionato sono più generali e possono essere utilizzate in seguito ad un confronto
fra numeri signed. Il salto prevede un offset memorizzato su 16 bit (± 32000 bytes rispetto alla posizione corrente).
JG jump if greater. Salta se NUM1 > NUM2.
JGE jump if greater or equal.
JL jump if less. Salta se NUM1 < NUM2.
JLE jump if less or equal.
Nota: Tecnicamente JG esegue il salto se ZF=0 e se SF = OF, cioè se:
- non c’è overflow ed il segno del risultato è positivo (5, 3 o -3,-5 o 5,-3 )
- c’è overflow ed il segno del risultato è negativo (124, -5 oppure 5, -124 oppure)
Se non c’è overflow ed il risultato è negativo non salta (4, 5 oppure -3, 2)
Se c’è overflow ed il risultato è positivo non salta ( -124, +5; -129 viene codificato come +127).
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 10
Traduzione dell’istruzione IF
L‟istruzione IF, tipica dei linguaggi di alto livello, può essere tradotta in Assembly nel modo seguente:
CMP A, B
JB VERO
operazioni NO
JMP FINEIF
VERO:
operazioni SI
FINEIF:
.................
Se sul ramo NO non c‟è nessuna operazione da eseguire, si può utilizzare il seguente schema semplificato ottenuto
invertendo la condizione del primo jump
CMP A, B
JAE FINEIF
operazioni SI
FINEIF:
.................
Traduzione del ciclo post-condizionale
Il ciclo post-condizionale è il più semplice da utilizzare in Assembly.
Può essere tradotto nel modo seguente:
XOR CX, CX
INIZIO_CICLO:
.................
.................
.................
INC CX
CMP CX, 20 ; numero di cicli da effettuare
JB INIZIO_CICLO
.................
A<B Si No
operazioni SI operazioni NO
FINEIF:
VERO:
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 11
I sottoprogrammi La definizione di ogni procedura deve iniziare con una direttiva PROC e deve terminare con
una direttiva ENDP.
stampa PROC
. . . . . . . . . . . .
. . . . . . . . . . . .
RET
stampa ENDP
stampa è il nome simbolico della procedura, che dovrà essere utilizzato per chiamare la procedura stessa.
Le procedure possono essere scritte prima del main, nel qual caso occorre dichiarare esplicitamente un entry point di
inizio del main, oppure dopo le istruzioni di ritorno al sistema operativo in modo da essere fuori dalla sequenza di
esecuzione principale.
Nota: Dopo PROC si può indicare NEAR / FAR
- NEAR indica che la procedura può essere chiamata solo all'interno del segmento in cui è stata definita (default)
- FAR indica che la procedura può essere chiamata da qualsiasi segmento.
Chiamata ad una procedura mediante l’istruzione CALL
L‟istruzione CALL trasferisce il controllo dal programma chiamante alla procedura chiamata. A differenza
dell‟istruzione JUMP in cui il valore corrente del Program Counter va perso in quanto soprascritto dal nuovo valore,
nel caso delle CALL il valore corrente del Program Counter viene automaticamente salvato all‟interno dello stack
prima di essere soprascritto in modo che, terminata la procedura, la CPU possa riesumare il vecchio valore del
Program Counter dallo stack e riprendere l‟esecuzione esattamente dal punto in cui si era fermata.
In sintesi, in corrispondenza della CALL, vengono automaticamente eseguite le seguenti operazioni:
il valore corrente del Program Counter viene salvato in cima allo stack (operazione di PUSH)
all’interno di IP viene caricato l’indirizzo (offset) del sottoprogramma a cui eseguire il salto
lo Stack Pointer SP viene decrementato di 2
Nota: Se la procedura è stata dichiarata FAR all’interno di un altro segmento, la chiamata dovrà essere eseguita
utilizzando la direttiva FAR PTR
call FAR PTR stampa
In tal caso in cima allo stack, ancor prima del Program Counter, viene salvato anche il Code Segment, e SP viene
ulteriormente decrementato di 2.
L’istruzione RET
L‟istruzione RET consente il ritorno dalla procedura chiamata al programma chiamante.
In corrispondenza dell‟istruzione RET la CPU provvede automaticamente a:
leggere dallo stack (mediante una operazione detta POP) l'indirizzo di ritorno salvato dalla CALL
ricaricare l’indirizzo di ritorno all’interno del Program Counter
incrementare di 2 il valore dello Stack Pointer
Nota: Se la procedura era di tipo FAR, oltre al Program Counter verrà ripristinato anche il Code Segment.
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 12
Salvataggio dei registri
E‟ abbastanza probabile che la procedura chiamata debba fare uso dei registri della CPU, registri all‟interno del quale
probabilmente il chiamante stava memorizzando i dati della propria elaborazione. La procedura chiamata, prima di
iniziare la propria elaborazione deve quindi sempre provvedere a salvare nello stack (mediante apposite istruzioni
PUSH) il contenuto di tutti i registri della CPU che dovrà modificare nel corso della propria esecuzione.
push SI
push DI
In corrispondenza di ogni PUSH lo stack pointer verrà automaticamente decrementato di 2.
Prima di eseguire la RET, la procedura chiamata dovrà preoccuparsi di riesumare dallo stack i valori dei registri
precedentemente salvati, in ordine inverso rispetto all‟ordine con cui erano stati salvati (LIFO).
pop DI
pop SI
Secondo le convenzioni utilizzate dai compilatori C non è richiesto il salvataggio dei registri dati (AX, BX, CX,
DX). Cioè al termine della procedura per questi registri non è garantita la conservazione dei valori che avevano prima
della chiamata. Tutti gli altri registri utilizzati devono invece essere salvati.
Variabili globali e variabili locali
int A = 5;
int B;
main() {
int C = 15;
ind D;
. . . . . . . . . . . . . . . . .
}
Le variabili globali A e B sono visibili ed utilizzabili da tutti (main e sottoprogrammi).
Esse vengono allocate all‟interno del Data Segment.
.data
A dw 5;
B dw ?
; data ends
Le variabili locali C e D sono visibili ed utilizzabili soltanto all‟interno della procedura in cui vengono dichiarati
(cioè se sono dichiarati nel main sono visibili ed utilizzabili soltanto dal main).
Esse vengono allocate all‟interno dello stack segment, cioè vengono tradotte del compilatore nel modo seguente::
mov ax, 15
push ax
La seconda (int D) viene tradotta semplicemente mediante una push AX con AX non inizializzato.
Passaggio dei parametri ad una procedura
Spesso una procedura ha la necessità di utilizzare variabili definite all‟interno del main, variabili che pertanto devono
essere passate come parametri alle procedura. Esistono in questo caso tre diverse possibilità:
1) Copiare i valori in una variabile globale del Data Segment, in modo che sia visibile da tutti. Soluzione poco
portabile perché rende la procedura dipendente dalle variabili esterne (procedura non rientrante). Se le variabili
esterne non vengono create correttamente dal chiamante, la procedura fallisce.
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 13
2) Passare i valori nei registri. Anche questa strada non è ottimale dato il numero limitato dei registri e la loro
dimensione fissa. Una variabile a 4 byte può essere passata su due registri, ma diventa difficile passare più
variabili di questo tipo. Inoltre dai linguaggi di alto livello (C, VB, Pascal) non si può accedere direttamente ai
registri di CPU
3) Passare i valori salvandoli all’interno dello stack.. I compilatori ANSI C passano i parametri attraverso lo
stack. Il chiamante prima di eseguire la chiamata copia i parametri all‟interno dello stack, quindi esegue la
chiamata (salvando automaticamente all‟interno dello stack anche il valore del program counter).
La procedura chiamata dovrà andare a leggersi i parametri all‟interno dello stack, facendo attenzione all‟ordine.
Al ritorno dalla procedura, il main deve provvedere a rimuovere i parametri dallo stack, incrementando lo stack
pointer di un valore pari al totale in byte dei parametri passati.
Esempio
Vediamo come viene tradotta in Assemby la seguente chiamata C ad una funzione somma che esegue la somma di tre
numeri interi int a, int b, int c, ciascuno grande 2 bytes
somma (a, b, c);
push c ; SP <- SP - 2
push b ; SP <- SP - 2
push a ; SP <- SP - 2
call _somma „chiamata alla funzione somma
add sp, 6 „rimozione dei parametri dallo stack
L‟istruzione finale ADD SP, 6 ripulisce lo stack sostituendo 3 inutili POP (al main i parametri ormai non servono più)
Procedura chiamata
La procedura chiamata (somma) deve salvare nello stack il contenuto dei registri che andrà ad utilizzare e quindi
deve leggere i parametri che il main gli ha passato (parametri che sono “sepolti” in fondo allo stack).
Poiché non è possibile utilizzare SP come registro di indirizzamento indiretto dello stack, occorre copiare il valore
di SP all’interno di BP e poi utilizzare BP per indirizzare lo stack in modo indiretto. Prima di fare la copia occorre
salvare il vecchio valore di BP.
_somma proc
push bp
mov bp,sp
mov ax, [bp+4] ; a
mov bx, [bp+6] ; b
mov cx, [bp+8] ; c
add ax, bx ;a+b
add ax, cx ;a+b+c
pop bp
ret cella 1024
_somma endp
Restituzione dei valori
Una funzione di solito restituisce un risultato al main. Secondo le convenzioni del compilatore C :
I valori su 8 bit vengono convenzionalmente restituiti in AL
I valori su 16 bit vengono convenzionalmente restituiti in AX
I valori su 32 bit vengono convenzionalmente restituiti in DX : AX
Nell‟esempio precedente la somma finale viene messa in AX dove il main potrà andarla a leggere.
SP BP
SP + 2 Program Counter
SP + 4 a
SP + 6 b
SP + 8 c
EOS
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 14
Procedure di Lettura e di Visualizzazione
INT 21H
AH = 1 Legge un carattere ASCII e lo memorizza in AL
AH = 2 Manda a video il carattere contenuto in DL
AH = 9 Manda a video una stringa terminata dal $
AH = 4c Ritorno al Sistema Operativo
Visualizzazione di un numero a 16 bit passato nello stack ed estratto in AX
buffer db 10 dup (?)
visualizza proc
push bx
push cx
push dx
push di
push bp
; non è possibile utilizzare sp con un displacement
mov bp, sp
mov ax, [bp+12]
mov bl, 10
mov di, 0
inizio1:
; AX / BL => AL quoziente, AH resto
div bl
add ah, 48
mov buffer[di], ah
mov ah, 0
inc di
cmp al, 0
jne inizio1
inizio2:
dec di
mov dl, buffer[di]
mov ah, 2
int 21h
cmp di, 0
jne inizio2
call acapo
pop bp
pop di
pop dx
pop cx
pop bx
mov ax, 0 ; procedura terminata correttamente ok
ret
visualizza endp
AX=numero da visualizzare
BL=10;
DI=0;
INIZIO1:
AL=AX/10;
AH=AX%10;
AH=AH+48;
BUFFER[DI]=AH;
AH=0;
DI++;
if(AL>0) goto INIZIO1;
INIZIO2:
DI--;
DL=BUFFER[DI];
printf(DL);
if(DI>0) goto INIZIO2;
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 15
Lettura da tastiera di un numero a 8 bit e restituzione in AL
leggi proc
push bx
push cx
push dx
xor dx, dx
lettura:
mov ah, 1
int 21h
cmp al,13
je fine
mov cl, al
sub cl, 48
mov al, 10
mul dl ; ax = al * dl
mov dl, al
add dl, cl
jmp lettura
fine:
mov al, dl ; prima di terminare occorre copiare il risultato in AX
pop dx
pop cx
pop bx
ret
leggi endp
acapo proc
push ax
push dx
mov ah, 2
mov dl, 13
int 21h
mov dl, 10
int 21h
pop dx
pop ax
ret
acapo endp
Esempio di Main: Area e Perimetro di un rettangolo
.data
msg1 db “Inserire valore della base $”
msg2 db “Inserire valore della altezza $”
msg3 db “Area = $”
msg4 db “Perimetro = $”
buffer db 10 dup (?)
DX=0;
LETTURA:
AL = getchar( );
if(AL==‟\n‟) goto FINE;
CL = AL - 48
DL = DL * 10 +CL
goto LETTURA;
FINE:
…………..………
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 16
base db ?
altezza db ?
area dw ?
perimetro dw ?
.code
mov ax, @data
mov ds, ax
lea dx, msg1 ; inserire valore della base
mov ah, 9
int 21h
call leggi
call acapo
mov base, al
lea dx, msg2 ; inserire valore della altezza
mov ah, 9
int 21h
call leggi
call acapo
mov altezza, al
mov al, base ; calcolo del perimetro
mov ah, 0
mov bl, altezza
mov bh, 0
add ax, bx
add ax, ax
mov perimetro, ax
mov al, base ; calcolo dell’area
mov ah, 0
mov bl, altezza
mul bl
mov area, ax
lea dx, msg3 ; visualizzazione dell’area
mov ah, 9
int 21h
push area
call visualizza
add sp, 2
lea dx, msg4 ; visualizzazione del perimetro
mov ah, 9
int 21h
push perimetro
call visualizza
add sp, 2
mov ah, 4ch ;ritorno al DOS
int 21h
seguono le procedure precedenti
end
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 17
Direttive di Segmento
nomeSeg SEGMENT [allineamento] [rango] ['classe'] contenuto del segmento
nomeSeg ENDS
E‟ ammessa la creazione di più segmenti con lo stesso nome (soprattutto in programmi articolati in più moduli),
segmenti che saranno riuniti dall‟Assemblatore o dal linker in un unico segmento. In tal caso deve però essere
specificata l‟opzione RANGO.
Dopo SEGMENT è possibile specificare le seguenti opzioni:
[allineamento] indica l‟allineamento del segmento rispetto al primo byte di memoria libera. Può assumere i valori
byte, word, dword, para (16 bytes), page (256 bytes). Il default è para. Gli allineamenti inferiori a para sono
ottenuti assegnando a PC un offset iniziale maggiore di zero.
[„classe’] Il Terzo Parametro „classe‟ consente di raggruppare i segmenti in classi. Può assumere i valori ‘Data’
‘Code’ ‘Stack’. Definisce un pratica il tipo di segmento che si sta definendo. L‟eventuale fusione dei diversi
segmenti dello stesso tipo verrà definita sulla base dell‟opzione rango. Segmenti con lo stesso nome non sono
combinati se il loro campo class assume valori diversi.
[rango]Il Seconda Parametro denominato RANGO indica all‟assemblatore come combinare tra loro i segmenti
aventi lo stesso nome e appartenenti alla stessa classe. Può assumere i valori:
PUBLIC. Tutti i segmenti PUBLIC con lo stesso nome e la stessa classe vengono fusi in un unico segmento.
Valore normalmente utilizzato per Data e Codice
STACK. Come PUBLIC, però questa volta, in aggiunta, l‟indirizzo assoluto del segmento unificato dichiarato
„STACK‟ viene automaticamente caricato allo start up all‟interno del registro SS:SP. Se non si specifica il rango
„STACK‟ il caricamento dovrà essere eseguito manualmente.
PRIVATE. [default]. Ogni segmento viene allocato separatamente dagli altri.
COMMON Sovrappone i segmenti con lo stesso nome. Eventuali variabili con lo stesso nome vengono fuse in
una unica variabile. Abbastanza pericoloso.
AT B800:0000 localizza il segmento a partire da un ben preciso indirizzo assoluto. Utile per accedere
direttamente ad una precisa area di sistema (ad esempio B800:0000 è la RAM Video). In tal caso non è possibile
definire né allineamento né classe.
La direttiva ASSUME
Le direttive ASSUME possono essere inserite ovunque all‟interno del programma, ed ogni direttiva maschera la
direttiva precedente. Obbligatorie. Se omesse errore di sintassi.
assume cs:_code, ds:_data, ss:_stack, es:_extraData
Associa un registro di segmento al segmento avente il nome indicato. Viene utilizzata dall'assemblatore per
determinare il registro di segmento che dovrà essere utilizzato dal RUN TIME per accedere alle variabili (ed
etichette) di un certo segmento. Non provvede però al caricamento dell'indirizzo del segmento nel registro
relativo, che dovrà pertanto essere eseguito manualmente via codice
Ad esempio scrivendo ds: _data, quando il RUN TIME dovrà accedere ad una variabile del _data segment, utilizzerà
DS come indirizzo di partenza del segmento. Quando dovrà accedere ad una variabile del _code segment utilizzerà
CS e così via. Se i registri di segmento non stati caricati adeguatamente, ovviamente si avrà un malfunzionamento.
Operatori Aritmetici sulle costanti
+, - , *, / , MOD
Gli operatori relazionali (== != < <= > >=) non sono definiti in Assembler, ma fanno parte dell‟istruzione JUMP
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 18
Pseudoistruzioni aggiuntive relative alle variabili
OFFSET
L‟operatore OFFSET restituisce il valore dell'offset di una variabile. Può essere usato in alternativa all‟istruzione
LEA. Ad esempio le due seguenti istruzioni sono equivalenti:
MOV AX, OFFSET VAR
LEA AX, VAR
L‟operatore OFFSET può essere applicato solo ad operandi indirizzati direttamente attraverso un nome di variabile e
non a operandi indirizzati indirettamente. Ad esempio
MOV AX, OFFSET VAR[SI] ; Errore !!
Si possono utilizzare le seguenti istruzioni: MOV AX, OFFSET VAR
ADD AX, SI
oppure: LEA AX, VAR[SI]
LENGTH
Restituisce il numero di elementi di una variabile dichiarata mediante DUP (variabile vettoriale).
Per una variabile scalare restituisce 1.
NUM DW 5 DUP (?)
MOV AX, LENGTH NUM ; 5
TYPE
Restituisce il numero di byte occupati da una variabile scalare o da una singola variabile vettoriale.
Nel caso dell‟esempio precedente si può scrivere
MOV BX, TYPE EXP ; 2
SIZE
Restituisce lo spazio di memoria complessivamente occupato dalla variabile (in pratica SIZE = LENGHT * TYPE).
MOV CX, SIZE EXP ; 10
SEG
L‟operatore SEG restituisce l‟indirizzo di inizio del segmento a cui la variabile appartiene.
MOV AX, SEG STR
MOV DS, AX
PTR
L‟operatore PTR (sintassi : TIPO PTR nomeVariabile) forza l‟assemblatore a modificare per l'istruzione
corrente il tipo della variabile, eseguendo in pratica un TYPE CAST (conversione del tipo).
TOT DW 35
MOV BH, BYTE PTR TOT
MOV CH, BYTE PTR TOT+1
COPPIA DB 2 DUP (?)
MOV AX, WORD PTR COPPIA
Sistemi - Classe Terza robertomana.it
Cenni di Assembler
pag 19
Public e Extern
Utilizzabili per variabili e procedure. Gestiscono l‟accesso a variabili / procedure definite all‟interno di altri moduli.
PUBLIC rende la variabile / procedura visibile ed utilizzabile su ogni file del progetto
PUBLIC MAX_ETH DD 1500
EXTRN Consente al file corrente di accedere ad una variabile / procedura dichiarata altrove.
EXTRN MAX_ETH : DWORD
EXTRN miaProc : NEAR / FAR
Per ogni simbolo dichiarato EXTRN, deve corrispondere una dichiarazione PUBLIC in qualche altro modulo.
Etichette tipizzate
La pseudoistruzione LABEL, utilizzata davanti ad una pseudoistruzione di definizione di un dato, consente di creare
una nuova etichetta tipizzata che fa riferimento alla stessa cella di memoria relativa alla variabile che segue, ma
utilizzando un tipo di riferimento differente. Ad esempio :
RamVideoByte LABEL BYTE
RamVideo DW 0b800H
L‟etichetta RamVideoByte consente di indirizzare la cella di memoria RamVideo come Byte anziché come Word
Gestione delle etichette da parte del Compilatore
Queste etichette servono al compilatore per crearsi una mappa dei simboli (salvata dal compilatore all‟interno del file
.LST). Ogni volta che in compilazione incontra uno di questi simboli, va nella mappa a vedere a quale indirizzo
corrisponde ed in quale segmento si trova, producendo codice macchina opportuno.
Modello completo di un programma Assembly
_data segment para public ‘data’
num1 dw 61
num2 dw 4
ris dw ?
_data ends
_stack segment para stack ‘stack’
db 128 dup (?)
eos label word ; INUTILE !
_stack ends
_code segment para public ‘code’
assume cs:_code, ds:_data, ss:_stack
main: mov ax, _stack
mov ss, ax
lea ax, eos
mov sp, ax ;queste 4 istruzioni sono eseguite in automatico dalla direttiva
mov ax, _data
mov ds, ax
mov ax,num1 ; AX <- DS:NUM1
add ax,num2
mov ris, ax
mov ah, 4ch ; ritorno al DOS
int 21h
_code ends
end main