Note tecniche sulla Toolchain

Questa sezione prova a spiegare alcuni dei dettagli tecnici e logici dietro al metodo di costruzione complessivo. Non è importante che qui comprendiate ogni cosa immediatamente. Molto di questo avrà senso quando avrete realizzato una compilazione. Sentitevi liberi di tornare a questo capitolo in ogni momento.

Lo scopo del Capitolo 5 è di fornire un ambiente provvisorio sano in cui possiamo accedere con chroot e dal quale possiamo produrre una costruzione pulita e priva di problemi del sistema LFS obiettivo del Capitolo 6. Lungo la strada, tenteremo di separarci il più possibile dal sistema host, e nel farlo costruiremo una toolchain auto-contenuta che si auto-sostiene. Bisogna notare che il processo di costruzione è stato pensato per minimizzare il rischio per i lettori nuovi e fornire un minimo valore didattico nello stesso tempo. In altre parole, potrebbero essere utilizzate tecniche più avanzate per costruire il sistema.

[Important]

Importante

Prima di continuare dovete essere certi del nome della vostra piattaforma, a cui spesso si fa riferimento anche come target triplet. Per molta gente il "target triplet" probabilmente sarà i686-pc-linux-gnu . Un modo semplice per determinare la vostra piattaforma è quello di avviare lo script config.guess che è presente nei sorgenti di molti pacchetti. Scompattate i sorgenti delle Binutils e avviate: ./config.guess e annotatevi l'output.

Dovrete anche essere a conoscenza del nome del linker dinamico della vostra piattaforma, al quale si fa spesso riferimento anche come dynamic loader, da non confondere con il linker standard ld che è parte delle Binutils. Il linker dinamico è fornito da Glibc e ha il compito di trovare e caricare le librerie condivise necessarie ad un programma, preparare il programma a funzionare e quindi avviarlo. Per molta gente il nome del linker dinamico sarà ld-linux.so.2. Su piattaforme che siano meno recenti il nome potrebbe essere ld.so.1 e le nuove piattaforme a 64 bit potrebbero averne uno completamente diverso. Dovete essere in grado di determinare il nome del linker dinamico della vostra piattaforma guardando nella directory /lib sul vostro sistema host. Un sistema a colpo sicuro è di ispezionare un binario a caso del vostro sistema host avviando readelf -l <name of binary> | grep interpreter e annotando l'output. Il riferimento autoritativo che copre tutte le piattaforme è nel file shlib-versions nella root dell'albero dei sorgenti di Glibc.

Alcuni punti tecnici su come funziona il metodo di costruzione del Capitolo 5:

Le binutils sono installate per prime perché il file ./configure sia di GCC che di Glibc effettua vari tipi di test sull'assemblatore e il linker per determinare quali caratteristiche del software abilitare e disabilitare. Questo è molto più importante di quanto uno possa pensare inizialmente. Una configurazione scorretta di GCC o Glibc può risultare in una toolchain corrotta, dove l'impatto di una tale corruzione potrebbe non evidenziarsi fin quasi al termine della costruzione dell'intera distribuzione. Grazie al cielo, un fallimento di una suite di test normalmente ci avvisa prima di aver perso troppo tempo.

Le Binutils installano il loro assemblatore e linker in due locazioni, /tools/bin e /tools/$TARGET_TRIPLET/bin. In realtà, gli strumenti in una locazione sono collegati all'altra. Un importante aspetto del linker è il suo ordine di ricerca delle librerie. Informazioni dettagliate possono essere ottenute con ld passandogli il flag --verbose. Ad esempio: ld --verbose | grep SEARCH mostrerà i percorsi di ricerca attuali e il loro ordine. Potete vedere quali file sono linkati da ld compilando un falso programma e passando lo switch --verbose al linker. Per esempio: gcc dummy.c -Wl,--verbose 2>&1 | grep succeeded vi mostrerà tutti i file aperti con successo durante il linking.

Il prossimo pacchetto da installare è GCC e durante il funzionamento del suo ./configure vedrete, ad esempio:

checking what assembler to use... /tools/i686-pc-linux-gnu/bin/as
checking what linker to use... /tools/i686-pc-linux-gnu/bin/ld

Questo è importante per le ragioni sopra menzionate. Dimostra anche che lo script di configurazione di GCC non cerca le directory del PATH per trovare quali strumenti usare. Tuttavia, durante le operazioni effettive dello stesso gcc, non sono necessariamente utilizzati gli stessi percorsi di ricerca. Potete trovare quale linker standard usa gcc avviando: gcc -print-prog-name=ld. Informazioni dettagliate possono essere ottenute da gcc passandogli il flag -v mentre compiliamo un finto programma. Ad esempio: gcc -v dummy.c mostrerà informazioni dettagliate riguardo gli stadi di preprocessamento, compilazione e assemblaggio, inclusi i percorsi di ricerca di gcc e il loro ordine.

Il pacchetto installato successivamente è Glibc. Le considerazioni più importanti per costruire Glibc sono il compilatore, i tool binari e gli header del kernel. Il compilatore normalmente non è un problema perché Glibc userà sempre il gcc trovato in una directory del PATH. I tool binari e gli header del kernel possono essere più problematici. Quindi non corriamo rischi e utilizziamo gli switch di configurazione disponibili per forzare le selezioni corrette. Dopo aver avviato ./configure potete controllare i contenuti del file config.make nella directory glibc-build per tutti i dettagli importanti. Noterete alcune cose interessanti come l'uso di CC="gcc -B/tools/bin/" per controllare quali tool binari sono usati, e anche l'uso dei flag -nostdinc e -isystem per controllare il percorso di ricerca dell'include del compilatore. Questi aspetti aiutano a evidenziare un importante aspetto del pacchetto Glibc: è estremamente autosufficiente durante la compilazione e in genere non si lega ai default della toolchain.

Dopo l'installazione di Glibc, facciamo un po' di aggiustamenti per assicurarci che ricerca e linking avvengano solo all'interno della nostra directory /tools. Installiamo un ld aggiustato, che ha un percorso di ricerca fisso limitato a /tools/lib. Quindi modifichiamo il file specs di gcc per puntare al nostro nuovo linker dinamico in /tools/lib. Questo ultimo passo è vitale per l'intero procedimento. Come sopra menzionato, un percorso fisso verso un linker dinamico è incluso in ogni eseguibile ELF condiviso. Potete verificare questo avviando readelf -l <name of binary> | grep interpreter. Modificando il file specs di gcc, assicuriamo che ogni programma compilato da qui alla fine di questo capitolo userà il nostro nuovo linker dinamico in /tools/lib.

La necessità di usare il nostro nuovo linker dinamico è anche la ragione per cui noi applichiamo la patch Specs per il secondo passo di GCC. Errori nel fare questo avranno come risultato che gli stessi programmi di GCC avranno incluso il nome della directory del linker dinamico del sistema host /lib, che vanificherebbe il nostro obiettivo di allontanarci dall'host.

Durante il secondo passo di Binutils, siamo in grado di utilizzare lo switch --with-lib-path per controllare il percorso di ricerca della libreria ld. Da questo punto in poi, la toolchain è auto-contenuta e autosufficiente. I rimanenti pacchetti del Capitolo 5 si costruiranno tutti sulla nuova Glibc in /tools.

Una volta entrati nell'ambiente chroot nel Capitolo 6, il primo grosso pacchetto che installiamo è Glibc, per via della sua natura auto-sufficiente cui abbiamo accennato prima. Una volta installato Glibc in /usr, realizziamo un rapido cambio delle impostazioni predefinite della toolchain, e quindi procediamo alla vera costruzione del resto del nostro sistema LFS.

Note sui collegamenti statici

Molti programmi devono fare, oltre al loro compito specifico, molte operazioni piuttosto comuni e talvolta complesse. Tra queste ci sono allocazione di memoria, ricerca nelle directory, lettura e scrittura file, manipolazione di stringhe, confronti, aritmetica e molte altre operazioni. Invece di obbligare ogni programma a reinventare la ruota, il sistema GNU fornisce tutte queste funzioni di base in librerie pronte all'uso. La più grossa ed importante libreria in ogni sistema Linux è Glibc.

Ci sono due principali modi di collegare le funzioni da una libreria a un programma che la usa: staticamente o dinamicamente. Quando un programma è collegato staticamente, il codice della funzione utilizzata è incluso nell'eseguibile, e il risultato è un programma piuttosto gonfiato. Quando un programma è collegato dinamicamente, vi è incluso un riferimento al collegamento dinamico, il nome della libreria, e il nome della funzione, e il risultato è un programma più snello. (Un terzo modo è di usare l'interfaccia di programmazione del linker dinamico. Vedete la pagina man di dlopen per ulteriori informazioni).

Il collegamento dinamico è il default in Linux, ed ha tre grandi vantaggi rispetto a quello statico. Primo, avete bisogno di una sola copia del codice eseguibile della libreria sul vostro disco rigido, invece di avere più copie dello stesso codice incluse in un intero insieme di programmi, il che salva spazio sul disco. Secondo, quando più programmi usano la stessa funzione della libreria nello stesso momento, è necessaria solo una copia del codice della funzione in memoria, il che salva spazio in memoria. Terzo, quando viene fissato un bug in una funzione della libreria o questa è comunque migliorata, dovete ricompilare solamente questa libreria, invece di dover ricompilare tutti i programmi che fanno uso della funzione migliorata.

Se il collegamento dinamico ha diversi vantaggi, perché colleghiamo staticamente i primi due pacchetti in questo capitolo? Le ragioni sono tre: storica, didattica e tecnica. Storica, perché le prime versioni di LFS collegavano staticamente ogni programma in questo capitolo. Didattica, perché sapere la differenza è utile. Tecnica, perché così facendo guadagnamo un elemento di indipendenza dal sistema host. Tuttavia, è bene sapere che LFS può essere costruita con successo anche con i primi due pacchetti collegati dinamicamente.