Dal codice sorgente all’eseguibile #
In questi articoli useremo principalmente il linguaggio di programmazione C. Per compilare un programma in C ci serviremo del compilatore gcc, facente parte della GNU Compiler Collection.
Compilare un programma in C consiste di queste fasi:
- preprocessing
- compilazione
- assembly
- linking
Il preprocessing #
In questa fase le direttive al precompilatore vengono interpretate. Questi comandi si notano
perché iniziano con il carattere #.
Prendiamo ad esempio il seguente codice C
#define ARR_LEN 16
int main() {
int arr[ARR_LEN];
return 0;
}
Dopo aver eseguito il preprocessore C con cpp -P main.c/gcc -E -P main.c, otteniamo:
int main() {
int arr[16];
return 0;
}
La compilazione #
La seconda fase consiste nel tradurre il codice pre-processato in istruzioni assembly.
Prendiamo il codice uscito dalla fase precedente
int main() {
int arr[16];
return 0;
}
Dopo la fase di compilazione, viene tradotto in questo codice assembly (gcc -S main.c):
.file "main.c"
.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
subq $80, %rsp
movq %fs:40, %rax
movq %rax, -8(%rbp)
xorl %eax, %eax
movl $0, %eax
movq -8(%rbp), %rdx
subq %fs:40, %rdx
je .L3
call __stack_chk_fail@PLT
.L3:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 11.2.0"
.section .note.GNU-stack,"",@progbits
L’assembly #
Nella terza fase le istruzioni macchina vengono tradotte in codice macchina (object code).
Con gcc -c main.c possiamo generare un file oggetto (main.o) a partire dalle
istruzioni macchina che abbiamo generato prima.
Il linking #
L’ultima fase prevede che più file oggetto generati vengano combinati in un singolo eseguibile. Nel file generato vengono aggiunti alcuni riferimenti (i cosiddetti link) alle librerie utilizzate.
Vi sono due modalità principali che possono essere usate nella fase di link.
Il linking statico prevede che i binari siano indipendenti da librerie esterne (self-contained).
Il linking dinamico prevede che il binario faccia affidamento alle librerie di sistema, che vengono caricate quando necessario. In questo caso, serviranno dei meccanismi per ricollocare (relocation) il codice in maniera dinamica.