550 likes | 729 Views
Compilazione separata. Compilazione separata. define/include. Finora abbiamo trattato solo programmi C contenuti in un unico file. typedef. variabili globali. Struttura tipica di un sorgente C. prototipi F1..FN. main. def F1. …. def FN. Compilazione separata (2).
E N D
Compilazione separata define/include • Finora abbiamo trattato solo programmi C contenuti in un unico file typedef variabili globali Struttura tipica di un sorgente C prototipi F1..FN main def F1 … def FN
Compilazione separata (2) • Scopi di suddividere il codice sorgente C su più file • per raggruppare un insieme di funzioni congruenti (e solo quelle) in un unico file • per incapsulare un insieme di funzioni esportando solo quelle che vogliamo fare utilizzare agli altri (pubbliche) • per compilare una volta per tutte un insieme di funzioni e metterlo a disposizione di altri in una libreria • es. le librerie standard viste finora ….
Compilazione separata (2.1) • Problemi da risolvere : • (1) come si possono utilizzare funzioni definite in altri file ? • Vogliamo che il compilatore continui a fare i controlli di tipo etc … • (2) come posso compilare una volta per tutte le funzioni definite in un file separato ? • non voglio doverle ricompilare ogni volta che le uso in un altro file (come avviene per printf(), scanf() ) • voglio che il codice assembler risultante possa essere facilmente ‘specializzato’ per generare l’eseguibile finale
Compilazione separata (3) define/include glob.h typedef • Tipicamente : variabili globali main.c main fun_toK.h fun_1toN.h prototipi Fk+1..FN prototipi F1..Fk fun_toK.c def F1 fun_toN.c def Fk+1 … … def Fk def FN
define/include glob.h typedef variabili globali #include “glob.h ” #include “fun_toK.h” #include “fun_toN.h” …... main.c fun_toK.h fun_1toN.h prototipi Fk+1..FN prototipi F1..Fk fun_toK.o ass F1 fun_toN.o ass Fk+1 … … ass Fk ass FN
Compilazione separata (4) • Per utilizzare delle funzioni definite in file diversi basta • (1) specificare il prototipo (dichiarazione) prima dell’uso • tipicamente includendo un file che lo contiene • (2) avere a disposizione una versione precompilata delle funzioni stesse (il modulo oggetto relativo al file che le contiene) • Perché questo basta ?
Compilazione separata (5) • Perché basta ? • Il prototipo della funzione permette al compilatore di fare i controlli di tipo • ogni funzione di cui non è noto il prototipo viene considerata void -> int • questo genera errori strani ma non fa fallire la compilazione (provate a non includere stdio.h….) • Dal modulo oggetto si può generare correttamente l’eseguibile finale del programma che utilizza le funzioni definite separatamente senza ricompilarle da capo (cioè dal sorgente complessivo)
Compilazione separata Seconda parte
Compilazione separata (5.1) define/include glob.h typedef • Partiamo da più file di testo : variabili globali main.c main fun_toK.h fun_1toN.h prototipi Fk+1..FN prototipi F1..Fk fun_toK.c def F1 fun_toN.c def Fk+1 … … def Fk def FN
Compilazione separata (5.2) • Vogliamo ottenere un unico eseguibile • Formato di un eseguibile ELF Numero che contraddistingue il file come eseguibile File a.out Magic number Ampiezza area di memoria occupata dalle variabili globali NON inizializzate Altre info Ampiezza BSS Variabili globali inizializzate I-Data segment Text segment Codice del programma (assemblato)
Compilazione separata (5.3) FRAME per la funzione main • L’eseguibile contiene tutte le informazioni per creare la configurazione iniziale dello spazio di indirizzamento (loading) File a.out 232 - 1 Stack Area vuota Magic number Altre info BSS-segment Data Ampiezza BSS I-Data segment I-Data segment Text segment Text 0
Compilazione separata (6.0) file1.c modulo oggetto preproc file1.o compil assembler linker Opzioni del gcc permettono di fermarsi in corrispondenza dei vari passi assembler -c compil file2.o file2.c -S preproc file2.s a.out -E eseguibile
Compilazione separata (6) • Come si crea il modulo oggetto? • gcc -c file.c produce un file file.o che contiene il modulo oggetto di file.c • Il formato dell’oggetto dipende dal sistema operativo • Che informazioni contiene l’oggetto ? • L’assemblato del sorgente testo e dati (si assume di partire dall’indirizzo 0) • La tabella di rilocazione • La tabella dei simboli (esportati ed esterni)
Compilazione separata (7) • Tabella di rilocazione • identifica le parti del testo che riferiscono indirizzi assoluti di memoria • es. JMP assoluti, riferimenti assoluti all’area dati globali (LOAD, STORE…) • questi indirizzi devono essere rilocati nell’eseguibile finale a seconda della posizione della prima istruzione del testo (offset) • all’indirizzo contenuto nell’istruzione ad ogni indirizzo rilocabile va aggiunto offset
Datimain datiN main.o Situazione iniziale eseguibile Testomain ? Ind inizio ? TabRiloc,TabSimbol TabRiloc,TabSimbol TabRiloc,TabSimbol Dati k datiN fun_toK.o Dati N datiN fun_toN.o Dati N datiN Testo k Testo N Testo N
Compilazione separata (8) • Tabella di rilocazione (cont.) • il codice pre-compilato è formato da testo e dati binari • l’assemblatore assume che l’indirizzo iniziale sia 0 ind X ind Tabella di rilocazione X 0
Datimain datiN Ind inizio main.o Datimain Testomain 0 Dati k Dati N Tr,ts Ind inizio Testomain Testo k Testo N Tr,ts 0 Tr,ts Dati k datiN fun_toK.o Dati N datiN fun_toN.o Dati N datiN Testo k Testo N 0 Testo N 0
Compilazione separata (9) • Tabella di rilocazione (cont.) • ad ogni indirizzo rilocabile va aggiunto offset, l’indirizzo iniziale nell’eseguibile finale ind + offset X + offset ind offset ind + offset
Compilazione separata (10) • Tabella dei simboli • identifica i simboli che il compilatore non è riuscito a ‘risolvere’, cioè quelli di cui non sa ancora il valore perché tale valore dipende dal resto dell’eseguibile finale • ci sono due tipi di simboli ... • definiti nel file ma usabili altrove (esportati) • es: i nomi delle funzioni definite nel file, i nomi delle variabili globali • usati nel file ma definiti altrove (esterni) • es: le funzioni usate nel file ma definite altrove(es. printf())
Compilazione separata (11) • Tabella dei simboli (cont.) • per i simboli esportati, la tabella contiene • nome, indirizzo locale • per i simboli esterni contiene • nome • indirizzo della/e istruzioni che le riferiscono
Compilazione separata (12) • Il linker si occupa di risolvere i simboli. • Analizza tutte le tabelle dei simboli. • Per ogni simbolo non risolto (esterno) cerca • in tutte le altre tabelle dei simboli esportati degli oggetti da collegare (linkare) assieme • nelle librerie standard • nelle librerie esplicitamente collegate (opzione -l)
Compilazione separata (12.1) • Il linker si occupa di risolvere i simboli (cont.) • Se il linker trova il simbolo esterno • eventualmente ricopia il codice della funzione (linking statico) nell’eseguibile • usa l’indirizzo del simbolo per generare la CALL giusta o il giusto riferimento ai dati • Se non lo trova da errore ... • Provate a non linkare le librerie matematiche ...
Compilazione separata (13) file1.c preproc file1.o compil assembler linker Opzioni del gcc permettono di fermarsi in corrispondenza dei vari passi assembler -c compil file2.o file2.c -S preproc file2.s -E eseguibile
Esempio: percolation ... dmat2.o dmat2.c percolation-sol.c r.h dmat2.h • Come costruire l’eseguibile (1): • $gcc -Wall -pedantic -c dmat2.c • --crea dmat2.o
Esempio: percolation … (2) dmat2.o percolation-sol.o dmat2.c percolation-sol.c r.h dmat2.h • Come costruire l’eseguibile (2): • $gcc -Wall -pedantic -c percolation-sol.c • --crea percolation-sol.o
Esempio: percolation … (3) exe dmat2.o percolation-sol.o dmat2.c percolation-sol.c r.h dmat2.h • Come costruire l’eseguibile (3): • $gcc dmat2.o percolation-sol.o -o exe • --crea l’eseguibile ‘exe’
Esempio: percolation … (4) $gcc -Wall -pedantic -c dmat2.c --crea dmat2.o $gcc -Wall -pedantic -c percolation-sol.c --crea percolation-sol.o $gcc dmat2.o percolation-sol.o -o exe --crea l’eseguibile ‘exe’ • se modifico dmat2.c devo rieseguire (1) e (3) • se modifico dmat2.h devo rifare tutto
Esempio: percolation … (5) $gcc -M dmat2.c --fa vedere le dipendenze da tutti i file anche dagli header standard delle librerie dmat2.o : dmat2.c /usr/include/stdio.h \ /usr/include/sys/types.h \ … … … … … • perche’ questo strano formato ? • per usarlo con il make ….
Come visualizzare i moduli oggetto • Comando nm options file.o fornisce tutti i simboli definiti in file.o • $nm -g dmat2.o fornisce solo i simboli esportati • Comandi objdump e readelf permettoni di leggere le varie sezioni dell’eseguibile • $objdump -d dmat2.o fornisce il testo disassemblato • -rtabelle di rilocazione • -tsymbol table • Vedere ancheinfo binutilsda emacs
Makefile Il file dependency system di Unix (serve ad automatizzare il corretto aggiornamento di più file che hanno delle dipendenze)
makefile: idea di fondo • (1) Permette di esprimere dipendenze fra file • es. f.o dipende da f.c e da t.h ed r.h • in terminologia make : • f.oè detto target • f.c, t.h, r.hsono una dependency list
makefile: idea di fondo (2) • (2) Permette di esprimere cosa deve fare il sistema per aggiornare il target se uno dei file nella dependency list è stato modificato • es. se qualcuno ha modificato f.c, t.h o r.h, per aggiornare f.o semplicemente ricompilare f.c usando il comando gcc -Wall -pedantic -c f.c • In terminologia make : • la regola di aggiornamento di uno o più target viene detta make rule
makefile: idea di fondo (2) • (3) L’idea fodamentale è: • descrivere tutte le azioni che devono essere compiute per mantenere il sistema consistente come make rule in un file (Makefile) • usare il comando make per fare in modo che tutte le regole descritte nel Makefile vengano applicate automaticamente dal sistema
Formato delle ‘make rule’ Target list Dependency list : • Formato più semplice f.o : f.c t.h r.h gcc -Wall -pedantic -c f.c Command 1 Command list … Command N
Formato delle ‘make rule’ (2) Target list Dependency list : • ATTENZIONE!!! f.o : f.c t.h r.h gcc -Wall -pedantic -c f.c Command 1 … Qua deve esserci un TAB Command N
Formato delle ‘make rule’ (3) Fra due regole deve esserci almeno una LINEA VUOTA • Esempio con più regole exe: f.o r.o gcc f.o r.o -o exe f.o: f.c t.h r.h gcc -Wall -pedantic -c f.c r.o: r.h r.c gcc -Wall -pedantic -c r.c Il file deve terminare con un NEWLINE
Formato delle ‘make rule’ (4) • L’ordine delle regole è importante! • Il make si costruisce l’albero delle dipendenze a partire dalla prima regola del makefile Il/I target della prima regola trovata sono la radice dell’albero exe
Formato delle ‘make rule’ (5) • L’ordine delle regole è importante! • Il make si costruisce l’albero delle dipendenze a partire dalla prima regola del makefile Ogni nodo nella dependency list della radice viene appeso come figlio exe f.o r.o
Formato delle ‘make rule’ (6) • L’ordine delle regole è importante! • Poi si visitano le foglie e si aggiungono le dipendenze allo stesso modo Si considerano le regole che hanno f.o e r.o come target exe f.o r.o f.c r.c t.h r.h
Formato delle ‘make rule’ (7) • L’ordine delle regole è importante! • La generazione dell’albero termina quando non ci sono più regole che hanno come target una foglia exe Albero delle dipendenze complessivo f.o r.o f.c r.c t.h r.h
Come viene usato l’albero ... • Visita bottom up • Per ogni nodo X si controlla che il tempo dell’ultima modifica del padre sia successivo al tempo dell’ultima modifica di X Se t1 > t2, si esegue la command list della regola che ha come target il padre exe t2 f.o r.o • gcc -Wall -pedantic -c f.c f.c r.c t1 t.h r.h
Come viene usato l’albero … (2) • Visita bottom up • Se il file corrispondente ad un nodo X non esiste (es. è stato rimosso) ... Si esegue comunque la regola che ha come target X exe • gcc -Wall -pedantic -c f.c t2 f.o r.o f.c r.c t1 t.h r.h
Come si esegue il make ... • Se il file delle regole si chiama ‘Makefile’ • basta eseguire $ make • altrimenti …. $ make -f nomefile gcc -Wall -pedantic -c f.c $ • stampa dei comandi eseguiti per aggiustare i tempi sull’albero delle dipendenze • -n per stampare solo i comandi (senza eseguirli)
Come si esegue il make … (2) • È possibile specificare una radice dell’albero diversa dal target nella prima regola del file • dobbiamo passare il nome del target come parametro al make. Es. $ make f.o Crea solo questo sottoalbero f.o f.c t.h r.h
Variabili ... • È possibile usare delle variabili per semplificare la scrittura del makefile • stringhe di testo definite una volta ed usate in più punti # nomi oggetti objects = r.o f.o # regole exe: $(objects) gcc $(objects) -o exe
Variabili (2) • Inoltre ci sono delle variabili predefinite che permettono di comunicare al make le nostre preferenze, ad esempio : • quale compilatore C utilizzare per la compilazione CC = gcc • le opzioni di compilazione preferite CFLAGS = -Wall -pedantic • a che serve poterlo fare ?
Regole implicite ... • Le regole che abbiamo visto finora sono più estese del necessario • Il make conosce già delle regole generali di dipendenza fra file, basate sulle estensioni dei nomi • es : nel caso del C, sa già che per aggiornare un XX.o è necessario ricompilare il corrispondente XX.c usando $CC e $CFLAGS • quindi una regole della forma XXX.o: XXX.c t.h r.h gcc -Wall -pedantic -c XXX.c
Regole implicite … (2) • È equivalente a XXX.o: XXX.c t.h r.h $(CC) $(CFLAGS) XXX.c • e sfruttando le regole implicite del make può essere riscritta come XXX.o: t.h r.h
Regole implicite … (3) • Riscriviamo il nostro esempio con le regole implicite e le variabili : CC = gcc CFLAGS = -Wall -pedantic objects = f.o r.o exe: f.o r.o $(CC) $(CFLAGS) $(objects) -o exe f.o: t.h r.h r.o: r.h