Dal programma C all'eseguibile ARM
In questa pagina 6
In questa pagina 3
La catena di traduzione
| Passo | Ingresso → uscita | Cosa fa |
|---|---|---|
| preprocessore | .c → .i |
espande #include e #define |
| compilatore | .c → .s |
traduce in assembly; alloca i registri, ottimizza |
| assemblatore | .s → .o |
traduce le istruzioni in codice macchina; espande le pseudo-istruzioni (LDR r0, =x) |
| linker | più .o + librerie → eseguibile |
unisce i file, risolve i riferimenti tra file, assegna gli indirizzi definitivi |
| loader (sistema operativo) | eseguibile → processo | carica in memoria, prepara stack e argomenti, salta all'inizio |
Con GCC per ARM: arm-linux-gnueabihf-gcc -S prog.c produce l'assembly, -c l'oggetto, senza opzioni l'eseguibile.
File oggetto
Contiene: intestazione, segmento testo (codice macchina), segmento dati (variabili globali inizializzate), informazioni di rilocazione (istruzioni e dati che dipendono da indirizzi assoluti), tabella dei simboli (etichette definite ed esterne), informazioni di debug.
L'assemblatore lavora di solito in due passate: nella prima assegna un indirizzo a ogni etichetta (così i salti in avanti si possono risolvere), nella seconda genera il codice.
Linker
- Mette uno dopo l'altro i segmenti testo e dati di tutti i file.
- Risolve i simboli esterni: una
BL printfinmain.oviene collegata alla definizione diprintfnella libreria. - Corregge gli indirizzi segnati come rilocabili.
Collegamento statico: le librerie vengono copiate nell'eseguibile. Dinamico: l'eseguibile contiene solo un riferimento; la libreria condivisa (.so, .dll) viene caricata all'esecuzione e condivisa tra i processi.
Memoria di un processo
Dagli indirizzi bassi agli alti: testo (codice, sola lettura), dati statici (globali e static), heap (allocazione dinamica con malloc, cresce verso l'alto), spazio libero, stack (record di attivazione, cresce verso il basso, vedi Stack e chiamata di funzioni in ARMChiamata con BL e ritorno con BX LR; convenzione di chiamata AAPCS (argomenti in r0-r3, risultato in r0, registri da preservare r4-r11); stack discendente pieno con PUSH e POP; record di attivazione; funzioni foglia e non foglia; esempio ricorsivo del fattoriale con evoluzione dello stack.Stack e chiamata di funzioni in ARM →).
Assembly e C insieme
Una funzione assembly è chiamabile da C se rispetta la convenzione di chiamata (argomenti in r0–r3, risultato in r0, r4–r11 preservati).
max.s:
.text
.global massimo @ visibile al linker
massimo: @ int massimo(int a, int b)
CMP r0, r1
MOVLT r0, r1
BX lrmain.c:
#include <stdio.h>
int massimo(int a, int b); // dichiarazione: definita in max.s
int main(void) {
printf("%d\n", massimo(3, 8)); // stampa 8
return 0;
}Compilazione: gcc main.c max.s -o prog. Al contrario, dall'assembly si chiamano funzioni C (BL printf) mettendo gli argomenti in r0–r3 e salvando prima ciò che serve dopo la chiamata.
Leggere l'assembly prodotto dal compilatore (gcc -O1 -S) è il modo più rapido per vedere come vengono tradotti cicli, array e chiamate (vedi Strutture di controllo in assembly ARMSalti B e condizionati, codici di condizione con e senza segno; traduzione di if, if-else, while, for e do-while da C ad ARM; esecuzione condizionata per eliminare salti brevi; switch con tabella di salto.Strutture di controllo in assembly ARM → e Array e strutture dati in assembly ARMArray in memoria e calcolo dell'indirizzo di un elemento; scorrimento con indice o con puntatore; esempi svolti (somma, massimo, copia); stringhe terminate da zero e calcolo della lunghezza; matrici per righe; struct con offset dei campi e allineamento.Array e strutture dati in assembly ARM →).
Errori tipici
- Dimenticare
.global: il linker dà "riferimento non definito". - Confondere compilatore e assemblatore: il secondo traduce assembly in codice macchina quasi uno a uno, il primo traduce un linguaggio ad alto livello.
Versione ripasso
Catena di traduzione
- Preprocessore (
.c.i):#include,#define. - Compilatore (
.c.s): assembly, registri, ottimizzazione. - Assemblatore (
.s.o): codice macchina e pseudo-istruzioni (LDR r0, =x); due passate (etichette, poi codice). - Linker: unisce i segmenti, risolve i simboli esterni (
BL printf), corregge gli indirizzi rilocabili. - Loader: carica il processo.
Il file oggetto contiene testo, dati, rilocazione, tabella dei simboli. Collegamento statico (librerie copiate) o dinamico (.so, .dll caricate all'esecuzione).
Memoria di un processo
Indirizzi bassi alti: testo, dati statici, heap (cresce verso l'alto), stack (verso il basso, Stack e chiamata di funzioni in ARMChiamata con BL e ritorno con BX LR; convenzione di chiamata AAPCS (argomenti in r0-r3, risultato in r0, registri da preservare r4-r11); stack discendente pieno con PUSH e POP; record di attivazione; funzioni foglia e non foglia; esempio ricorsivo del fattoriale con evoluzione dello stack.Stack e chiamata di funzioni in ARM →).
Assembly e C
Convenzione: argomenti in r0-r3, risultato in r0, r4-r11 preservati. massimo con .global massimo, CMP r0, r1, MOVLT r0, r1, BX lr, dichiarata in C int massimo(int a, int b); (massimo(3, 8) dà 8); gcc main.c max.s -o prog. gcc -O1 -S mostra le traduzioni di cicli e array (Strutture di controllo in assembly ARMSalti B e condizionati, codici di condizione con e senza segno; traduzione di if, if-else, while, for e do-while da C ad ARM; esecuzione condizionata per eliminare salti brevi; switch con tabella di salto.Strutture di controllo in assembly ARM →, Array e strutture dati in assembly ARMArray in memoria e calcolo dell'indirizzo di un elemento; scorrimento con indice o con puntatore; esempi svolti (somma, massimo, copia); stringhe terminate da zero e calcolo della lunghezza; matrici per righe; struct con offset dei campi e allineamento.Array e strutture dati in assembly ARM →).
Errori tipici: dimenticare .global (riferimento non definito); confondere compilatore e assemblatore.