Salta al contenuto
Note per Studenti Dal programma C all'eseguibile ARM

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

  1. Mette uno dopo l'altro i segmenti testo e dati di tutti i file.
  2. Risolve i simboli esterni: una BL printf in main.o viene collegata alla definizione di printf nella libreria.
  3. 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:

armasm
        .text
        .global massimo         @ visibile al linker
massimo:                        @ int massimo(int a, int b)
        CMP   r0, r1
        MOVLT r0, r1
        BX    lr

main.c:

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

  1. Preprocessore (.c →\to .i): #include, #define.
  2. Compilatore (.c →\to .s): assembly, registri, ottimizzazione.
  3. Assemblatore (.s →\to .o): codice macchina e pseudo-istruzioni (LDR r0, =x); due passate (etichette, poi codice).
  4. Linker: unisce i segmenti, risolve i simboli esterni (BL printf), corregge gli indirizzi rilocabili.
  5. 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 →\to 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.

Teoria collegata