Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
vuoi
o PayPal
tutte le volte che vuoi
GLOBALI
- Sono piazzate in un indirizzo fisso stabilito dall'assemblatore e dal linker.
- Normalmente l'identificativo con cui accediamo ad esse coincide con il loro nome.
- L'ordine di dichiarazione nel codice coincide con l'ordine di collocamento in memoria.
All'interno della memoria sono nel segmento dati il quale parte da 0x10000000. Il Global
Pointer è inizializzato a 0x10008000 così possiamo fare offset avanti e indietro. Prima del
segmento dati, da 0x00400000 a 0x10000000 c'è il segmento di testo. Tutto ciò che viene
prima del segmento di testo è riservato al processore. La pila parte da 0x7fffffff andando
all'indietro. Da 0x80000000 in poi è riservato al sistema operativo.
.data
C: .byte 64 # char C = '@';
A: .word 1 # int A = 1;
B: .half # short B;
VET: .space 40 # int VET[10];
PUNT1: .word 0 # int* PUNT1 = NULL;
PUNT2: .word # char* PUNT2;
Da questo codice che alloca alcune variabili, possiamo elencate le principali direttive
dell'assemblatore e il loro significato:
.data <addr> Dichiara il segmento dati a partire dall'indirizzo dato
.asciiz “str” Memorizza una stringa terminandola con il carattere nullo
.ascii “str” Memorizza una stringa senza il terminatore
.byte b1,...bn Memorizza dati contigui da un byte
.word w1,...wn Memorizza dati contigui da una word
.half h1,...hn Memorizza dati contigui da una half word
.space n Alloca n byte contigui
.text <addr> Dichiara il segmento di testo a partire dall'indirizzo dato
.globl sym Dichiara un'etichetta globale visibile dagli altri file
.eqv Sostituisce il secondo operando al primo come il #define nel C
Un bell'esempio con somma di un array (l'ho scritto io e ci ho messo un'ora lelz):
.data 0x10000000
ARRAY: .word 1,2,3,4,5,6,7,8,9,10
RESULT: .word 0
.text 0x00400000
.globl main
main: lw $t0, ARRAY # carica primo elemento array
lw $s0, RESULT # carica variabile risultato
la $t2, ARRAY # memorizza indirizzo array
NEXT: add $s0, $s0, $t0 # incrementa RESULT
slti $t1, $t0, 10 # condizione di terminazione
beq $t1, $0, END
addi $t2, $t2, 4 # indirizzo prossimo elemento
lw $t0, ($t2) # carica prossimo elemento
j NEXT # loop
END: sw $s0, RESULT # memorizza risultato finale
LOCALI
Le variabili locali entrano in gioco quando si parla di funzioni. I primi quattro parametri
vanno passati negli appositi registri a0, a1, a2, a3:
Se sono di tipo scalare o un puntatore a 32 bit;
– Gli array sono considerati puntatori al primo elemento;
– Per passare una struct dipende dal compilatore (di solito indirizzo primo elemento).
–
Nel raro caso in cui una funzione abbia più di quattro parametri bisogna inserirli nella pila.
Ed è un bel casino che vedremo dopo D:
I valori si ritorno invece vanno inseriti nel registro v0 e valgono le stesse tre regole dei
parametri in ingresso. Nel caso in cui il valore sia un numero in virgola mobile si usa anche
il registro v1. Le variabili locali poi possono essere gestite in vari modi:
Scalari e puntatori
In un registro tra s0-s7 se non è richiesto l'uso del loro indirizzo di memoria
– Nell'area di attivazione della funzione
–
Scalare accessibile attraverso puntatori
Solo nell'area di attivazione della funzione, perché richiede un indirizzo
–
Array e struct
Solo nell'area di attivazione della funzione
–
Un altro esempio bastardo che ho fatto io con tanta pazienza: un array di 5 elementi uguali
a zero, prendiamo gli ultimi tre e ci aggiungiamo sei.
.data 0x10000000
VET: .space 20
A: .word 2
.text 0x00400000
.globl main
main: li $t0, 5 # carica 5 (lunghezza array)
FOR: lw $s0, A # carica indice
slt $t1, $s0, $t0 # se (A < 5) continua
beq $t1, $0, END
la $t2, VET # carica VET[0]
sll $s0, $s0, 2 # indirizzamento da word a byte
add $t2, $t2, $s0 # aggiungi offset primo elemento
lw $s1, ($t2) # carica VET[A]
addi $s1, $s1, 6 # aggiungi 6
sw $s1, ($t2) # memorizza VET[A]
srl $s0, $s0, 2 # indirizzamento da byte a word
addi $s0, $s0, 1 # incrementa contatore
sw $s0, A # memorizza A
j FOR
END:
SYSTEM CALL
Alcune funzionalità sono molto ricorrenti in ogni programma e sarebbe bello se non
dovessimo riprogrammarle noi ogni volta, per esempio la stampa a video o la lettura da
tastiera (che sono in realtà cose piuttosto complicate da fare, quindi è bene avere queste
funzionalità già pronte e gestite dal sistema operativo). Questi servizi si chiamano system
call, cioè “chiamate al sistema”.
Ogni system call ha un numero identificativo, dei parametri ed eventualmente un valore di
ritorno. Alcuni esempi:
print_int Stampa un intero
print_string Stampa una stringa (terminata con il carattere nullo)
read_int Legge un numero fino al carattere a capo incluso
read_string Legge una stringa di a1 caratteri, la memorizza in un buffer a0
e la termina con il carattere nullo
exit Interrompe l'esecuzione di un programma
Nome Codice Argomenti Risultato
print_int 1 $a0
print_float 2 $f12
print_double 3 $f12
print_string 4 $a0
read_int 5 $v0
read_float 6 $f0
read_double 7 $f0
read_string 8 $a0, $a1
sbrk 9 $a0 $v0
exit 10
Per poter usare queste funzioni bisogna:
Caricare il codice della system call in $v0;
– Caricare gli argomenti negli appositi registri (se richiesti);
– Eseguire syscall;
– Estrarre gli eventuali valori di ritorno.
–
Esempio di un programma che stampa a video “La risposta e' 5”
.data 0x10000000
str: .asciiz “La risposta e'”
.text 0x00400000
li $v0 4 # codice della print_string
la $a0, str # carica indirizzo stringa
syscall # stampa della stringa
li $v0, 1 # codice della print_int
li $a0, 5 # carica 5
syscall # stampa dell'intero
li $v0, 10 # codice della exit
syscall # termina programma
Vediamo nello specifico come gestire i SOTTOPROGRAMMI (aka funzioni) in MIPS
perché sono un po' complicate.
Nei linguaggi di alto livello la chiamata a funzione crea un record di attivazione nella
cima dello stack. La presenza di più funzioni in esecuzione semplicemente provoca
l'impilamento di più record uno sopra l'altro. Al termine di una funzione il corrispettivo
record viene deallocato. Il main è il primo record che viene allocato in memoria, ed esiste
per tutta l'esecuzione del programma.
Ogni record contiene queste informazioni:
Parametri in ingresso con i loro valori
– Indirizzo di ritorno
– Informazioni sulla gestione della memoria nel record
– Variabili locali
– Valore di ritorno
–
Cosa succede a basso livello?
Il chiamante (CALLER) gestisce il passaggio dei parametri;
– Il chiamante attiva il sottoprogramma tramite l'istruzione apposita;
– L'istruzione di chiamata gestisce il salvataggio del valore di ritorno e l'esecuzione
– del sottoprogramma;
Il chiamato (CALLEE) gestisce l'allocazione delle variabili locali e del valore di
– ritorno.
Purtroppo il modello di chiamata a sottoprogramma non è identico in tutti i processori. Nel
caso del MIPS che analizzeremo per esempio le variabili possono essere salvate nei
registri o nello stack.
CHIAMATA A SOTTOPROGRAMMA
jal LABEL # $ra ← PC attuale
# PC ← indirizzo LABEL
RITORNO DA SOTTOPROGRAMMA
jr $ra # PC ← $ra
PASSAGGIO DEI PARAMETRI
I primi quattro parametri vanno inseriti nei registri del blocco a0-a3 (gli array sono
– puntatori al primo elemento);
Eventuali parametri in più vanno impilati sullo stack.
–
VALORE DI RITORNO
Viene salvato nel registro v0;
– Per il tipo double viene utilizzato anche il registro v1.
–
CONVENZIONI NEL SALVATAGGIO DEI REGISTRI
Salvati dal chiamante Salvati dal chiamato
t0-t9 s0-s7
Temporanei Variabili
a0-a3 fp
Argomento Frame Pointer
v0-v1 ra
Valore di ritorno Return Address
GESTIONE DELLO STACK
Purtroppo il MIPS non ha le istruzioni belle comode push e pop come l'x86. Devi fare te
da solo, ricordandoti che lo stack cresce da indirizzi più alti a indirizzi più bassi.
L'operazione di push si ottiene decrementando lo stack pointer e inserendo il dato co sw.
addi $sp, $sp, -4
sw $s0, 0($sp)
L'operazione di pop si ottiene prelevando il dato con lw e incrementando lo stack pointer.
lw $s0, 0($sp)
addi $sp, $sp, 4
La chiamata a funzione comporta:
Prologo del chiamante
– Salto al chiamato
– Prologo del chiamato
– Esecuzione del sottoprogramma
– Epilogo del chiamato
– Ritorno al chiamante
– Epilogo del chiamante
–
Convenzioni importanti:
Il prologo del chiamante corrisponde al caricamento dei parametri e al salvataggio
– dei registri;
Il prologo del chiamato corrisponde al salvataggio di registri e all'allocazione di
– variabili locali (quest'area è detta FRAME di attivazione della funzione);
Il salvataggio dei registri avviene seguendo l'ordine crescente dei numeri all'interno
– di ciascun gruppo;
Non si salvano registri laddove ciò è inutile: il fp si salva solo se è necessario
– referenziale dati nel frame di attivazione, il ra non si salva nelle funzioni foglia.
Ecco un'illustrazione più chiara di come può apparire lo stack durante una funzione:
t0-t7
a0-a3
v0-v1
arg5
arg6 ← Fine prologo del chiamante
fp precedente ← $fp
ra salvato
s0-s7
variabili locali ← Fine frame di attivazione
… ← $sp
CONVENZIONI CALLER
Prologo del chiamato
Inserisci in a0-a3 i parametri della funzione;
– Salva sullo stack i temporanei (ed eventualmente gli argomenti) che vuoi riavere
– inalterati al termine della funzione.
Salto al chiamato
jal FUNZIONE
CONVENZIONI CALLEE
Prologo del chiamato
Crea l'area di attivazione con un pushone (devi già sapere i byte che ti servono!);
– Se il fp è in uso il suo valore viene salvato per primo e il nuovo fp punterà a se
– stesso;
Se la funzione non è foglia ra viene salvato in modo da non essere sovrascritto (e
– quindi perduto) dalle chiamate successive;
Salva in pila i registri s0-s7 per le variabili locali.
–
Esecuzione del sottoprogramma
Epilogo del chiamato
Scrive in v0 il valore di ritorno;
– Ripristina i registri s0-s7 assegnati a variabili locali;
– Ripristina fp e ra se sono stati salvati;
– Elimina l'area di a