Compilazione separata
1
Compilazione separata
• Finora abbiamo trattato solo programmi C
contenuti in un unico file
define/include
typedef
variabili globali
prototipi F1..FN
main
Struttura
tipica di un
sorgente C
def F1
…
def FN
2
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 ….
3
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
4
Compilazione separata (3)
• Tipicamente :
define/include
glob.h
typedef
variabili globali
main
fun_toK.h
prototipi F1..Fk
def F1
…
def Fk
fun_toK.c
main.c
fun_1toN.h
prototipi Fk+1..FN
def Fk+1
fun_toN.c
…
def FN
5
define/include
glob.h
typedef
variabili globali
fun_toK.h
#include “glob.h ”
#include “fun_toK.h”
main.c
#include “fun_toN.h”
…...
prototipi F1..Fk
ass F1
…
ass Fk
fun_toK.o
fun_1toN.h
prototipi Fk+1..FN
ass Fk+1
fun_toN.o
…
ass FN
6
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 ?
7
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)
8
Compilazione separata
Seconda parte
10
Compilazione separata (5.1)
• Partiamo da più file di testo :
define/include
glob.h
typedef
variabili globali
main
fun_toK.h
prototipi F1..Fk
def F1
…
def Fk
fun_toK.c
main.c
fun_1toN.h
prototipi Fk+1..FN
def Fk+1
fun_toN.c
…
def FN
11
Compilazione separata (5.2)
• Vogliamo ottenere un unico eseguibile
– Formato di un eseguibile ELF
File a.out
Magic number
Altre info
Numero che contraddistingue il file
come eseguibile
Ampiezza area di
memoria occupata dalle variabili
globali NON inizializzate
Ampiezza BSS
Variabili globali
I-Data segment inizializzate
Text segment
Codice del programma
(assemblato)
12
Compilazione separata (5.3)
– L’eseguibile contiene tutte le informazioni per creare
la configurazione iniziale dello spazio di
indirizzamento (loading)
FRAME per la funzione main
File a.out
Magic number
Altre info
Stack
232 - 1
Area vuota
BSS-segment
Ampiezza BSS
I-Data segment
I-Data segment
Text segment
Text
Data
0
13
Compilazione separata (6.0)
file1.c
modulo oggetto
file1.o
preproc
compil
assembler
linker
Opzioni del gcc permettono
di fermarsi in corrispondenza
dei vari passi
assembler
compil
file2.c
preproc
-E
-S
file2.s
-c
file2.o
a.out
eseguibile
14
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)
15
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
16
Datimain
datiN
main.o
Situazione iniziale eseguibile
Testomain
?
Ind inizio
TabRiloc,TabSimbol
?
TabRiloc,TabSimbol
TabRiloc,TabSimbol
Dati
datiNk
Testo k
fun_toK.o
Dati
N
datiN
Dati
datiNN
fun_toN.o
Testo
Testo NN
17
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
Tabella di rilocazione
X
X
ind
0
18
Datimain
datiN
Ind inizio
main.o
Testomain
Tr,ts
Datimain
Dati k
Dati N
0
Ind inizio
Testomain
Testo k
Testo N
Tr,ts
Dati
datiNk
Testo k
0
Tr,ts
fun_toK.o
0
Dati
N
datiN
Dati
datiNN
Testo
Testo NN
fun_toN.o
0
19
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
20
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())
21
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
22
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)
23
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 ...
24
Compilazione separata (13)
file1.c
file1.o
preproc
compil
assembler
linker
Opzioni del gcc permettono
di fermarsi in corrispondenza
dei vari passi
assembler
compil
file2.c
preproc
-E
-S
-c
file2.o
file2.s
eseguibile
25
Esempio: percolation ...
dmat2.o
dmat2.c
dmat2.h
r.h
percolation-sol.c
Come costruire l’eseguibile (1):
$gcc -Wall -pedantic -c dmat2.c
--crea dmat2.o
26
Esempio: percolation … (2)
dmat2.o
percolation-sol.o
dmat2.c
dmat2.h
r.h
percolation-sol.c
Come costruire l’eseguibile (2):
$gcc -Wall -pedantic -c percolation-sol.c
--crea percolation-sol.o
27
Esempio: percolation … (3)
exe
dmat2.o
percolation-sol.o
dmat2.c
dmat2.h
r.h
percolation-sol.c
Come costruire l’eseguibile (3):
$gcc dmat2.o percolation-sol.o -o exe
--crea l’eseguibile ‘exe’
28
Esempio: percolation … (4)
$gcc -Wall -pedantic -c dmat2.c
--crea dmat2.o
$gcc -Wall -pedantic -c percolationsol.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
29
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 ….
30
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
• -r tabelle di rilocazione
• -t symbol table
– Vedere anche info binutils da emacs
31
Makefile
Il file dependency system di Unix
(serve ad automatizzare il corretto
aggiornamento di più file che hanno
delle dipendenze)
32
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.h sono una dependency list
33
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
34
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
35
Formato delle ‘make rule’
• Formato più semplice
Target list :
Dependency list
Command 1
…
Command list
Command N
f.o : f.c t.h r.h
gcc -Wall -pedantic -c f.c
36
Formato delle ‘make rule’ (2)
• ATTENZIONE!!!
Target list :
Dependency list
Command 1
…
Qua deve esserci
un TAB
Command N
f.o : f.c t.h r.h
gcc -Wall -pedantic -c f.c
37
Formato delle ‘make rule’ (3)
• Esempio con più regole
exe: f.o r.o
gcc f.o r.o -o exe
Fra due regole
deve esserci almeno
una LINEA VUOTA
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
38
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
39
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
40
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
f.c
t.h
r.o
r.h
r.c
41
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.c
f.o
t.h
r.o
r.h
r.c
42
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
t2
il padre
f.o
gcc -Wall -pedantic -c f.c
t1
f.c
exe
t.h
r.o
r.h
r.c
43
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
gcc -Wall -pedantic -c f.c
exe
t2
f.o
t1
f.c
t.h
r.o
r.h
r.c
44
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)
45
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
f.o
f.c
t.h
Crea solo questo
sottoalbero
r.h
46
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
47
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 ?
48
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
49
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
50
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
51
Regole implicite … (4)
• Il makefile di percolation :
exe = percolation
prefix = percolation-sol
CC = gcc
CFLAGS = -Wall -pedantic
objects = dmat2.o $(prefix).o
$(exe): $(objects)
$(CC) $(CFLAGS) $(objects) -o $(exe)
$(prefix).o: dmat2.h
dmat2.o: dmat2.h
52
Phony targets ...
• È possibile specificare target che non sono
file e che hanno come scopo solo
l’esecuzione di una sequenza di azioni
clean:
rm $(exe) $(objects) *~ core
– siccome la regola non crea nessun file chiamato
‘clean’, il comando rm verrà eseguita ogni
volta che invoco
$make clean
• ‘clean’ è un target fittizio (phony) inserito
per provocare l’esecuzione del comando in
ogni caso
53
Phony targets … (2)
• Questo stile di programmazione è tipico ma
ha qualche controindicazione :
– se casualmente nella directory viene creato un
file chiamato ‘clean’ il gioco non funziona più
• siccome la dependency list è vuota è sempre
aggiornato!
– È inefficiente!
• Il make cerca prima in tutte le regole implicite per
cercare di risolvere una cosa che è messa apposta
per non essere risolta
54
Phony targets … (3)
• Soluzione :
– prendere l’abitudine di dichiarare
esplicitamente i target falsi
.PHONY : clean
clean:
-rm $(exe) $(objects) *~ core
‘-rm’ significa che l’esecuzione del make può
continuare anche in caso di errori nell’esecuzione
del comando rm (es. uno dei file specificati non c’è)
55
Documentazione su make
• Make può fare molte altre cose
• per una descrizione introduttiva Glass
– pp 329 e seguenti
• per una descrizione davvero dettagliata info
di emacs
– ESC-X info
– cercare (CTRL-S) "make"
56
Scarica

PPT