Dal codice sorgente all'eseguibile

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.