Guida al Linguaggio Assembler

Introduzione al linguaggio:

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 prende o dà il valore (ricordate sempre di spostare  il vostro valore dentro a questo registro o viceversa per  il read)

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…

 

Se invece il file contiene degli errori vi apparirà la seguente finestra con il numero di linea dove è

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 che il file è stato caricato con successo basta spingere l’icona evidenziata in figura:

 

 

 

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

 

 Alcune istruzioni e secondo programma assembler:

 

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

 

 

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)

  

and rd, rs, rt  à rd = rs AND rt

 

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)

 

Istruzioni di Jump:

 

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