[ PR0GRAMMAZi0NE UNiX: rIPASSO C ] – [ PARTE III : GCC Compilation step ]


----------------------[ PR0GRAMMAZi0NE UNiX: rIPASSO C ]----------------------
---------------------[ PARTE III : GCC Compilation step ]---------------------
--------------------------------[ abnm3nt4l1 ]--------------------------------

Indice

00. Introduzione
01. Consumo
03. Basi del processo di compilazione
04. Preprocessing, compilazione, assembly, Linking
05. Qualche esempio

00. iNtrODUZIONE
------------

Nel precedente post abbiamo trattato in maniera sommaria il "three-stage" process
di creazione di un programma binario eseguibile a partire dal suo codice sorgente
in C.
Di questi tre passi, il secondo, quello di compilazione del codice sorgente
che ha come risultato la produzione di un file binario verra' descritto nel
seguito del presente articolo in relazione a cio' che viene effettuato dal
compilatore GCC dietro le quinte.

02. cONSUMO
-------

1) ll contenuto di una caffettiera da 3 di caffe' 100% arabica tostatura forte
2) Una tazza di te' verde
3) ... acqua! (ragazzi sono appena trascorti 3 intensi giorni di mangiate
e bevute a base alcolica)

03. Basi del processo di compilazione
---------------------------------

In principio fu:

$ gcc -o salutomondo salutomondo.c

$ ./salutomondo
Hello, World!

Il sorgente e' il medesimo mostrato nel primo post della serie.

Ad ogni modo abbiamo un banalissimo programma scritto in C che stampa
sul vostro terminale la stringa "Hello, World!" seguita dalla sequenza
di escape \n.

Il codice sorgente viene compilato per mezzo di gcc e prodotto il file
eseguibile di output (-o) salutomondo.

Nel seguito diamo un'occhiata a cio' che esegue gcc (talvolta avvalendosi
di altri strumenti) per generare il file eseguibile salutomondo a partire
dal file sorgente salutomondo.c.

A tal riguardo aggiungiamo un'opzione alla nostra linea di commando per
istruire il nostro compilatore affinche' salvi i file temporanei che genera
nel corso della compilazione di un sorgente C, --save-temps.

$ gcc -save-temps -o salutomondo salutomondo.c

$ ls salutomondo*
salutomondo salutomondo.c salutomondo.i salutomondo.o salutomondo.s

A differenza dall'esecuzione del primo comando, ora nella directory nella quale abbiamo
eseguito la compilazione oltra al file sorgente salutomondo.c ed eseguibile salutomondo
troviamo altri 3 file con estensione .i, .o e .s.
Prima di procedere con la verifica del contenuto dei singoli file riporto il contenuto
della man page di gcc per comprendere cosa effettivamente gcc quando invocato Con
l'opzione -save-temps.

-save-temps
-save-temps=cwd
Store the usual "temporary" intermediate files permanently; place them in
the current directory and name them based on the source file.
Thus, compiling foo.c with -c -save-temps produces files foo.i and foo.s,
as well as foo.o. This creates a preprocessed foo.i output file even
though the compiler now normally uses an integrated preprocessor.

Quindi in poche parole non facciamo altro che indicare al compilatore di salvare
tutti i file temporanei che genera in ciascuno dei 4 passi che compie nel corso
del processo di compilazione e che elimina quando giunge al termine dello stesso.

04. Preprocessing, compilazione, assembly, Linking

Gli step che il compilatore compie sono:

1. Preprocessing;
2. Compilazione;
3. Assembly;
4. Linking.

1. Preprocessing

In questa fase tutti gli header file che si sono inclusi e utilizzati nel proprio
programma vengono espansi e inclusi nella posizione in cui compare la direttiva
#include relativa all'header stesso.
Oltre questo e' nel corso di questa fase che i valori di costanti e macro vengono
sostituiti e i commenti rimossi.

Il file intermedio generato dalla fase di preprocessing e' quello con estensione .i.

2. Fase di compilazione

In questa fase il compilatore compila il sorgente producendo il codice assembly.
Il codice assembly consta di un insieme di istruzioni che determinano cio' che
il programma fa. Ciascuna piattaforma ha un specifico insieme di istruzioni
assembly che vengono direttamente mappate in istruzioni relative processore.

Vediamo il contenuto del file salutomondo.s

.file "salutomondo.c"
.section .rodata
.LC0:
.string "Hello, World!"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: what fuck?"
.section .note.GNU-stack,"",@progbits

3. Assemblaggio (assembly step)

A questo punto, un compilatore particolare, l'assemblatore converte le istruzioni
assembly generate nella fase di compilazione e le converte nelle corrispondenti
istruzioni macchina o meglio un bit stream che consta di 0 e 1. Questo codice,
direttamente comprensibile al processore, viene salvato nel file oggetto salutomondo.o.
Questo file e' costituito da differenti sezioni che il processore utilizza durante
l'esecuzione del programma.

Il contenuto di questo file non risulta leggibile e interpretabile all'uomo per mezzo
di strumenti quali editor a eccezione di pochi simboli (come printf, main o stringhe).

4. Linking

Eccoci all'ultimo step del processo di compilazione.
Il file oggetto contiene il codice macchina prodotto dall'assemblatore alla fase
precedente; il linker si preoccupa di garantire che tutti i simboli non
definiti nel sorgente perche' inclusi da librerie esterne vengano risolti
unendo tra loro i diversi file oggetto e creando un unico file eseguibile.

I 4 step possono essere eseguito singolarmente utilizzando gcc con le opzioni
-E, -C e -S e il linker ld separatamente. Vedremo nel seguito degli esempi.

05. Qualche esempio
---------------

-- Creiamo l'eseguibile salutomondo con un solo comando preservando i file
temporanei generati dagli step intermedi

$ gcc -save-temps salutomondo.c -o salutomondo

-- Creiamo un eseguibile eseguendo manualmente le singole fasi che dal sorgente
conducono alla creazione dell'eseguibile

a. preprocessore

$ cpp salutomondo.c > salutomondo.i

oppure

$ gcc -E salutomondo.c -o salutomondo.i

b. compilazione

$ gcc -S salutomondo.i -o salutomondo.s

c. assemblaggio

$ as salutomondo.s -o salutomondo.o

d. linking

$ ld -o salutomondo -dynamic-linker /lib64/ld-linux-x86-64.so.2 \
/usr/lib64/crt1.o /usr/lib64/crti.o salutomondo.o -lc /usr/lib64/crtn.o

Provate e divertitevi!

— abnm3nt4l1st