Dopo aver terminato di discutere quali applicazioni girano su macOS parliamo di librerie dinamiche: i file eseguibili sono raramente autonomi, eccezione fatta per alcuni eseguibili statici, la maggior parte dei programmi sono collegati dinamicamente a librerie preesistenti.
Caricamento in fase di avvio delle librerie
In macOS quasi tutti gli eseguibili sono collegati in maniera dinamica. Letteralmente significa che l’immagine di un file Mach-O è piena di “buchi” – riferimenti a librerie e simboli esterni – che vengono risolti automaticamente all’avvio del programma. Questo lavoro viene svolto dal linker dinamico e tutto il processo, viene generalmente indicato come binding.
Il linker assume il controllo del processo, in quanto il kernel svolge una funzione particolare, creando uno speciale punto di ingresso nel processo, dando la precedenza al linker di operare in libertà.
Il lavoro del linker, come abbiamo detto è letteralmente “riempire i buchi“: deve cercare qualsiasi dipendenza da simboli e librerie e risolverli, anche in modo ricorsivo, perchè molto spesso accade che le librerie abbiano dipendenze da altre librerie.
Come in tutti i sistemi Unix-based, le librerie Mach-O si possono facilmente trovare in /usr/lib (non esiste /lib in macOS o iOS). Tuttavia, ci sono varie differenze:
- Le librerie non sono “oggetti condivisi” (.so), poiché come abbiamo più volte detto, non è compatibile con ELF e questo concetto non esiste in Mach-O. Le librerie dinamiche hanno un’estensione .dylib.
- Non c’è libc. Gli sviluppatori hanno sicuramente familiarità con la libreria C Runtime sugli altri Unix. La libreria corrispondente, /usr/lib/libc.dylib, esiste solo come collegamento simbolico a libSystem.B.dylib. Questa libSystem fornisce funzionalità libc, oltre a funzioni aggiuntive, che nei sistemi Unix vengono fornite da librerie – ad esempio, funzioni matematiche (-lm), risoluzione hostname (-lnsl) e thread (-lpthread).
libSystem è il prerequisito fondamentale di tutti i binari su macOS: C, C ++, Objective-C, o altri. Questo perché molto semplicemente funge da interfaccia (una sorta di wrapper) per le chiamate di sistema di livello inferiore e i servizi del kernel, senza i quali non si farebbe nulla.
Shared Library Caches in iOS e macOS
Un altro meccanismo supportato da dyld è quello delle shared library cache. Queste librerie sono memorizzate, precollegate, in un file sul disco. Le cache condivise sono particolarmente importanti in iOS, dove le librerie più comuni sono memorizzate nella cache.
Se provate a cercare su iOS la maggior parte delle librerie, come ad esempio libSystem, non le troverete. Sebbene tutti i binari abbiano questa dipendenza, il file effettivo non è presente nel file system. Per risparmiare tempo sul caricamento delle librerie, il dyld di iOS impiega una cache preconnessa condivisa.
In macOS, le cache condivise di dyld sono nel path /private/var/db/dyld. Su iOS, la cache condivisa di trova in /System/Library/Caches/com.apple.dyld: è un file singolo, denominato dyld_shared_cache_armv7.
Le shared cache, sia su macOS che su iOS, possono diventare molto grandi. macOS contiene oltre 200 file mentre iOS ne contiene addirittura oltre 500 con una dimensione di circa 200 MB. La comunità dei jailbreakers è particolarmente interessata alla questione, al punto che sono stati scritti vari cache “unpackers” per estrarre le librerie e i framework.
Caricamento a runtime
Normalmente gli sviluppatori dichiarano librerie e simboli che useranno quando utilizzano i vari #include all’interno delle intestazioni e, facoltativamente, specificano librerie aggiuntive al linker da terminale. Un eseguibile costruito in questo modo non viene caricato fino al momento in cui tutte le dipendenze non saranno risolte, come visto prima.
Una valida alternativa, consiste nell’utilizzare le funzioni fornite in <dlfcn.h> per caricare le librerie proprio durante il runtime, cioè durante l’esecuzione del programma. Questa tecnica consente maggiore flessibilità ma il nome della libreria deve essere comunque conosciuto in fase di compilazione; in questo modo, lo sviluppatore può preparare diverse librerie e caricare quella più appropriato in base alle caratteristiche o ai requisiti durante il runtime.
Inoltre cosa molto importante, se il caricamento di una libreria fallisce viene restituito un codice di errore che può essere a questo punto gestito dal programma in esecuzione, a differenza del caricamento classico.
Curiosità
Come per tutti i programmi C standard, i file eseguibili in macOS hanno il punto di ingresso standard, di default denominato “main“. Oltre ai soliti tre canonici argomenti, tuttavia è presente un quarto argomento, un noto come “apple“.
L’argomento “apple“, fino a Snow Leopard incluso, conteneva solo una stringa che indicava il percorso completo del programma, il suo path. A partire da Lion, l’argomento “apple” è stato esteso, contenendo due nuovi parametri aggiuntivi: stack_guard e malloc_entropy.
Il primo è usato dalla funzione “stack protector” di GCC per prevenire i buffer overflow mentre il secondo viene usato dalla funzione malloc, che lo usa per aggiungere un po di casualità allo spazio degli indirizzi del processo (capiremo con il prossimo articolo il motivo per il quale è necessario aggiungere casualità allo spazio degli indirizzi).
Anche per questo articolo è tutto. Per eventuali domande, curiosità o feedback potete lasciare un commento qui in basso, a presto!