Appunti Assembler
Appunti Assembler
Appunti Assembler
01 - ASSEMBLER
generali.
Consente:
(binario).
• Le istruzioni sono in corrispondenza (quasi) uno a uno con quelle linguaggio macchina
• La notazione simbolica consente di rappresentare istruzioni, registri, dati e riferimenti alla memoria
Per eseguire un programma scritto in assembler serve un ASSEMBL ATORE che traduce in linguaggio
macchina in modo da tradurre i codici mnemonici delle istruzioni in codici operativi binari, sostituire
tutti i riferimenti simbolici degli indirizzi con la loro forma binaria e riservare spazio di memoria per le
variabili
Un programma eseguibile (istruzioni + dati) caricato in memoria è una sequenza di codici binari che il
processore interpreta come dati (interi, caratteri e numeri in virgola mobile) oppure come istruzioni da
eseguire.
posso fare: c = a + b ;
ld a ; (load a)
ld b ; (load b)
sd c ; (store c)
istruzione in linguaggio
operazione/variabile)
• È la descrizione del calcolatore riferita al suo linguaggio macchina, cioè all’insieme delle istruzioni
(instruction set) che possono essere interpretate direttamente dall’architettura del processore
• Due processori con lo stesso linguaggio macchina hanno la stessa architettura anche se le
Occorre definire:
• modalità di riferimento agli operandi → tipi di indirizzamento in memoria e/o nei registri del
• modalità operative
Sviluppata all’università di Berkeley a partire dal 2010, appartiene alla famiglia delle architetture RISC
• Minimizzare i costi
Registri RISC-V
• I registri sono elementi di memoria interni alla CPU utilizzati per memorizzare le variabili di un
programma
• Le variabili sono potenzialmente di numero infinito, mentre i registri del processore sono in numero
finito, per cui devo continuare a riutilizzare gli stessi registri molte volte, per variabili diverse.
• 32 registri generali da 64-bit (double word, mentre word = 32 bit) organizzati in un banco di
• per convenzione si possono usare i nomi simbolici ABI per indicare la specificità di utilizzo dei
registri, ad esempio:
- t0, t1, … per contenere valori temporanei (durante il calcolo delle espressioni)
- i valori decimali vanno gestiti tramite istruzioni e registri in virgola mobile: f0, f1, ...
ENDIAN
- il tipo di istruzione è riconosciuto in base al valore di alcuni bit (7 bit) meno significativi
Le istruzioni possono:
• referenziare al massimo tre registri: due registri sorgente (rs1 e rs2) e un registro destinazione (rd); i
• indicare direttamente un valore numerico (immediato), che possiede un significato diverso a seconda
• per valore contenuto nel registro o nella parola di memoria associata all’indirizzo e rappresentato
ISTRUZIONI ARITMETICO-LOGICHE
addu e subu lavorano in modo analogo su operandi considerati UNSIGNED cioè senza segno
Pseudo-istruzione di negazione logica not (bit a bit): not rd, rs1 # rd <- not rs1
Esempi
add immediate
• addiziona una costante: il valore del secondo
• la costante viene addizionata dopo averla estesa in segno da 12 bit fino a 64 bit
RISC V non ha un’istruzione di sottrazione di costante ( subi non esiste), perché basta usare addi con
Versione binaria a 12 bit della costante 5₁₀ → 5₁₀ : 0000 0000 0101
L’estensione di segno è effettuata copiando il bit più significativo del formato a 12 bit (0) nei bit a sx
Versione binaria a 12 bit della costante -5₁₀ → -5₁₀ : 1111 1111 1011
L’estensione di segno è effettuata copiando il bit più significativo del formato a 12 bit (1) nei bit a sx
Ci sono 2 istruzioni:
• si considerano solo i 32 bit meno significativi dei registri sorgente rs1 e rs2, mentre il risultato in rd
• si rappresentano in virgola mobile secondo lo standard IEEE 754, su parole da 32 bit o 64 bit
• hanno apposite operazioni aritmetiche: fadd, fsub, fmul, ecc… (permettono di trasferire numeri tra
Istruzioni di scorrimento logico a sinistra/destra di una costante k intera positiva compresa tra 1 e 63:
Esempi
Istruzioni di confronto
Confronto tra due variabili: ad esempio devo verificare se una variabile sia minore di un’altra o no.
Confronto tra variabile e costante: ad esempio devo verificare se una variabile sia minore di una
costante o no.
Prima estende in segno la costante da 12bit fino a 64bit, poi assegna a rd il valore 1 se rs1 < 8;
• slt/slti eseguono i confronti in aritmetica SIGNED, cioè interpretando i valori numerici come
complemento a due
Pseudo-istruzioni
Istruzione per trasferire dalla memoria fisica ai registri ( load ) e viceversa ( store ).
Istruzione load
Trasferisce una copia della parola doppia contenuta ad uno specifico indirizzo di memoria (a 64bit)
• la CPU invia l’indirizzo della locazione desiderata alla memoria e richiede un’operazione di lettura del
• La memoria effettua la lettura della parola doppia memorizzata all’indirizzo specificato e la invia alla
Istruzione store
Trasferisce una parola doppia da un registro della CPU (64bit) mettendola in uno specifico indirizzo di
• la CPU invia l’indirizzo della locazione desiderata alla memoria, insieme alla parola che vi deve essere
Istruzione ld
In RISC-V l’istruzione ld ha 3 argomenti, per esempio:
1. il registro destinazione (rd) in cui caricare la parola doppia letta dalla memoria;
2. una costante o spiazzamento (offset su 12 bit), in CP2 (espresso come numero di byte);
3. un registro base (base register rs1) che contiene il valore dell’indirizzo base (base address) da
L'indirizzo della parola doppia di memoria da caricare nel registro destinazione è ottenuto sommando
→ In rd viene caricato il valore contenuto all’indirizzo di memoria contenuto in s1 (base register che
Istruzione sd
Servono 2 registri sorgenti, per esempio:
Esempio
Memorizzare (copiare) una parola doppia dall’indirizzo di memoria [NUM1 + s0] all’indirizzo [NUM2 +
s0] , lasciandone in t0 una copia.
La direttiva .eqv associa 100 al simbolo NUM1, non è un’istruzione macchina, ma solo la dichiarazione
Pseudo-istruzioni di trasferimento
Entrambe caricano un valore da 32bit nella metà meno significativa del registro destinazione rd e poi
Si considera una parola doppia di memoria (double word) con contenuto (64bit) e indirizzo (64bit)
espressi in HEX:
si supponga che il simbolo NUM indichi l’indirizzo (’associazione tra simbolo NUM e indirizzo numerico
si costruisce con una direttiva che si vedrà in seguito) e che il contenuto del registro s1 valga 0:
• Caricare il contenuto dell’elemento A[3] di un array A di elementi interi a 64-bit nella variabile a
allocata al registro s1
- rb è il registro di base
Esempio 1
10
Esempio 2
ISTRUZIONI DI SALTO
Normalmente l’esecuzione delle istruzioni assembler è sequenziale, il registro Program Counter (PC),
indica l’indirizzo della prossima istruzione da eseguire, il flusso standard è: esegui l’istruzione
Le istruzioni per la modifica del flusso servono a modificare il PC rispetto al flusso standard
sequenziale. Possiamo ridirigere l’esecuzione del programma in ogni punto, saltando avanti e indietro.
• Permettono di realizzare:
• Chiamate di funzioni
11
Salti condizionati
s ignificato: se confrontando i registri rs1 e rs2 la condizione viene soddisfatta allora salta.
ind_salto : indirizzo di destinazione del salto espresso in modalità di indirizzamento relativo rispetto
al PC: PC ← PC + ind_salto
Spesso è utile condizionare l’esecuzione di una istruzione al fatto che una variab. sia minore di un’altra:
Combinando slt con beq e bne si possono realizzare test sui valori di due variabili ( =, !=, <, <=, >, =>)
e comporre pseudo-istruzioni.
Attenzione:
• slt esegue confronti in aritmetica SIGNED, cioè interpretando i valori numerici come cpl2
Salti incondizionati
j (jump)
12
Esempi
13
Etichette ( LABELS )
numero di dati/istruzioni
14
Sono: add, sub, or, and, xor, slt, sll, srl, sra
Esempi
(esadecimale)
La combinazione dei campi opcode, funct3, funct7 specifica esattamente l’operazione che il
15
Sono:
Esempi - aritmetiche
# t0 = 0 otherwise
Esempi - load
ld t0, 32(s3)
16
Esempio
sd t0, 32(s3)
# imm[11:0] offset (12bit) da sommare al registro base per ottenere l’indirizzo di memoria
• beq rs1, rs2, L1 (branch on equal) • jal rd, L1 (jump & link)
• bne rs1, rs2, L1 (branch on not equal) • jalr rd, offset12(rs1) (jump & link da
• e anche e
bge blt registro - cioè salvataggio dell’ind. di rientro in rd)
• funct3: indicano la variante specifica dell’istruzione per effettuare il confronto tra rs1 e rs2
17
• imm[12:1]: valore ricostruito dell’offset su 12 bit in CPL2 che esprime l’offset, cioè la distanza di
salto (positiva/negativa) rispetto al PC in termini di mezze parole (half word) => nell’eseguibile sarà
Per il principio di località spaziale degli indirizzi di memoria è utile calcolare l’indirizzo di destinazione
Esempi
• Il valore di L1 è calcolato come offset in CPL2 rispetto al PC in modo da saltare di 2^11 parole in
• l’assemblatore sostituirà l’etichetta L1 con lo spiazzamento alla mezza parola relativo a PC:
3.
• specificano l’indirizzo a cui saltare e prevedono che il PC dell’istruzione successiva a quella di salto
• Esistono due istruzioni native di salto incondizionato con collegamento usate per le chiamate a
sottoprogramma/funzione:
18
• imm[20:1] : valore “ricostruito” dell’offset (rispetto al PC) su 20 bit in CPL2 che esprime la distanza di
salto (positiva/negativa) rispetto al PC in termini di mezze parole (half word) => nell’eseguibile sarà
• imm[11:0] : valore dell’offset su 12 bit in CPL2 che esprime la distanza di salto (positiva/negativa) in
Attenzione: non serve aggiungere un bit a 0 nel LS bit (come si deve fare nei formati B e J) poiché
19
• Ma se volessi caricare costanti/indirizzi su 32bit in CPL2 per istruzioni li/la ? Devo caricarlo come
immediato in un registro tramite due istruzioni, suddividendo i 32bit in 20+12, trattandoli come due
1. lui (load upper immediate) → carica il primo immediato (i primi 20bit significativi) nei 20bit più
significativi della metà inferiore da 32bit di un registro da 64bit. Azzera i 12bit meno significativi
della metà inferiore del registro e il segno della metà inferiore viene esteso sui 64bit del registro:
2. addi → consente di sommare il secondo immediato che rappresenta i 12bit meno significativi in
Istruzione load upper immediate (lui) e auipc (add upper immediate to pc)
• lui serve a espandere la pseudo-istruzione li di caricamento di costante a 32bit
• di solito la costante cost20 è specificata come valore numerico o come costante simbolica dichiarata
istruzione o di dato
20
1. Compilazione
2. Assemblaggio
3. Collegamento (linking)
4. Caricamento ed esecuzione
Compilazione
Il codice prodotto è in forma simbolica, non numerica → non eseguibile dal processore
Assemblaggio
• Espande le pseudo-istruzioni
- indirizzi di memoria
- offset
Collegamento (linking)
2. Dati: valori di variabili globali e statiche e variabili dinamiche (create con malloc)
3. Pila (o stack): contiene le aree (record) di attivazione delle funzioni (indirizzi, parametri, eventuali
21
Partizione della memoria virtuale di RISC-V con ISA RV64i - testo H&P
- Non occorre dichiarare esplicitamente il segmento pila, che inizia all’indirizzo 0x 0000 003F FFFF
FFF0 e cresce verso indirizzi più bassi
- .data e .text sono direttive inserite nel codice assembler che saranno interpretate e risolte
- Variabili e strutture dati da allocare nel segmento dati delle modulo di programma
- Eventuale indicazione di simboli globali definiti nel modulo, cioè referenziabili anche da altri
moduli
Variabili e memoria
• ciascuna variabile corrisponde a un indirizzo di memoria che contiene il valore della variabile
• esempio: la variabile a corrisponde all’indirizzo 0x 100 ; se la variabile è inizializzata a 1 (in base 10),
• se una variabile è già stata caricata in un registro il suo indirizzo sarà all’indirizzo del registro
22
Nel linguaggio macchina ogni cosa va esplicitata nei dettagli dell’architettura: bisogna conoscere la
• locali e parametri → allocate nei registri del processore o nell’area di attivazione in pila
In RISC-V le variabili sono manipolate tramite indirizzo simbolico di memoria o indirizzo (nome
simbolico) di registro.
Per operare su una variabile locale già allocata in un registro ovviamente si salta la load e la store.
• è allocata in memoria virtuale ad un indirizzo fisso stabilito dall’assemblatore nel segmento dati statici
• Per convenzione, l’indirizzo simbolico della variabile globale coincide con il nome della variabile
23
Le variabili collocate in memoria sono allineate (cioè poste a indirizzi) secondo il loro tipo:
• parola doppia (double word - 64bit) a indirizzo multiplo di otto: 0, 8, 16, 24, …
24
Alcuni simulatori RISC-V (es RARS) richiedono direttive .dword, .word, .half, .byte specifichino
sempre un valore iniziale per la variabile; è comunque sempre possibile allocare una variabile
25
26
27
SOTTOPROGRAMMI slide 4
Modello semplificato
Chiamata a sottoprogramma
Salvataggio dell’indirizzo di rientro (PC+4) nel registro ra e salto a LABEL che è l’indirizzo simbolico
Ritorno da sottoprogramma
Problemi
Ci sono vari problemi, come preservare i valori passati come parametri alla procedura / funzione,
oppure ci potrebbe essere una procedura che potrebbe aver bisogno di più parametri rispetto ai 6
registri a disposizioni e ai 2 registri per la restituzione dei risultati → la soluzione è usare la pila (stack)
Annidamento di funzioni
28
Sottoprogrammi (funzioni)
Nei linguaggi ad alto livello la chiamata a sottoprogramma ha come effetto la creazione di un’area di
• con sottoprogrammi annidati le aree vengono impilate sullo stack e l’ultima area corrisponde al
Pila (stack)
• è una struttura dati dinamica costituita da parole doppie (64bit) organizzate in una coda LIFO (last-
• È necessario un puntatore alla cima della pila: il registro sp (stack pointer), che viene aggiornato ogni
• il registro sp contiene l’indirizzo della prima cella occupata in cima alla pila
• l’inserimento di un dato nella pila (operazione di push ) avviene prima decrementando sp per allocare
lo spazio (aumentando la dim. della pila) e poi eseguendo l’istruzione sd per inserire il dato nella pila
• l’estrazione di un dato dalla pila (operazione di pop ) avviene prima eseguendo l’istruzione ld per
estrarre il dato e poi incrementando sp per deallocare lo spazio, riducendo la dimensione della pila
Push - inserire due elementi nella pila Pop - estrarre due elementi dalla pila
29
Tutto lo spazio in pila di cui una funzione ha bisogno v iene esplicitamente allocato dal
programmatore decrementando al valore del registro sp il numero di byte necessari (nell’esempio -8)
• pop (registro): per deallocare l’elemento in cima alla pila e copiarlo in un registro
Quanto una funzione rientra, essa dealloca l’area di attivazione incrementando il registro sp della
• l’esecuzione di un sottoprogramma non deve interferire con l’ambiente del programma chiamante
• Il contenuto dei registri che saranno utilizzati dal chiamato vanno salvati nella pila e andranno
ciascuno
locali
A ogni operazione di push/pop modifichiamo dinamicamente sp , ma può essere utile sfruttare anche il
In questo modo di può deallocare velocemente l’area di attivazione al rientro dalla funzione
• il programmatore deve assegnare il valore al fp all’inizio della funzione (dopo averne salvato il valore
30
- Passaggio dei parametri: il chiamante scrive in a2-a7 i parametri da passare in ingresso alla
funzione
- Opzionale:
‣ Se il chiamante intende preservare i valori dei registri temporanei t0-t6 per usarli al
rientro, li deve salvare esplicitamente sullo stack e poi ripristinarlo dopo il rientro del
chiamato
‣ Allo stesso modo, se serve preservarlo, salva sulla pila gli argomenti a2-a7 e il valore dei
registri a0-a1
• Salto al chiamato →
- Se il registro fp è in uso
‣ Poi viene aggiornato in modo da puntare alla cima dell’area di attivazione, cioè alla parola
‣ Salva il registro ra sulla pila perchè sarà ancora usato (e quindi sovrascritto) in chiamata
annidata
- Salva sulla pila i registro s0És7 (callee-saved registers) se li userà per variabili locali
• Rientro a chiamante →
• Opzionale:
• ripristina dalla pila i registri a0Ða3 che erano stati preservati per il rientro della funzione
• ripristina dalla pila i registri temporanei t0-t6 che erano stati preservati per il rientro della
funzione
31
Una variabile locale può essere gestita in vari modi, secondo il tipo di variabile e di come essa viene
utilizzata
• associata a un registro del blocco s0-s7 (*) se non deve avere un indirizzo di memoria e se
bastano i registri
→ convenzione: la variabile di tipo array è allocata sullo stack a partire dagli indirizzi più bassi di
(*) Se una variabile locale è associata ad un registro del blocco s0-s7 , il registro deve essere stato
32
(simbolico) e genera la rappresentazione oggetto del programma (che è in formato binario ma non
ancora eseguibile)
native
• Applicato modulo per modulo al programma: per ogni modulo (file) sorgente genera il modulo (file)
oggetto corrispondente, che viene collegato al altri file oggetti e a funzioni di libreria per produrre il
file eseguibile
• Il processo esami, riga per riga, il codice sorgente di un modulo e traduce le istruzioni simboliche nel
• I codici operativi delle istruzioni sono tradotti nei corrispondenti codici binari
• I riferimenti simbolici del modulo (identificatori di variabili, etichette di salto, nomi di funzioni)
saranno tradotti degli indirizzi binari corrispondenti: per questa operazione l’assemblatore
• Ogni segmento ( .text e .data ) di ogni modulo è assemblato con indirizzi virtuali rilocabili di
• Un programma eseguibile sarà generato a partire da più modulo (main, funzioni e librerie standard
• Nella traduzione di un singolo modulo non è sempre possibile ottenere la traduzione di tutti i
dipendenti dalla rilocazione del modulo (simboli rilocabili) costituiscono dei riferimenti non
risolti del modulo e saranno gestiti nella fase successiva dal collegatore (linker)
• Etichette (L ABEL) che definiscono variabili del segmento dati - nella tabella si crea la coppia
<simbolo, indirizzo>
33
• Etichette (L ABEL) che contrassegnano istruzioni destinazioni di salto - nella tabella si crea la
• I valori degli indirizzi inseriti sono quelli rilocabili rispetto al segmento considerato (testo o dati)
• gli accessi a tutte le variabili, scalari o vettori, nel segmento dati statici sono ottenuto in modo
Esempio
34
È la fase di traduzione vera e propria: usa la tabella dei simboli del modulo e genera - oltre alla
In tale tabella, un’istruzione è tradotta in modo incompleto nella forma: <indirizzo rilevabile istruzione,
• per vettori la traduzione è tramite la pseudo-istruzione la , ossia tramite auipc e addi con
valore espresso tramite un’etichetta (simbolo su cui agisce un modificatore) viene espansa
• Simboli esterni non presenti nella tabella dei simboli del modulo (ad esempio per istruzioni di branch
e jump ): la traduzione del simbolo è posta a 0 per convenzione;
I valori dei simboli da rilocare posti a 0 per convenzione nella tabella di rilocazione saranno poi
Esempio
35
Il collegatore (linker) ha il compito di generare un solo programma binario eseguibile (in formato
Il linker calcola gli indirizzi dei riferimenti non risolti e completa la tradizione delle istruzioni presenti
• la dimensione del segmento testo e del segmento dati di ciascun modulo tradotto
1. Determinare la posizione in memoria virtuale, cioè l’indirizzo iniziale o base, delle sezioni codice e
2. Determinare il nuovo valore di tutti gli indirizzi simbolici che risultano modificati dallo spostamento
3. Risolvere in tutti i moduli i riferimenti a indirizzi simbolici che sono stati modificati, in base alle
tabelle di rilocazione
→ per fare queste operazioni, i moduli oggetto devono contenere una serie di informazioni aggiuntive,
oltre al codice:
36
• l’intestazione descrive le dimensioni del segmento testo e del segmento dati del modulo e della loro
• Il segmento testo contiene il codice in linguaggio macchina delle procedure (o funzioni) del file
sorgente. Queste procedure potrebbero essere non eseguibili a causa di riferimenti non risolti.
• Il segmento dati statici contiene un rappresentazione binata dei dati definiti nel file sorgente. Anche i
dati potrebbero essere incompleti a causa di riferimenti non risolti a etichette definite in altri file.
assoluto all’interno del file eseguibile del programma completo. La posizione di queste istruzioni e
dati deve essere modificata se parti del programma vengono spostate in memoria.
• La tabella dei simboli associa un indirizzo alle etichette “esterne” contenute nel modulo sorgente e
• Ma i modulo non possono essere tutti caricati nella stessa zona di memoria; essi vanno invece
caricati dal linker sequenzialmente rispettando la struttura generale del modello di memoria
Esempio →
È costituita dall’unione delle tabelle dei simboli di tutti i moduli da collegare, modificati in base
37
• ISTR un’istruzione riferita dalla tabella di rilocazione di un modulo M, con simbolo S e indirizzo IND
• VS l’indirizzo rilocato (ind. effettivo) di una istruzione ISTR riferita dalla tabella di rilocazione di un
• %pcrel(S) = delta_VS = VS - PC cioè delta indirizzo effettivo dell’istruzione a cui si riferisce rispetto al
program counter
→ ISTR è in formato U:
→ ISTR è in formato I:
Esempio
delta_IND = delta_VS
38
39
Somiglia al file oggetto di un singolo modulo, ma ormai contiene un programma completo (unione di
L’intestazione ha dimensioni fisse e contiene alcuni campi necessari per guidare il lancio del
programma. Specifica le dimensioni del codice eseguibile e della lista dei valori iniziali, e l’indirizzo
Il codice eseguibile è la lista delle istruzioni del programma. Tutte le istruzioni sono ormai in forma
numerica completa, ossia tutti i loro campi sono numericamente risolti. Al lancio del programma, esse
I valori iniziali dei dati statici (var globali) sono la lista dei valori iniziali da dare ai dati (a quelli
inizializzati – gli altri vengono azzerati per default). Al lancio del programma, tali valori vengono caricati
La tabella dei simboli globale è includibile facoltativamente a solo scopo di debug. Se è presente, al
lancio del programma viene caricata con il codice. Eseguendo il programma passo-passo per debug,
quando un’istruzione genera un indirizzo numerico, consultando la tabella il sistema debugger trova il
nome del simbolo corrispondente e lo visualizza. Serve per aiutare il programmatore a comprendere il
Nei Sistemi Operativi di famiglia UNIX (Linux), il nucleo (kernel) del SO carica il programma nella
Operazioni:
1. Legge l’intestazione del file eseguibile per determinare le dimensioni dei segmenti testo e dati, e
2. Crea un nuovo spazio di indirizzamento per il programma. Questo spazio è abbastanza grande per
contenere i segmenti di testo (con eventuale tabella dei simboli globale) e dei dati, nonché un
3. Carica le istruzioni e i valori iniziali dei dati dal file eseguibile alla memoria, mettendo tutto quanto
5. Inizializza i registri dell’architettura. In generale, la maggior parte dei registri viene azzerata, tranne
il registro stack pointer, cui viene assegnato l’indirizzo della prima cella libera della pila (stack).
6. Esegue un’apposita procedura di avvio (che fa parte del nucleo del sistema operativo). Tale
procedura copia gli argomenti del programma dalla pila ai registri e poi chiama la procedura main
del programma, saltando all’indirizzo iniziale di esecuzione specificato dal file eseguibile. Quando
main termina, la procedura di avvio conclude il programma tramite la chiamata di sistema exit.
40