Guida al Linguaggio Assembler
Si dice
che un linguaggio di programmazione è a basso o ad alto livello a seconda
della maggiore o minore corrispondenza con l'hardware. Partiamo quindi da
linguaggio macchina che è basato principalmente su valori numerici
per memorizzare ed eseguire programmi,passiamo
quindi al linguaggio assembler più comprensibile a mente umana per
il fatto che usa simboli invece di numeri per la rappresentazioni di dati,per
poi arrivare al linguaggio di alto livello con il quale è possibile
scrivere programmi con un linguaggio simile a quello naturale.
Es.
Linguaggio macchina = 00100111101010010001111011001011 e il corrispondente
linguaggio
assembler può essere:addiu $s2 ,$s2, 32
Da qui possiamo
incominciare a capire il nome di questo linguaggio Assembler
in pratica è uno strumento che traduce programmi scritti nel linguaggio
assembly in linguaggio macchina. L’assembler
legge un file sorgente
in Assembler e produce un file contenente linguaggio macchina!
Perchè allora usare e
conoscere il linguaggio Assembler?La conoscenza di quei linguaggi a basso
livello che sono gli assembler è importante se non indispensabile ad un
programmatore sia per capire poi i linguaggi come il (C,Java,..)
sia per sfruttare a pieno le risorse disponibili ad un programma.
Quindi quali sono i vantaggi
del linguaggio Assembler?I principali vantaggi sono la velocità
di esecuzione utile quindi ad esempio per
la programmazione di centraline ABS che richiedono una velocità ci esecuzione
molto bassa, ma anche per ottimizzare programmi scritti
nel linguaggio ad alto livello nei loro punti critici dal punto di vista
delle performance, ma questo linguaggio ha anche lati negativi come
tutti i linguaggi macchina: per prima cosa non è portabile (viene
usato per un particolare processore)seconda cosa è complesso
portando facilmente all’errore il programmatore.
Quindi
il compito del linguaggio Assembler è quello di semplificare al programmatore
l’uso del linguaggio macchina diretto, dando la possibilità di creazione
etichette, definire macro, aggiungere commenti.
Su cosa lavoriamo?
Lavoreremo principalmente
su un simulatore non avendo a disposizione l’architettura desiderata.
Il programma che useremo
è il SPIM è un simulatore che esegue programmi
per le architetture R2000/R3000 SPIM può leggere ed assemblare programmi
scritti in linguaggio assembly MIPS e SPIM contiene
inoltre un debugger per poter analizzare il
funzionamento dei programmi prodotti.
Dove
possiamo trovare il nostro programma per simulare l’architettura richiesta?
http://www.cs.wisc.edu/~larus/spim.html
oppure QUI
Ora abbiamo
tutto quello che ci serve………
Introduzione alle principali caratteristiche
del linguaggio Assembler
Direttive:
le direttive forniscono informazioni
utili all’Assembler per organizzare il codice alcuni esempi possono essere:
Esempi:
.text
indica che le righe successive contengono istruzioni
.data
indica che le righe successive contengono dati
Organizzazione
della memoria:
La memoria viene
suddivisa in frammenti ogni frammento viene utilizzato per un particolare
scopo i frammenti principali sono: Text:
Contiene il codice dei programmi, Data:Contiene i dati “globali”
dei programmi Stack Contiene
i dati “locali” delle funzioni.
Etichette:
Una
etichetta consiste di una sequenza
di caratteri alfanumerici minuscoli per esempi esempio: main,
ciclo, variabile, intero, ecc., seguiti dal simbolo :(due punti) Le etichette
sono utili per riconosce alcuni punti del programma oppure per creare cicli
all’interno del programma stesso richiamando la stessa etichetta più volte
(vedremo in seguito come fare).
Allocazione
della memoria:
Direttive di
allocazione di memoria:
•.half
Alloca nquantità a 16 bit in halfword
successive in memoria
•.word
Alloca nquantità a 32 bit in word successive
in memoria
•.float
Alloca nvalori floatingpointa
singola precisione in locazioni successive in memoria
•.double
Alloca nvalori floating
pointa doppia precisione in locazioni successive
in memoria
•.asciiz
Alloca la stringa strin memoria, terminata con
il valore 0
Tabella
1
Registri
generali:
•$zero Valore
fisso a 0
•$v0-$v1
Risultati di una funzione
•$a0-$a3 Argomenti
di una funzione
•$t0-$t7
Temporanei (non preservati fra chiamate)
•$s0-$s7
Temporanei (preservati fra le chiamate)
•$t8-$t9
Temporanei (non preservati fra chiamate)
•$gp
Pointer to global area
•$sp
Stack pointer
•$fp
Frame pointer
•$ra
Return address
Istruzioni:
Ora piccoli esempi
di istruzioni:
Add
$t0, $t1, $t3 che
è uguale a fare t0=t2+t3
dove t0 è il registro destinazione.
Sub $t0, $t1,
$t3 che è uguale a
fare t0=t2-t3 dove
t0 è il registro destinazione.
Bne
$t0, $t1, etichetta
che uguale a dire se $t0 !=(diverso) $t1 allora salta al punto del programma
desiderato attraverso una etichetta.
Ora che siamo quasi pronti a creare un piccolo programma Assembler dobbiamo conoscere bene il nostro simulatore……….
Il simulatore Spim:
Passiamo subito
all’interfaccia del programma versione win:
Il programma
è composto da 2 finestre:
La finestra “Console” è la finestra che visualizzerà
l’output del vostro programma invece la finestra più grossa in secondo piano
visualizza lo stato dei registri e l’output in linguaggio macchina.
Prima
di iniziare ….
Prima di iniziare
o comunque quando si carica un file Assembler
sul programma è sempre bene cancellare e reinizializzare
i registri.
Per fare tutto questo
bisogna cliccare sulla voce “simulator”
del programma e poi cliccare le prime due voci
della lista che sono rispettivamente “clear
register” “reinitialize”.
Dove
scriviamo in nostri programmi Assembler?
Per scrivere i nostri
programmi Assembler basta lo strumento di Windows Blocco Note
o un altro qualsiasi Editor di testo
(NON MICROSOFT WORD) , una volta scritto
il nostro codice
basta
salvare il tutto con l’estensione “.asm”
oppure “.s”.
Di che cosa si compone un programma Assembler
e come è strutturato?
Le istruzioni
principali sono:
•
Istruzioni
“Load and Store”
Queste
istruzioni spostano dati fra la memoria e i registri
generali
del processore
•
Istruzioni
“Data Movement”
Queste
istruzioni spostano dati fra i registri del processore
•
Istruzioni
“Load Immediate”
Queste
istruzioni caricano nei registri valori immediati
(“costanti”)
•
Istruzioni
aritmetico/logiche
Queste
istruzioni effettuano operazioni aritmetico-logiche
sui
registri del processore
•
Istruzioni
di salto
Queste
istruzioni permettono di spostare l’esecuzione da un
punto
ad un altro di un programma
•
Istruzioni
di confronto
Queste
istruzioni effettuano il confronto fra i valori
contenuti
nei registri
•
Istruzioni
“Branch”
Queste
istruzioni permettono di spostare l’esecuzione da un
punto
ad un altro di un programma in presenza di certe condizioni
Ecco
alcune Istruzioni load and
store
lb
rdest, address Load
Byte at address
in
register rdest
lbu
rdest, address Load
Byte Unsigned at address
in
register rdest
lh
rdest, address Load
Halfword at address
in
register rdest
lhu
rdest, address Load
Halfword Unsigned at address
in
register rdest
lw
rdest, address Load
Word from address
in
register rdest
la
rdest, address
Load
computed Address
in
register rdest
sb
rsource, address Store
lower Byte at address
from
register rsource
sh
rsource, address Store
lower Halfword at address
from
register rsource
sw
rsource, address StoreWord
at address
from
register rsource
Attenzione:
•
L'istruzione lw
$t0, address carica
il contenuto
della
word
indirizzata dall'etichetta address in memoria
•
L'istruzione la
$t0, address carica
l'indirizzo
della word
indirizzata
dall'etichetta address in memoria
Quando
utilizzare load address?
Quando
vogliamo memorizzare in un registro l'indirizzo di
una
particolare zona di memoria
Uso
di syscall:
Il
comando syscall è una chiamata
la sistema operativo che invoca certi servizi.
Esempio
per stampare una stringa dovremo invocare il
comando di stampa stringa , il codice è:
li
$v0, 4 e poi mettere
la stringa dentro al registro $a0 perchè il comando li
$v0, 4 legge i dati SOLO da quel registro dopo aver
fatto queste 2 operazioni bisogna inserire la stringa syscall
e la stringa verrà stampata!
Tabella
2
Eccovi
una lista completa di tutte le istruzioni relative alla
gestione della syscall…
| Azione | Istruzione da usare | Registro
da cui la chiamata |
|
Print_float |
$v0,
2 |
$f12 contiene
il valore |
|
Print_double |
$v0,
3 |
$f12 contiene
il valore |
|
Print_string
|
$v0,
4 |
$a0 contiene valore |
|
Read_integer |
$vo,
5 |
$a0 contiene il
valore |
|
Read_float |
$v0,
6 |
$f0 contiene il
valore |
|
Read_double |
$v0,
7 |
$f0 contiene il
valore |
|
Read_string |
$v0,
8 |
$a0 contiene il
valore |
|
Exit |
$v0,
10 |
- |
Ora che
abbiamo fatto una piccola introduzione possiamo
iniziare a scrivere il nostro primo programma Assembler:
|
.data
#direttive per l’assemblatore str:
.asciiz "Hello
World" #stringa di caratteri .text
.globl
main main: li
$v0, 4 #codice
di stampa per le stringhe di caratteri la
$a0, str #metto
nel registro $a0 la stringa di caratteri con etichetta “str”
syscall
li
$v0, 10 #codice
di uscita questo codice deve SEMPRE essere messo alla fine di ogni
programma
#
per la terminazione dello stesso syscall #i
commenti in assembler si delimitano con il simbolo “ # ” all’inizio
del commento |
Ora dobbiamo
vedere se funziona con il nostro simulatore:
Per prima cosa carichiamo il file che abbiamo creato con il codice sopra descritto se il codice non contiene errori apparirà questa schermata:
In basso si po’ vedere la scritta evidenziata in blu del file caricato con successo…
presente l’errore
Esempio:
In questo caso l’errore
consiste nel aver scritto .txt invece che .text
In questo caso basta correggere
l’errore reinizializzare i registri e ricaricare
il file…..
Una volta premuta l’icona cerchiata in figura vi apparirà un messaggio, se il valore dentro la casella “Starting Adress” è 0x00400000 (altrimenti modificatelo), allora premete Ok e guardate il vostro output sulla “Console”
- Caricamento di un valore immediato: li Rdest, Imm (dove Rdest è il registro destinazione dove verrà caricato il valore immediato e Imm è il valore immediato)
- Caricamento di un indirizzo
della memoria centrale: la Rdest,
Indirizzo
Quando
utilizzare load address?
•
Quando vogliamo memorizzare in un registro l'indirizzo di
una
particolare zona di memoria
•
Questo registro può essere utilizzato in seguito come indice
per
leggere il contenuto di byte consecutivi in memoria
•
Esempio:
la
$t0, spese # Carica
l'indirizzo di spese
lw
$t1, ($t0)
# Carica in t1 il
contenuto della word
#
indirizzata da $t0
add
$t2, $t2, $t1 #
Fai la sommatoria
addi
$t0, $t0, 4 # Incrementa
$t0 di 4 byte (1 word)
bnez
$t1, end # Continua
finchè non trova 0
- Trasferimento da un registro
all’altro: move Rdest,
Rsource
Tabella
3
Istruzioni
Aritmetico/Logiche
add
rd, rs, rt à
rd
= rs + rt (with overflow)
addu
rd, rs, rt à
rd
= rs + rt (without overflow)
addi
rd, rs, imm à
rd
= rs + imm (with overflow)
addiu
rd, rs, imm à
rd
= rs + imm (without overflow)
sub
rd, rs, rt à
rd
= rs - rt (with overflow)
subu
rd, rs, rt à
rd
= rs - rt (without overflow)
neg
rd, rs à
rd
= - rs (with overflow)
negu
rd, rs à
rd
= - rs (without overflow)
abs
rd, rs à
rd
= |rs|
mult
rs, rt à
hi,lo
= rs * rt (signed)
multu
rs, rt à
hi,lo
= rs * rt (unsigned)
mul
rd, rs, rt à
rd
= rs * rt (without overflow)
mulo
rd, rs, rt à
rd
= rs * rt (with overflow)
mulou
rd, rs, rt à
rd
= rs * rt (with overflow, unsigned)
andi
rd, rs, imm à
rd
= rs AND imm
or
rd, rs, rt à
rd
= rs OR rt
ori
rd, rs, imm à
rd
= rs OR imm
xor
rd, rs, rt à
rd
= rs XOR rt
xori
rd, rs, imm à
rd
= rs XOR imm
Secondo
programma Assembler:
Scrivere un programma assembler che legga un numero intero in ingresso lo incrementi di 1
e stampi il risultato, gli input e gli output devono essere preceduti
da messaggi (Es. “Immetti intero”)
|
.data etc1: .asciiz "Immetti il numero: " etc2: .asciiz "Il risultato è: " .text .globl main main: li $v0, 4
#stampo la stringa la $a0, etc1
#metto
nel registro la stringa syscall
#chiamata
di sistema li $v0, 5 #leggo un intero da tastiera usando i codici syscall(vedi Tab. 2) syscall
# move
$s0,$v0 #memorizzo il numero digitato
in un registro ($s0 vedi Tab. 1) di
memorizzazione permanente
addi
$t0,$s0,1 #aggiungo 1 al valore contenuto
nel registro (vedi Tab. 3) li $v0, 4
# stampo la stringa etc2 la $a0, etc2 # metto nel registro la stringa syscall move $a0,$t0 #per stampare il valore contenuto in $t0 devo metterlo in $a0 infatti il codice li $v0, 1
#di stampa per gli interi
(vedi Tab.2) legge i dati solo dal Reg.
$a0 syscall li $v0,10
#codice
di uscita questo codice deve SEMPRE essere messo alla fine di ogni
programma
#
per la terminazione dello stesso syscall |
Istruzioni
di Confronto e di Branch
:
Tabella
4
Istruzioni
di confronto:
slt
rd, rs, rt Set
register rd
to
1 if rs
<
rt,
0 otherwise
(with
overflow)
sltu
rd, rs, rt Set
register rd
to
1 if rs
<
rt,
0 otherwise
(without
overflow)
slti
rd, rs, imm Set
register rd
to
1 if rs
<
imm,
0 otherwise
(with
overflow)
sltiu
rd, rs, imm Set
register rd
to
1 if rs
<
imm,
0 otherwise
(without
overflow)
Istruzioni
di Branch :
beq
rs, rt, target Branch
to instruction at target
if
rs
=
rt
bne
rs, rt, target Branch
to instruction at target
if
rs
!=
rt
bgez
rs, target Branch
to instruction at target
if
rs
>=
0
bgtz
rs, target Branch
to instruction at target
if
rs
>
0
blez
rs, target Branch
to instruction at target
if
rs
<=
0
bltz
rs, target Branch
to instruction at target
if
rs
<
0
beqz
rs, target Branch
to instruction at target
if
rs
=
0
bnez
rs, target Branch
to instruction at target
if
rs
!=
0
bge
rs, rt, target Branch
to instruction at target
if
rs
>=
rt
bgeu
rs, rt, target Branch
to instruction at target
if
rs
>=
rt
(unsigned)
bgt
rs, rt, target Branch
to instruction at target
if
rs
>
rt
bgtu
rs, rt, target Branch
to instruction at target
if
rs
>
rt
(unsigned)
ble
rs, rt, target Branch
to instruction at target
if
rs
<=
rt
bleu
rs, rt, target Branch
to instruction at target
if
rs
<=
rt
(unsigned)
blt
rs, rt, target Branch
to instruction at target
if
rs
<
rt
bltu
rs, rt, target Branch
to instruction at target
if
rs
<
rt
(unsigned)
j
target Unconditionally
jump to instruction at target
jal
target Unconditionally
jump to instruction at target,
and
save
the address of the next instruction in register
$ra
jr
rsource Unconditionally
jump to the instruction whose
address
is in register rsource
jalr
rsource, rdest
Unconditionally
jump to the instruction whose
address
is in register rsource
and
save the address
of
the next instruction in register rdest
A cosa
servono le istruzioni di Branch e Jump?
Le istruzioni di
Branch servono per creare dei cicli
infatti se noi abbiamo un blocco di istruzioni attraverso il Branch
possiamo ripetere questo blocco fino a quando la condizione del branch
non sia verificata…….
Esempio:
|
loop:
add
$s0, $s0, 1 #addiziona
1 al reg $s0 fino a quando $s0 non diventa
>=0 bgez
$s0 , exit #una
volta che $s0 è diventato maggiore di 0 allora il ciclo provocato
dal “j”si interrompe perché
#l’istruzione “bgez”
fa saltare il programma all’etichetta “exit” (vedi tabella 4 per
tutti i tipi di Brach) j
loop #fa
tornare l’esecuzione del programma all’etichetta “loop”
exit: li $v0,10
#codice
di uscita questo codice deve SEMPRE essere messo alla fine di ogni
programma
#
per la terminazione dello stesso syscall |
A cura di Marco Fattorosi Barnaba