I
software, sappiamo tutti, fanno uso di librerie. Queste sono collezioni
di funzioni che vengono utilizzate dai vari binari che compongono una
applicazione.
Ogni libreria è composta da due elementi
- la parte binaria (i file .so presenti in /usr/lib)
- l'header (i file .h presenti in /usr/include)
L'header
è usato a livello di codice sorgente tramite la direttiva #include e
contiene al suo interno tutte le macro ed i prototipi di funzione,
ovvero la dichiarazione di una funzione con i vari parametri che essa
chiede.
Tuttavia bada bene: l'header NON contiene nessuna funzione, ma solo dichiarazioni.
Ciò
serve al preprocessore (cpp) affinché, quando vada a processare il
sorgente per poi passare la palla al compilatore, sappia che la funzione
int pippo(int a, int b)
non sia una roba uscita a caso, ma una roba
definita in un contesto specifico, ovvero l'header a cui facciamo
riferimento.
E come fa a sapere dove si trova l'header in questione?
Dalle variabili CFLAGS e CXXFLAGS (la prima per il compilatore C e la
seconda per quello C++) che contengono svariate direttive
"-I/path/libreria", spesso ottenute in fase di configure
tramite l'uso
di pkg-config
.
Una volta che l'header è stato trovato e quindi le
funzioni e le macro sono state definite correttamente, il compilatore
può provvedere a creare il codice oggetto (.o) di ogni file del
sorgente.
Tuttavia il codice oggetto non è eseguibile. Per poterlo
diventare occorre far sì che quelle funzioni definite nell'header
vengano messe a disposizione a livello binario.
Mi spiego meglio:
l'header pippo.h
che abbiamo messo nel codice sorgente contiene solo una
lista di funzioni. Ma non ne contiene la logica. Ecco perché si
chiamano prototipi di funzione.
Immagina un registro scolastico con la lista degli alunni. Ecco è una cosa del genere.
In sostanza tu avrai definite cose come:
int somma(int a, int b);
che
vanno benissimo al preprocessore in quanto lui ha solo bisogno di
sapere se somma()
sia una funzione esistente, tuttavia non vanno bene al
software perché questo ha bisogno della logica, ovvero di ciò che
succede in somma()
.
Ed ecco che interviene il linker (GNU ld nel
nostro caso) che va quindi a legare il codice oggetto al binario
contenente la funzione definita nell'header.
Come fa a sapere dove
sta? Tramite la variabile d'ambiente LDFLAGS che ha parametri tipo
"-L/path/libreria -lpippo"; anche in questo caso molto spesso LDFLAGS
viene "riempita" da pkg-config.
Ora che differenza c'è tra il linking statico e quello dinamico?
Il
linking dinamico scrive nel codice oggetto che sta per diventare
eseguibile un riferimento alla libreria che contiene la funzione. Ovvero
gli dice: "ciccio, la funzione somma la devi cercare in libpippo.so
",
che diventa quella che tutti noi conosciamo come DIPENDENZA e che è
visibile tramite l'arcinoto comando ldd.
Nel linking statico le funzioni presenti in libpippo vengono integrate direttamente nel codice oggetto che diventa binario eseguibile rendendolo indipendente dalle dipendenze. Le librerie statiche infatti sono dei meri archivi AR, che riconosci in quanto hanno l'estensione .a. Esse sono persino esplorabili tramite il comando ar e contengono tutti i codici oggetto che compongono la libreria.
Erroneamente si pensa che il linking statico sia l'integrare tutta la libreria in un software, ma non è così.
Solo il codice oggetto che contiene la funzione desiderata viene integrato, non il resto.
Il
vantaggio è che tutti i binari sono indipendenti e sono CROSS-DISTRO.
Una applicazione compilata e linkata staticamente su Ubuntu 10.04 32-bit
può funzionare su tutte le distro Linux anche a 64-bit prive di
multilib senza bisogno di nient'altro.
Non si avranno MAI problemi di runtime, come dipendenze non soddisfatte o incompatibilità binarie.
Lo svantaggio è che il software è indipendente, e quindi in caso di vulnerabilità in una libreria usata va ricompilato.
Il
vantaggio del linking dinamico non esiste. Si dice che occupi meno
spazio su disco ma in realtà non è vero, perché il problema dello spazio
eccessivo richiesto dai software statici è in realtà imputabile a GLIBC
che è volutamente incompatibile con questo linking.
Di contro però
introduce ENORMI falle di sicurezza: LD_LIBRARY_PATH ed LD_PRELOAD
permettono di caricare al volo librerie e funzioni in memoria, andando
incontro a iniezioni di codice malevolo che va a sostituire quello
buono.
Kensan.it
Per quanto riguarda un recente problema di sicurezza dovuto al linking dinamico si veda questo articolo in inglese di una società di sicurezza. Trend Micro parla di un rootkit ring 3 di nome Umbreon ovvero di un rootkit (malware con ampie capacità stealth e comandabile da remoto) che agisce al livello utente e non necessita dei diritti di root. In pratica se installato come un normale programma dall'utente diventa un malware non rilevabile. L'unica possibilità per riconoscerlo è agire tramite una live CD oppure con opportuni comandi tipols
che facciano uso di sole librerie kernel mode ovvero di livello 0. Un ls
che sia compilato con linking statico non sarebbe stato taroccato da questi rootkit ring 3.Il rootkit di nome Umbreon sfrutta proprio le enormi falle di sicurezza dovute al linking dinamico. Anziché sfruttare le due variabili d'ambiente a runtime (LD_LIBRARY_PATH ed LD_PRELOAD), va direttamente a modificare il loader e la libc, andando così a corrompere in maniera perenne il sistema. Con un modello statico non ci sarebbero MAI stati danni di questo tipo, perché ld-linux.so non viene affatto usato e di conseguenza ogni software sarebbe stato al sicuro.
Il linking statico è DA SEMPRE mal visto dalla FSF in quanto viola palesemente la clausola di mixing di codice con differente licenza.
Come già detto quando compili staticamente un software non fai altro che unire il codice oggetto della libreria a quello del software creando un binario eseguibile completo. Ma questo vuol dire che se il codice della libreria è sotto licenza incompatibile con la LGPL tu stai creando un binario ILLEGALE in quanto girano nello stesso spazio di allocazione parti di codice non combinabili.
Diverso è con il linking dinamico che invece permette la cosa in quanto la libreria è vista come una sorta di plug-in. Tant'è che l'unica soluzione è o distribuire tutto sotto LGPL (anche il tuo software proprietario) oppure permettere agli utenti di re-linkare il software sotto differente libreria, ovvero linkarla dinamicamente.
ATTENZIONE: sto parlando di LGPL. Con la GPL non potresti nemmeno fare linking dinamico, perché non siamo in presenza di un fork.
Ma
la FSF non si è limitata a parlare male del linking statico ma ha
cercato di impedirlo. Come? Rendendo bloat la GLIBC, aggiungendoci
simboli inutili, in modo che un software linkato staticamente sia
esageratamente voluminoso anche se contiene un semplice "Hello World".
In
aggiunta a questo, GLIBC è piena zeppa di estensioni non standard al
linguaggio, rendendo molti software incompatibili con altri OS o libc, a
meno di patch custom.
Per questo ho spesso detto che GLIBC è spazzatura e GNU è un cancro.
E le critiche a GLIBC sono arrivate da più parti, tipo da Torvalds.
Naturalmente
per giustificare la cosa, per anni Ulrich Drepper (ex-sviluppatore Red
Hat e sviluppatore di GLIBC) andò a dire in giro le famose stupidaggini
relative alla migliore sicurezza del linking dinamico derivata dal fatto
che si patcha una volta e tutti i software sono al sicuro.
Ed è per
questo che oggi su Linux ci ritroviamo ancora le dipendenze a ragnatela
con tutti i problemi che ne derivano: repository di terze parti che
incasinano il sistema, applicazioni vecchie, impossibilità di avere una
versione più recente di un software senza aggiornare la distro ecc.
Non
possiamo tecnicamente uscire da questa spirale finché qualcuno non avrà
le palle (permettimi il termine) di mandare GNU a quel paese e rendere
Linux un SISTEMA OPERATIVO completo.
E intanto Mac OS X da 25 anni
(se contiamo il suo antenato NeXTSTEP) impone il linking statico salvo
rare e limitate eccezioni nei bundle senza problemi di spazio su disco o
delle minchiate di sicurezza paventate da Drepper sviluppatore delle GLIBC.
Anonimo il 10 febbraio 2021 con il titolo: Commento veloce computer.
NON
e' sempre meglio. Dipende dall'utilizzo. Il linking dinamico puo' portare ad errori di librerie mancanti. Il linking statico d'altro canto risulta in eseguibili piu' pesanti in termini di spazio di archiviazione.