Statiche o condivise: che cosa cambia davvero
Per ottenere un eseguibile da un sorgente servono due passi: il compilatore trasforma il codice in object file, poi il linker li unisce e li collega alle librerie. Quel collegamento può avvenire in due modi, e la differenza è tutta qui.
- Libreria statica — il codice della libreria viene copiato dentro il programma al momento del link. Il programma non ha più dipendenze a runtime, ma è più pesante, e se la libreria viene corretta bisogna ricompilarlo. I file statici finiscono in
.a(per esempiolibpthread.a). - Libreria condivisa (o dinamica, o shared object) — il linker si limita a registrare che il programma fa riferimento a quella libreria. Il codice non viene copiato: deve essere presente all’esecuzione. In cambio i binari sono più piccoli e una sola copia della libreria resta in memoria anche se la usano dieci processi.
Sui sistemi Linux le librerie condivise ci sono sempre; quelle statiche possono benissimo mancare, perché servono solo a chi compila.
Leggere un nome: libc.so.6 non è un file, quasi mai
Il nome di una libreria condivisa, il soname, è fatto di tre pezzi: il nome (di solito con il prefisso lib), il suffisso so che sta per shared object, e il numero di versione. In libpthread.so.0 il nome è libpthread, il suffisso so, la versione 0. Saper spezzare un soname nei suoi tre pezzi è una richiesta esplicita dell’obiettivo, quindi allenatici: libsystemd.so.0, libdl.so.2, ld-linux-x86-64.so.2.
Quel nome generico è quasi sempre un collegamento simbolico al file vero, che porta la versione esatta:
$ ls -l /lib/x86_64-linux-gnu/libc.so.6
lrwxrwxrwx 1 root root 12 feb 6 22:17 /lib/x86_64-linux-gnu/libc.so.6 -> libc-2.24.so
Le posizioni tipiche sono /lib, /lib32, /lib64, /usr/lib e /usr/local/lib. Qui le famiglie divergono: Debian e Ubuntu usano il multiarch e mettono le librerie a 64 bit in /lib/x86_64-linux-gnu e /usr/lib/x86_64-linux-gnu, mentre Red Hat e Fedora restano su /lib64 e /usr/lib64. Su molte distribuzioni recenti, inoltre, /lib è a sua volta un collegamento a /usr/lib: se un percorso ti sembra doppio, di solito è questo.
ldd: chiedere a un binario di che cosa ha bisogno
ldd seguito dal percorso di un programma elenca le librerie condivise che gli servono, con il percorso risolto e l’indirizzo di memoria in cui verranno caricate:
$ ldd /usr/bin/git
linux-vdso.so.1 => (0x00007ffcbb310000)
libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f18241eb000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f18235c7000)
/lib64/ld-linux-x86-64.so.2 (0x00007f182445b000)
Tre righe da saper interpretare. linux-vdso.so.1 non ha un percorso perché non è un file su disco: la fornisce il kernel. ld-linux-x86-64.so.2 compare con il percorso assoluto ed è il dynamic linker in persona. E se al posto di un percorso leggi not found, hai trovato la dipendenza mancante: è così che si diagnostica un programma che si rifiuta di partire.
ldd funziona anche su una libreria, per vedere le dipendenze di quella. Con -u (o --unused) mostra le dipendenze dichiarate ma non usate, un residuo delle opzioni con cui il binario è stato collegato. Una nota da collega: ldd per risolvere i nomi può arrivare a eseguire il programma, quindi su un binario di provenienza dubbia è più prudente objdump -p FILE | grep NEEDED, che si limita a leggere gli header.
Dove cerca il dynamic linker, e come glielo si dice
A risolvere i riferimenti quando il programma parte è il dynamic linker ld.so (o ld-linux.so), che cerca in un elenco di directory: la library path. La si configura in /etc.
Il file /etc/ld.so.conf oggi contiene quasi sempre una sola riga, include /etc/ld.so.conf.d/*.conf, e il lavoro vero lo fanno i file dentro /etc/ld.so.conf.d/, che elencano percorsi assoluti, uno per riga. Aggiungere una directory di librerie significa quindi creare lì un file .conf con dentro il percorso.
Non basta: il linker a runtime non legge quei file, legge la cache binaria /etc/ld.so.cache. A rigenerarla è ldconfig, che va lanciato ogni volta che aggiungi o modifichi un file di configurazione, e che richiede i privilegi di root perché scrive in /etc/ld.so.cache. Le due opzioni da ricordare sono -v (--verbose), che mostra directory, versioni e collegamenti creati, e -p (--print-cache), che stampa il contenuto della cache — utile per verificare in un colpo solo se una libreria è davvero visibile al sistema.
LD_LIBRARY_PATH: la scorciatoia, e i suoi limiti
La variabile d’ambiente LD_LIBRARY_PATH aggiunge percorsi di ricerca temporaneamente, senza toccare /etc e senza ldconfig. È un elenco di directory separate dai due punti, esattamente come PATH lo è per gli eseguibili — è l’analogia che l’esame usa.
$ export LD_LIBRARY_PATH=/usr/local/mylib
$ echo $LD_LIBRARY_PATH
/usr/local/mylib
$ unset LD_LIBRARY_PATH
Senza export il valore resta nella shell corrente e non arriva ai processi figli; per renderlo permanente si scrive la riga in uno script di inizializzazione di Bash come ~/.bashrc o /etc/bash.bashrc. Usala per provare una libreria nuova o per far girare un software che si porta dietro le proprie librerie, non come configurazione stabile: la via pulita resta un file in /etc/ld.so.conf.d/ seguito da ldconfig.