Il 0% ha trovato utile questo documento (0 voti)
10 visualizzazioni

Appunti Assembler

linguaggio Assembly

Caricato da

viyec18753
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Il 0% ha trovato utile questo documento (0 voti)
10 visualizzazioni

Appunti Assembler

linguaggio Assembly

Caricato da

viyec18753
Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Sei sulla pagina 1/ 41

lOMoARcPSD|8708955

Appunti Assembler

Architettura dei calcolatori e sistemi operativi (Politecnico di Milano)

Scansiona per aprire su Studocu

Studocu non è sponsorizzato o supportato da nessuna università o ateneo.


Scaricato da Damian Albescu ([email protected])
lOMoARcPSD|8708955

ARCHITETTURA DEI CALCOL ATORI E SISTEMI OPERATIVI

01 - ASSEMBLER

INTRODUZIONE A RISC-V slide 1a

Livelli di astrazione e flusso di compilazione

Linguaggio ad alto livello → produttività e portabilità del codice.

“Astrae” macchina hardware, fornendo costrutti più semplici e

generali.

È un’interfaccia generica, valida per tutti i computer, essendo

uguale per tutte le macchine, non può fornire accesso alle

funzionalità specifiche di una determinata macchina.

Linguaggio assembler → r a p p re s e n t a z i o n e testuale delle

istruzioni. Permette di svolgere operazioni fisicamente impossibili

per altri linguaggi.

Consente:

- una rappresentazione alfanumerica delle istruzioni

- di ignorare il formato binario del linguaggio macchina

Linguaggio macchina → codifica di istruzioni e dati in binario

Linguaggio assemblatore e linguaggio macchina

Il linguaggio assemblatore è il linguaggio simbolico che consente di programmare un calcolatore

utilizzando un traduttore (assemblatore) per convertire il codice assembly in linguaggio macchina

(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

Se nel codice sorgente sono presenti

• riferimenti simbolici definiti in moduli (file) esterni a quello assemblato

• riferimenti simbolici che dipendono dalla rilocazione del modulo

=> per risolvere i rifermenti tra moduli è necessario il LINKER (collegatore).

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

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.

Se nel linguaggio avanzato

posso fare: c = a + b ;

Nel linguaggio assembly mi

servono molte più informazioni

per scrivere la stessa cosa:

ld a ; (load a)

ld b ; (load b)

add c, a, b ; (add c,a,b)

sd c ; (store c)

Architettura di riferimento dei calcolatori

Flusso di esecuzione di una

istruzione in linguaggio

macchina (va fatto per ogni

operazione/variabile)

Instruction Set Architecture - ISA

• È 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

• Ogni architettura di processore ha il suo linguaggio macchina:

• Architettura definita dall’insieme delle istruzioni → ISA (Instruction Set Architecture)

• Due processori con lo stesso linguaggio macchina hanno la stessa architettura anche se le

implementazioni hardware possono essere diverse

Occorre definire:

• modello della memoria e registri per la gestione degli operandi;

• insieme delle istruzioni macchina → formato e dimensione

• modalità di riferimento agli operandi → tipi di indirizzamento in memoria e/o nei registri del

processore e/o costanti

• tipi di dati e dimensioni

• modalità operative

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Linguaggio assembler: tipologia delle istruzioni

Sono classificate in 4 categorie:

1. Istruzioni aritmetico-logiche (arithmetic / logic)

2. Istruzioni di trasferimento da memoria a registro (load), da registro in memoria (store) e

trasferimento tra registri (move);

3. Istruzioni per modificare il flusso di esecuzione di un programma: salto condizionato (branch) e

salto incondizionato (jump), salto a sottoprogramma, ritorno da sottoprogramma;

4. Istruzioni di trasferimento dati in ingresso/uscita (I/O);

Salto condizionato = cicli if, else, for

salto incondizionato = chiamata a funzione, chiamata a libreria

Architettura del processore RISC-V

Sviluppata all’università di Berkeley a partire dal 2010, appartiene alla famiglia delle architetture RISC

(reduced instruction set computer) sviluppate dal 1980 in poi.

Principali obiettivi delle architetture RISC:

• Istruzioni molto semplificate in modo da semplificare la progettazione dell'hardware e del compilatore

• Ridurre il tempo di esecuzione delle istruzioni in modo da massimizzare le prestazioni

• Minimizzare i costi

Registri RISC-V

• nelle istruzioni assembler esiste una corrispondenza tra variabili ↔ registri

• 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

registri (Register File)

• numerati da 0 a 31, e indicati come x0, x1, … , x31

• per convenzione si possono usare i nomi simbolici ABI per indicare la specificità di utilizzo dei

registri, ad esempio:

- s0, s1, … per contenere variabili (tipicamente locali) in C

- t0, t1, … per contenere valori temporanei (durante il calcolo delle espressioni)

• essendo 32 registri → servono 5 bit per codificare l’indirizzo (2^5 = 32)

• I registri possono contenere solo valori interi

- i valori decimali vanno gestiti tramite istruzioni e registri in virgola mobile: f0, f1, ...

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Registri non referenziabili

• a livello di ISA interessano solo i 32 registri del

Register File referenziabili nelle istruzioni macchina

• inoltre ci sono 3 registri da 32 bit non referenziabili

in ISA e di uso specifico:

Memoria - struttura e indirizzamento

Memoria indirizzabile al singolo byte:

• una parola 32-bit contiente 4 byte

• una parola doppia 64-bit contiene 8 byte

Gli indirizzi di memoria sono a 64 bit,

espressi con indirizzamento al byte

Le istruzioni occupano 32 bit di memoria →

hanno indirizzi allineati alla parola cioè

multipli di 4 byte (vincolo di allineamento).

RISC-V usa allineamento di tipo LITTLE

ENDIAN

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

SET ISTRUZIONI RISC-V slide 1b

Formato delle istruzioni RISC-V

Tutte le istruzioni RISC-V hanno la stessa dimensione (32bit)

I 32 bit hanno un significato diverso a seconda del formato (o tipo) di istruzione

- il tipo di istruzione è riconosciuto in base al valore di alcuni bit (7 bit) meno significativi

(codice operativo – OPCODE)

Le istruzioni possono:

• referenziare al massimo tre registri: due registri sorgente (rs1 e rs2) e un registro destinazione (rd); i

registri sono a 64 bit

• indicare direttamente un valore numerico (immediato), che possiede un significato diverso a seconda

del tipo di istruzione

Istruzioni e variabili in linguaggio macchina

Istruzioni - costituite da:

• codice operativo (opcode) che identifica l’istruzione e definisce l’operazione associata.

• riferimento/i all’operando/i su cui agisce l’istruzione (modalità di indirizzamento) può essere:

• implicito nel codice operativo

• direttamente il valore numerico dell’operando (immediato)

• un registro del processore

• in modo diretto o indiretto una locazione di memoria

Variabili - accessibili dal processore tramite due modalità:

• per riferimento rappresentato da un indirizzo di un registro o di memoria

• per valore contenuto nel registro o nella parola di memoria associata all’indirizzo e rappresentato

tramite codifica binaria

ISTRUZIONI ARITMETICO-LOGICHE

Ha 3 operandi + il codice operativo:

• 2 registri sorgente contenenti i valori da elaborare

• 1 registro di destinazione per il risultato

OPCODE DEST, SORG1, SORG2

Dove DEST, SORG1, SORG2 sono registri referenziabili a 64-bit

Istruzioni aritmetiche di somma e sottrazione (in complemento a 2)

addu e subu lavorano in modo analogo su operandi considerati UNSIGNED cioè senza segno

(tipicamente indirizzi) e non generano segnalazione di overflow del complemento a 2.

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Istruzioni logiche and, or e xor (lavorano bit a bit):

Pseudo-istruzione di negazione logica not (bit a bit): not rd, rs1 # rd <- not rs1

Esempi

Codice C Codice RISC-V

R = A + B add s0, s1, s2

add t0, s1, s2


A = B + C + D add s0, t0, s3
E = F -A sub s4, s5, s0

add t0, s1, s2


sub t1, s3, s4
a = (b+c)-(d-e)+f
sub t0, t0, t1
add s0, t0, s5

add immediate
• addiziona una costante: il valore del secondo

addendo è presente nell’istruzione come costante

(immediato) (nell’esempio 100 in base dieci)

• la costante deve essere un numero rappresentabile su 12 bit (con segno in complemento a 2)

• 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

costante cambiata di segno.

Esempi di estensione di segno di una costante su 12 bit

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

del formato a 64 bit → 5₁₀ : 0000 …. 0000 0000 0101

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

del formato a 64 bit → -5₁₀ : 1111 …. 1111 1111 1011

Moltiplicazione di numeri interi

Ci sono 2 istruzioni:

• rs1 e rs2 contengono i fattori, rd contiene il prodotto

• si considerano solo i 32 bit meno significativi dei registri sorgente rs1 e rs2, mentre il risultato in rd

può estendersi su 64 bit

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Divisione e resto di interi con risultato intero

Divisione: rs1 dividendo, rs2 divisore, rd quoziente (intero)

Resto (remainder): rs1 dividendo, rs2 divisore, rd resto

Numeri reali (cenno)

• si rappresentano in virgola mobile secondo lo standard IEEE 754, su parole da 32 bit o 64 bit

(precisione singola o doppia)

• si caricano in un banco di registri ausiliario: f0, f1, f2, ecc…

• hanno apposite operazioni aritmetiche: fadd, fsub, fmul, ecc… (permettono di trasferire numeri tra

registri in virgola mobile e registri interi, arrotondando se serve)

Istruzioni di scorrimento (shift)

Istruzioni di scorrimento logico a sinistra/destra di una costante k intera positiva compresa tra 1 e 63:

Esempi

s0 iniziale: 0000 …. 0000 0000 0011 1010

s0 finale: 0000 …. 0000 0000 1110 1000

s1 iniziale: 0000 …. 0000 0000 1000 1100

s1 finale: 0000 …. 0000 0000 0010 0011

Istruzioni di confronto

Confronto tra due variabili: ad esempio devo verificare se una variabile sia minore di un’altra o no.

Assegna a rd il valore 1 se rs1 < rs2; altrimenti assegna il valore 0.

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;

altrimenti assegna valore 0.

• slt/slti eseguono i confronti in aritmetica SIGNED, cioè interpretando i valori numerici come

complemento a due

• bisogna usare sltu/sltui , se i valori numerici sono indirizzi (e quindi UNSIGNED)

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Pseudo-istruzioni

Rappresentano un modo compatto ed intuitivo di specificare un insieme di una o più istruzioni

frequentemente utilizzate. Servono a semplificare la programmazione.

ISTRUZIONI DI TRASFERIMENTO DA/VERSO MEMORIA

Istruzione per trasferire dalla memoria fisica ai registri ( load ) e viceversa ( store ).

Ci sono vari formati e anche delle varianti:

Istruzione load
Trasferisce una copia della parola doppia contenuta ad uno specifico indirizzo di memoria (a 64bit)

mettendola in un registro della CPU (lasciando inalterata la memoria)

• la CPU invia l’indirizzo della locazione desiderata alla memoria e richiede un’operazione di lettura del

contenuto della memoria

• La memoria effettua la lettura della parola doppia memorizzata all’indirizzo specificato e la invia alla

CPU caricandola nel registro destinazione rd

Istruzione store
Trasferisce una parola doppia da un registro della CPU (64bit) mettendola in uno specifico indirizzo di

memoria (a 64bit), sovrascrivendone il contenuto precedente.

• la CPU invia l’indirizzo della locazione desiderata alla memoria, insieme alla parola che vi deve essere

scritta, e richiede un’operazione di scrittura in memoria;

• la memoria effettua la scrittura della parola doppia all’indirizzo specificato;

Istruzione ld
In RISC-V l’istruzione ld ha 3 argomenti, per esempio:

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

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

addizionare alla costante;

L'indirizzo della parola doppia di memoria da caricare nel registro destinazione è ottenuto sommando

la costante estesa in segno (fino a 64 bit) e il contenuto del registro base;

→ In rd viene caricato il valore contenuto all’indirizzo di memoria contenuto in s1 (base register che

contiene il valore di base address) sommato a 100 (offset)

Istruzione sd
Servono 2 registri sorgenti, per esempio:

Alla locazione di memoria di indirizzo s1 + 100 è memorizzato il valore a 64bit contenuto in s2

base register che contiene offset

il valore di base address

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

di una costante (come la #define in C)

Pseudo-istruzioni di trasferimento

Caricamento di costante a 32bit: load immediate →

Caricamento di indirizzo a 32bit: load address →

LABEL è un’etichetta simbolica per indicare il valore dell’indirizzo di memoria relativo al PC

Entrambe caricano un valore da 32bit nella metà meno significativa del registro destinazione rd e poi

estendono il segno alla metà più significativa del registro

load address la ( ) VS load double ld ( )

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:

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Esercizio: array con elementi interi da 64bit

• Caricare il contenuto dell’elemento A[3] di un array A di elementi interi a 64-bit nella variabile a

allocata al registro s1

• Indirizzo di inizio dell’array A allocato nel registro base s2

Risposta → ld s1, 24(s2)

L’elemento i-esimo di un array di parole doppie (double) si troverà nella locazione rb + 8 x i,


dove:

- rb è il registro di base

- i è l’indice dell’elemento dell’array

- Il fattore 8 dipende dall’indirizzamento al byte della memoria in RISC-V

Lettura di un elemento generico dell’array

Esempio 1

10

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

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

all’indirizzo indicato da PC, e avanza all’istruzione successiva.

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.

Si può modificare il flusso in due modi diversi:

1. Salto condizionato ( branch ) → subordinato al verificarsi di una condizione

2. Salto incondizionato ( jump ) → il salto viene sempre eseguito

Costrutti di controllo e istruzioni di salto

• alterano l’ordine di esecuzione delle istruzioni → la prossima istruzione è quella individuata

dall’indirizzo di destinazione del salto, non necessariamente la successiva.

• Permettono di realizzare:

• Costrutti di controllo condizionati (come if… else)

• Costrutti ciclici (come for)

• Chiamate di funzioni

11

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Salti condizionati

branch_cond rs1, rs2, ind_salto

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

blt → branch on less than (salta se strettamente minore)

bge → branch on greater or equal (salta se maggiore o uguale)

Spesso è utile condizionare l’esecuzione di una istruzione al fatto che una variab. sia minore di un’altra:

slt rd, rs1, rs2 # set on less than

assegna a rd il valore 1 se rs1 < rs2; altrimenti assegna il valore 0

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

• sltu si usa se i valori numerici sono indirizzi (quindi UNSIGNED)

Esempio - slt e beq per realizzare l’equivalente di blt

Salti incondizionati

Istruzioni di salto incondizionato (unconditional jump): il salto viene sempre eseguito:

j (jump)

jr (jump on register - ritorno da funzione)

jal (jump and link - chiamata di funzione)

12

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Esempi

Esempio - if … then … else

Salto incondizionato con collegamento

Le istruzioni di salto incondizionato specificano l’indirizzo a cui saltare e prevedono che il PC

dell’istruzione successiva a quella di salto venga salvato in un registro (collegamento).

Due istruzioni native per salti incondizionati:

Salto incondizionato: pseudo-istruzioni

13

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Etichette ( LABELS )

• per un programmatore non è conveniente/possibile calcolare/conoscere esplicitamente l’indirizzo

numero di dati/istruzioni

• si usano etichette (label) che si associano a specifici indirizzi del programma

• l’assemblatore poi traduce le etichette nei corrispondenti indirizzi

14

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

FORMATO DELLE ISTRUZIONI RISC-V slide 2

Le istruzioni RISC-V sono di 6 formati:

Corrispondenza tra formati e istruzioni macchina native:

1) Formato istruzioni di tipo R - aritmetico logiche

Sono: add, sub, or, and, xor, slt, sll, srl, sra

Va letto da dx a sx, mentre funct3 e funct7 indicano la variante specifica dell’istruzione.

Esempi

add t0, s1, s2 →

(esadecimale)

La combinazione dei campi opcode, funct3, funct7 specifica esattamente l’operazione che il

processore deve eseguire.

15

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

add s1, s2, s3 →

sub s2, s2, s3 →

2) Formato istruzioni di tipo I - immediate

Sono:

• addi, ori, andi, xori, slti, slli, srai


• ld, lw, lh, lb
• jalr

imm[11:0] → valore dell’immediato su 12 bit in CPL2

Esempi - aritmetiche

addi s1, s1, 4 →

slli s1, s0, 4 →

# registro s0 << 4 bit

slti t0, s2, 8


# t0 = 1 if s2 < 8; →

# t0 = 0 otherwise

Esempi - load

ld t0, 32(s3)

# rs1: registro base s3

# rd: registro dest. t0

# imm[11:0] offset da sommare al registro base per ottenere l’indirizzo di memoria

3) Formato istruzioni di tipo S - store

Sono: sd, sw, sh, sb.

• rs1: registro contenente il primo operando sorgente (registro base)

• rs2: registro contenente il secondo operando sorgente (dato da scrivere in memoria)

• funct3 : indica la variante specifica dell’istruzione

16

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

• imm[11:0] : valore dell’immediato su 12 bit in CPL2

Esempio

sd t0, 32(s3)

# rs1: registro base s3

# rs2: registro contenente il valore da scrivere in memoria

# imm[11:0] offset (12bit) da sommare al registro base per ottenere l’indirizzo di memoria

Esempio - confronto formati load, add e store

Formato istruzioni di salto

Salto condizionato: Salto incondizionato con collegamento:

• 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)

4) Formato istruzioni di tipo B

Sono: beq, bne, bge, blt

• funct3: indicano la variante specifica dell’istruzione per effettuare il confronto tra rs1 e rs2

17

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

• 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à

necessario aggiungere uno 0 nel LS bit per ottenere la distanza in byte.

Per il principio di località spaziale degli indirizzi di memoria è utile calcolare l’indirizzo di destinazione

del salto come offset rispetto all’istruzione corrente.

→ Modalità di indirizzante relativa al Program Counter

Esempi

1. bne s0, s1, L1


• per l’etichetta L1 nel formato B si hanno solo 12 bit

• Il valore di L1 è calcolato come offset in CPL2 rispetto al PC in modo da saltare di 2^11 parole in

avanti o indietro rispetto all’istruzione corrente

• l’assemblatore sostituirà l’etichetta L1 con lo spiazzamento alla mezza parola relativo a PC:

(L1 - PC)/2, dove:

• PC contiene l’indirizzo dell’istruzione di salto;

• La divisione per 2 serve per calcolare l’indirizzo di mezza parola

2. beq s1, s2, L1

3.

Salto incondizionato (con collegamento)

• specificano l’indirizzo a cui saltare e prevedono che il PC dell’istruzione successiva a quella di salto

venga salvato in un registro destinazione (collegamento)

• Esistono due istruzioni native di salto incondizionato con collegamento usate per le chiamate a

sottoprogramma/funzione:

18

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

5.1) Formato di istruzioni di tipo J - salto incondizionato (con collegamento)

È una sola: jal

• 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à

necessario aggiungere uno 0 nel LS bit per ottenere la distanza in byte.

5.2) Formato di istruzioni di tipo I - salto incondizionato da registro (con collegamento)

È una sola: jalr

• imm[11:0] : valore dell’offset su 12 bit in CPL2 che esprime la distanza di salto (positiva/negativa) in

byte rispetto al reg. base rs1

Attenzione: non serve aggiungere un bit a 0 nel LS bit (come si deve fare nei formati B e J) poiché

l’offset su 12 bit è già espresso al byte.

Pseudoistruzioni di salto incondizionato (jump)

19

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

GESTIONE DELLE COSTANTI SU 32-BIT

• Le istruzioni di tipo I consentono di rappresentare costanti su 12bit in CPL2.

• 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

immediati in due istruzioni successive.

Le due istruzioni sono:

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

CPL2 della costante. Attenzione all’estensione di segno.

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

• auipc usata per espandere la pseudo-istruzione la di caricamento di indirizzo a 32bit

• di solito la costante cost20 è specificata come valore numerico o come costante simbolica dichiarata

tramite la direttiva .eqv


• lo spiazzamento spi20 esprime la distanza (in byte) e di solito è specificato come etichetta di

istruzione o di dato

6) Formato istruzioni di tipo U

Sono: lui, auipc

imm[31:12] → valore dei 20bit più significativi dell’immediato da 32bit in CPL2

20

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

DA C AD ASSEMBLER RISC-V slide 3

Il passaggio da codice sorgente all’esecuzione del programma si articola in 4 fasi:

1. Compilazione

2. Assemblaggio

3. Collegamento (linking)

4. Caricamento ed esecuzione

Compilazione

Il programma C viene tradotto in linguaggio simbolico (assembler)

Il codice prodotto è in forma simbolica, non numerica → non eseguibile dal processore

Assemblaggio

• Espande le pseudo-istruzioni

• Traduce le istruzioni simboliche in codifica binaria in base ai formati delle istruzioni

• Risolve i simboli relativi a:

- indirizzi di memoria

- etichette (label) di istruzioni/dati

- offset

Collegamento (linking)

Collega più moduli e genera il codice macchina eseguibile

STRUTTURA DEL PROGRAMMA IN MEMORIA, VARIABILI E DIRETTIVE

L’immagine in memoria di un programma in esecuzione (processo per il SO) è costituita da 3 segmenti:

1. Codice (o testo): contiene il main e le funzioni utente

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

registri salvati e variabili locali)

Codice e dati sono segmenti dichiarati nel programma dal programmatore

Il segmento di pila viene creato dinamicamente dal SO al lancio del processo.

21

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Partizione della memoria virtuale di RISC-V con ISA RV64i - testo H&P

Indirizzi da 64bit → 16 cifre esadecimali

Max spazio di memoria = 2^64 byte = 16 Gi Gi byte ( 1 Gi = 2^30 = 10^9 = 1 G )

Usati 38 bit di indirizzo → 256 GByte

Dichiarazione dell’area dati e dell’area testo tramite direttive all’assemblatore

- MAIN: definisce in modo simbolico l’indirizzo di inizio del programma

- 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

dall’assemblatore per generare il codice oggetto e danno indicazioni su:

- Variabili e strutture dati da allocare nel segmento dati delle modulo di programma

- Istruzioni da allocare nel segmento codice del modulo

- Eventuale indicazione di simboli globali definiti nel modulo, cioè referenziabili anche da altri

moduli

Variabili e memoria

Il C ha costrutti di programmazione e variabili tipizzate, mentre il linguaggio assembler ha istruzioni,

memoria interna e memoria esterna (indirizzabile al byte)

• interna alla CPU: registri

• esterna alla CPU: RAM

Come vengono rappresentate le variabili in 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),

la cella di indirizzo 0x 100 contiene il valore 1

• se una variabile è già stata caricata in un registro il suo indirizzo sarà all’indirizzo del registro

22

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Dimensioni delle variabili

Nel linguaggio macchina ogni cosa va esplicitata nei dettagli dell’architettura: bisogna conoscere la

dimensione di ciascun tipo di dato per allocare le variabili in memoria.

L’unità di memoria minima indirizzabile è il singolo byte

Convenzioni per operare sulle variabili

In un programma ad alto livello le variabili generalmente sono:

• globali → allocate in memoria a indirizzo fissato (assoluto)

• locali e parametri → allocate nei registri del processore o nell’area di attivazione in pila

• dinamiche → in memora (qui non considerate)

In RISC-V le variabili sono manipolate tramite indirizzo simbolico di memoria o indirizzo (nome

simbolico) di registro.

Le istruzioni aritmetico-logiche operano su registri.

Quindi per operare su una variabile allocata in memoria è necessario:

1. caricarla in un registro libero ( load )

2. elaborarla nel registro

3. riscriverla in memoria ( store )

Per operare su una variabile locale già allocata in un registro ovviamente si salta la load e la store.

Variabile globale nominale

• è allocata in memoria virtuale ad un indirizzo fisso stabilito dall’assemblatore nel segmento dati statici

a partire dall’indirizzo 0x 0000 0000 1000 0000 verso indirizzi alti

• Per convenzione, l’indirizzo simbolico della variabile globale coincide con il nome della variabile

esempio: LONG a ⟷ A: .dword


nota: l’indirizzo simbolico è scritto in carattere minuscolo

• L’allocazione in memoria delle variabili globali procede in ordine di dichiarazione 0

Esempio - esercizio con array

23

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Allineamento in memoria delle variabili

Le variabili collocate in memoria sono allineate (cioè poste a indirizzi) secondo il loro tipo:

• byte (8bit) a indirizzo qualunque: 0, 1, 2, 3, 4, …

• mezza parola (half - 16bit) a indirizzo pari: 0, 2, 4, 6, …

• parola (word - 32bit) a indirizzo multipli quattro: 0, 4, 8, 12, …

• parola doppia (double word - 64bit) a indirizzo multiplo di otto: 0, 8, 16, 24, …

Si può cambiare l’allineamento con una direttiva

Esempio - allocazione non allineata

Direttive fondamentali assemblatore

24

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Dichiarazione variabili globali - scalari e vettori

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

con .zero n (n = ingombro var in byte) (RARS usa .space n ).

25

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Esempio - variabile globale con var intera a 32bit

Esempio - variabile globale con var LONG a 64bit

Esempio - variabile globale con due variabili

Esempio - puntatore a var globale con var LONG a 64bit

Esempio - vettore di interi LONG con accesso costante

26

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

ESEMPI TRADUZIONI IF-ELSE, WHILE, DO-WHILE, FOR

Esempio - IF (THEN-ELSE) - salto condizionato in avanti

Esempio - WHILE - salto condizionato in avanti

Esempio - DO-WHILE - salto condizionato indietro

Esempio - FOR - ciclo a conteggio - salto condizionato in avanti

27

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

SOTTOPROGRAMMI slide 4

Modello semplificato

Il programma chiamante (caller) deve:

• predisporre i valori dei parametro da passare alla funzione tramite i registri a2 - a7


• trasferire il controllo alla funzione tramite jal

La funzione chiamata (callee) deve:

• allocare l’area di attivazione sullo stack

• eseguire il compito richiesto

• memorizzare i risultato/i nel registro/i a0 (a1)


• restituire il controllo al chiamante tramite jr ra oppure ret

Istruzioni di chiamata e di ritorno

Chiamata a sottoprogramma

Salvataggio dell’indirizzo di rientro (PC+4) nel registro ra e salto a LABEL che è l’indirizzo simbolico

della prima istruzione del sottoprogramma

Ritorno da sottoprogramma

Posso anche usare ret (sono entrambe pseudo-istruzioni)

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

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Sottoprogrammi (funzioni)

Nei linguaggi ad alto livello la chiamata a sottoprogramma ha come effetto la creazione di un’area di

attivazione sulla pila (stack)

• ad ogni chiamata viene allocata dinamicamente un’area di attivazione

• quando il sottoprogramma ha finito l’area viene deallocata

• il main è il primo ad essere allocato e l’ultimo ad essere deallocato

• con sottoprogrammi annidati le aree vengono impilate sullo stack e l’ultima area corrisponde al

sottoprogramma correntemente in esecuzione

Esempio di allocazione delle aree di attivazione in caso di procedure annidate

Pila (stack)

• è una struttura dati dinamica costituita da parole doppie (64bit) organizzate in una coda LIFO (last-

in-first-out) per salvare le informazioni necessarie per la gestione dei sottoprogrammi

• È necessario un puntatore alla cima della pila: il registro sp (stack pointer), che viene aggiornato ogni

volta che viene inserito o estratto il valore di un registro nella pila

• push → inserisce i dati nella pila

• pop → estrae i dati dalla pila

Gestione della pila in RISC-V

• lo stack cresce da indirizzi di memoria alti versi indirizzi bassi

• 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

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Operazioni sulla pila: push e pop


• push (registro): per allocare e inserire nell’elemento in cima alla pila il valore contenuto in un registro

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

stessa quantità di byte di cui lo aveva decremento alla chiamata (nell’esempio 8)

Convenzioni per il salvataggio dei registri

• 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

ripristinati al rientro, ad esempio il registro ra e i registri s0-s7


• Il salvataggio di un registro varrà fatto solo se necessario: ad esempio il valore del registro ra non

sarà salvato nella pila se si tratta di una procedura “foglia”

Convenzioni adottate da RISC-V

• per ottimizzare gli accessi alla memoria, il

chiamante (caller) e il chiamato (callee) salvano

sulla pila soltanto un particolare gruppo di registri

ciascuno

• Inoltre il chiamato può usare la pila per le variabili

locali

Registro frame pointer

A ogni operazione di push/pop modifichiamo dinamicamente sp , ma può essere utile sfruttare anche il

registro fp (frame pointer) per memorizzare l’inizio dell’area

In questo modo di può deallocare velocemente l’area di attivazione al rientro dalla funzione

L’usa di fp è opzionale (GCC lo usa)

• il programmatore deve assegnare il valore al fp all’inizio della funzione (dopo averne salvato il valore

precedente nella pila)

• fp permette di concatenare le aree di attivazione impilate come in una lista di puntatori.

Passi del modello chiamata di RISC-V

Le istruzioni sono le 7 seguenti:

1. Prologo del programma chiamante

2. Salto alla procedura chiamata

3. Prologo della procedura chiamata (push)

4. Corpo elaborativo della procedura chiama

5. Epilogò della procedura chiama (pop)

6. Rientro al programma chiamante

7. Epilogo del programma chiamante

30

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Convenzioni del chiamante - caller

• Prologo del chiamante

- Passaggio dei parametri: il chiamante scrive in a2-a7 i parametri da passare in ingresso alla

funzione

‣ Se sono più di 6 li deve salvare sullo stack

- 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 →

Convenzioni del chiamato - calle

• Prologo del chiamato

- Alloca area/frame di attivazione: necessario calcolare la dimensione in byte dell’area richiesta:

addi $sp, $sp, - numbyte

- Se il registro fp è in uso

‣ Prima fp viene salvato sullo stack

‣ Poi viene aggiornato in modo da puntare alla cima dell’area di attivazione, cioè alla parola

di stack che contiene il valore appena salvato

- Se la funzione non è “foglia”

‣ 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

• Corpo elaborativo del chiamato

• Epilogo del chiamato

• Scrive nei registri a0 (a1) i valori di uscita

• Ripristina dalla pila i registri s1-s7 assegnati a variabili locali

• Se la funzione non è foglia, ripristina dalla pila il registro ra


• Se il registro fp è in uso, lo ripristina dalla pila

• Elimina area di attivazione: addi sp, sp, numbyte

• Rientro a chiamante →

Convenzioni del chiamante - caller

• Epilogo del chiamante

• Trova nel registro a0 (a1) il valore di uscita della funzione

• 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

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Convenzioni per variabili locali per il chiamato

Una variabile locale può essere gestita in vari modi, secondo il tipo di variabile e di come essa viene

utilizzata

• variabile di tipo scalare o puntatore – due modi

• associata a un registro del blocco s0-s7 (*) se non deve avere un indirizzo di memoria e se

bastano i registri

• altrimenti nell’area di attivazione della funzione, perchè deve avere un indirizzo;

• variabile di tipo scalare, che viene anche acceduta tramite puntatore:

• nell’area di attivazione della funzione, perché deve avere un indirizzo

• variabile di tipo array (o struct):

• sempre nell’area di attivazione della funzione, perchè deve avere un indirizzo

→ convenzione: la variabile di tipo array è allocata sullo stack a partire dagli indirizzi più bassi di

memoria (quindi array[0] sarà all’indirizzo di memoria più basso)

(*) Se una variabile locale è associata ad un registro del blocco s0-s7 , il registro deve essere stato

preventivamente salvato dal chiamato

32

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

RISC-V: ASSEMBL ATORE E COLLEGATORE (LINKER) slide 5

Assemblatore: traduzione in linguaggio macchina

• l’assemblatore traduce in linguaggio macchina (binario) un programma scritto in linguaggio assembly

(simbolico) e genera la rappresentazione oggetto del programma (che è in formato binario ma non

ancora eseguibile)

• Le pseudo-istruzioni costituiscono un insieme di istruzioni assembly pia ampio dell’insieme istruzioni

native

• L’assemblatore espande le pseudo-istruzioni nella corrispondente sequenza di istruzioni native

Esempio - espansione delle pseudo-istruzioni:

• move rd, rs1 viene tradotta in addi rd, rs1, 0


• li rd, cost32 viene tradotta in due istruzioni native:

• lui rd, &hi(cost32) // 20 bit sup - HI


• addi rd, rd, %lo(cost32) // 12 bit inf - LO

PROCESSO DI ASSEMBL AGGIO

• 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

corrispondente formato binario del linguaggio macchina:

• I codici operativi delle istruzioni sono tradotti nei corrispondenti codici binari

• I riferimenti ai registri nei corrispondenti indirizzi binari dei registri

• 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

degenera la tabella dei simboli del modulo

• Ogni segmento ( .text e .data ) di ogni modulo è assemblato con indirizzi virtuali rilocabili di

segmento (gli indirizzi di ogni segmento partono dall’indirizzo 0)

• Un programma eseguibile sarà generato a partire da più modulo (main, funzioni e librerie standard

coma la stdio del linguaggio C)

• Nella traduzione di un singolo modulo non è sempre possibile ottenere la traduzione di tutti i

riferimenti simbolici del modulo:

• i riferimenti relativi a identificatori definiti in altri moduli (simboli esterni) e identificatori

dipendenti dalla rilocazione del modulo (simboli rilocabili) costituiscono dei riferimenti non

risolti del modulo e saranno gestiti nella fase successiva dal collegatore (linker)

• Quindi il file oggetto prodotto dall’assemblatore deve contenere:

• Tabella dei simboli presenti nel modulo

• Tabella dei simboli esterni da ritoccare (tabella di rilocazione)

PRIMO PASSO - processo di assemblaggio

• Espande le pseudo-istruzioni e costruisce la tabella dei simboli del modulo

• I riferimenti simboli inseriti nella tabella possono essere:

• Etichette (L ABEL) che definiscono variabili del segmento dati - nella tabella si crea la coppia

<simbolo, indirizzo>

33

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

• Etichette (L ABEL) che contrassegnano istruzioni destinazioni di salto - nella tabella si crea la

coppia <simbolo, indirizzo>

• I valori degli indirizzi inseriti sono quelli rilocabili rispetto al segmento considerato (testo o dati)

Indirizzamento dati statici

• gli accessi a tutte le variabili, scalari o vettori, nel segmento dati statici sono ottenuto in modo

automatico tramite assemblatore e collegatore facendo uso della pseudo-istruzione la seguita da

un’istruzione di load/store con registro base appena caricato tramite la :

• la pseudo-istruzione la viene espansa in fase di traduzione come:

Esempio

34

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

SECONDO PASSO - processo di assemblaggio

È la fase di traduzione vera e propria: usa la tabella dei simboli del modulo e genera - oltre alla

traduzione - la tabella di rilocazione del modulo.

In tale tabella, un’istruzione è tradotta in modo incompleto nella forma: <indirizzo rilevabile istruzione,

codice op. istruzione, simbolo da rilocare> se il riferimento simboli è relativo a:

• variabili del segmento .data


• per variabili scalari la traduzione posta a 0 per convenzione

• per vettori la traduzione è tramite la pseudo-istruzione la , ossia tramite auipc e addi con

immediati posti a 0 per convenzione, costanti a 32bit tramite la pseudo-istruzione licon il

valore espresso tramite un’etichetta (simbolo su cui agisce un modificatore) viene espansa

tramite lui e addi con immediati posti a 0 per convenzione

• 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

calcolati dal collegatore (linker).

Esempio

Esempio di riferimento con MAIN

35

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Esempio con MAIN e procedura B

PROCESSO DI COLLEGAMENTO (LINKING)

Il collegatore (linker) ha il compito di generare un solo programma binario eseguibile (in formato

rilevabile) partendo da diversi moduli.

→ un solo spazio di indirizzamento (virtuale) per tutto il programma

Il linker calcola gli indirizzi dei riferimenti non risolti e completa la tradizione delle istruzioni presenti

nelle tabelle di rilocazione in base a queste informazioni:

• la dimensione del segmento testo e del segmento dati di ciascun modulo tradotto

• gli indirizzi di impianto

Operazioni svolte dal collegatore (linker)

1. Determinare la posizione in memoria virtuale, cioè l’indirizzo iniziale o base, delle sezioni codice e

dati dei diversi modulo (vedi esempio precedente)

2. Determinare il nuovo valore di tutti gli indirizzi simbolici che risultano modificati dallo spostamento

della base (creazione di una tabella dei simboli globale)

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

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Il file oggetto di un modulo

• l’intestazione descrive le dimensioni del segmento testo e del segmento dati del modulo e della loro

posizione di inizio in memoria

• 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.

• Le informazioni di rilocazione identificano le istruzioni e le parole di dati che dipendono da indirizzi

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

convente l’elenco dei riferimenti non risolti del modulo

• Le informazioni di debug, che non consideriamo ulteriormente

1. Determinazione della posizione in memoria dei moduli

• l’assemblatore alloca la sezione testo e la sezione dati a partire dall’indirizzo base 0

• 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 →

2. Creazione della tabella dei simboli globale

È costituita dall’unione delle tabelle dei simboli di tutti i moduli da collegare, modificati in base

all’indirizzo di base del modulo cui appartengono:

IND_FINALE = IND_INIZIALE + IND_BASE_MODULO

37

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

3. Correzione dei riferimenti nei moduli

• 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

modulo M con simbolo VS = IND + BASE_M

• %pcrel(S) = delta_VS = VS - PC cioè delta indirizzo effettivo dell’istruzione a cui si riferisce rispetto al

program counter

Regole da applicare in base al tipo di istruzione

→ ISTR è in formato J: inserire %pcrel(S)/2 su 20bit cioè (VS - PC)/2


→ ISTR è in formato B: inserire %pcrel(S)/2 su 12bit cioè. (VS - PC)/2

→ ISTR è in formato U:

• se auipc da espansione di la inserire: %pcrel_hi(S)


• se lui da espansione di la inserire: %hi(S)

→ ISTR è in formato I:

• se addi da espansione di la inserire: %pcrel_lo(S)


• se addi da espansione di li inserire: %lo(S)

Esempio

delta_IND = delta_VS

38

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

39

Scaricato da Damian Albescu ([email protected])


lOMoARcPSD|8708955

Il file eseguibile dell’intero programma

Somiglia al file oggetto di un singolo modulo, ma ormai contiene un programma completo (unione di

tutti i moduli) eseguibile, e informazioni su come caricarlo in memoria e gestirlo.

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

iniziale di esecuzione del programma (ossia il valore del simbolo MAIN).

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

verranno caricate in memoria in sequenza nel segmento di testo.

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

in memoria nel segmento dati, agli indirizzi dei dati corrispondenti.

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

tipo di errore e a correggerlo.

Caricamento ed esecuzione (runtime)

Nei Sistemi Operativi di famiglia UNIX (Linux), il nucleo (kernel) del SO carica il programma nella

memoria principale e ne lancia l’esecuzione.

Operazioni:

1. Legge l’intestazione del file eseguibile per determinare le dimensioni dei segmenti testo e dati, e

per conoscere l’indirizzo iniziale di esecuzione.

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

segmento per la pila (stack).

3. Carica le istruzioni e i valori iniziali dei dati dal file eseguibile alla memoria, mettendo tutto quanto

all’interno del nuovo spazio di indirizzamento.

4. Carica nella pila (stack) gli eventuali argomenti passati al programma.

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

Scaricato da Damian Albescu ([email protected])

Potrebbero piacerti anche