16

Analisi this question Ho scoperto alcune cose sul comportamento della risoluzione dei simboli deboli nel contesto del caricamento dinamico (dlopen) su Linux. Ora sto cercando le specifiche che governano questo.Caricamento dinamico e risoluzione dei simboli deboli

Prendiamo il an example. Supponiamo che esista un programma a che carica dinamicamente le librerie b.so e c.so, in questo ordine. Se c.so dipende da altri due librerie foo.so (effettivamente libgcc.so in quell'esempio) e bar.so (effettivamente libpthread.so), poi di solito simboli esportati da bar.so può essere utilizzato per soddisfare legami deboli simbolo foo.so. Ma se b.so dipende anche da foo.so ma non da bar.so, questi simboli deboli apparentemente non saranno collegati allo bar.so. Sembra che l'inchiostro foo.so cerchi solo simboli da a e b.so e tutte le loro dipendenze.

Questo ha senso, in una certa misura, poiché altrimenti il ​​caricamento di c.so potrebbe modificare il comportamento di foo.so in un punto in cui b.so utilizza già la libreria. D'altra parte, nella domanda che mi ha fatto iniziare questo ha causato un po 'di problemi, quindi mi chiedo se c'è un modo per aggirare questo problema. E per trovare il modo di aggirare, prima ho bisogno di una buona comprensione dei dettagli molto precisi su come viene specificata la risoluzione dei simboli in questi casi.

Qual è la specifica o altro documento tecnico per definire il comportamento corretto in questi scenari?

+0

Hai dato un'occhiata a [questo PDF] (http://refspecs.linuxbase.org/elf/elf.pdf)? Un sacco di dati interessanti, ma non sono sicuro se include ciò che cerchi. – rodrigo

+0

@rodrigo: Non sono sicuro se fosse questo o qualcosa di simile, ma finora tutti i documenti ELF che ho trovato descrivono solo il collegamento dinamico prima dell'esecuzione di un binario, non il collegamento coinvolto in oggetti caricati dinamicamente. È un documento lungo e potrei aver cercato nei posti sbagliati, ma finora non sembra essere quello che sto cercando. – MvG

+0

E che dire di questo [post di Drepper] (http://www.sourceware.org/ml/libc-hacker/2000-06/msg00029.html) e il suo più o meno [documento correlato] (http: // www. akkadia.org/drepper/dsohowto.pdf) (vedi sezione 1.5.2)? Mentre lo interpreto, i simboli deboli sono usati solo per il collegamento statico. Quindi 'dlopen()' non farebbe differenza tra simboli deboli e forti. – rodrigo

risposta

11

Sfortunatamente, la documentazione autorevole è il codice sorgente. La maggior parte delle distribuzioni di Linux usano glibc o il suo fork, eglibc. Nel codice sorgente per entrambi, il file che dovrebbe documentare dlopen() recita come segue:

manuale/libdl.texi

@c FIXME these are undocumented: 
@c dladdr 
@c dladdr1 
@c dlclose 
@c dlerror 
@c dlinfo 
@c dlmopen 
@c dlopen 
@c dlsym 
@c dlvsym 

Quali specifiche tecniche v'è si possono trarre dalla ELF specification e lo standard POSIX . La specifica ELF è ciò che rende significativo un simbolo debole. POSIX è lo stesso specification for dlopen().

Questo è quello che trovo essere la parte più rilevante delle specifiche ELF.

Quando l'editor di collegamenti cerca le librerie di archivi, estrae l'archivio membri che contengono definizioni di simboli globali non definiti. La definizione del membro può essere un simbolo globale o debole.

Le specifiche ELF non fanno riferimento al caricamento dinamico, quindi il resto di questo paragrafo è la mia interpretazione. La ragione per cui ritengo importante quanto sopra è che i simboli di risoluzione si verificano in un singolo "quando". Nell'esempio fornito, quando il programma a carica dinamicamente b.so, il caricatore dinamico tenta di risolvere i simboli non definiti. Potrebbe finire per farlo con simboli globali o deboli. Quando il programma carica dinamicamente c.so, il caricatore dinamico tenta nuovamente di risolvere i simboli non definiti. Nello scenario che descrivi, i simboli in b.so sono stati risolti con simboli deboli. Una volta risolti, quei simboli non sono più indefiniti. Non importa se sono stati utilizzati simboli globali o deboli per definirli. Non sono più non definiti dal momento in cui è stato caricato c.so.

Le specifiche ELF non forniscono una definizione precisa di cosa sia un editor di collegamenti o quando l'editor di collegamenti deve combinare i file oggetto. Presumibilmente è un non-problema perché il documento ha in mente il collegamento dinamico.

POSIX descrive alcune delle funzionalità dlopen() ma lascia molto all'implementazione, inclusa la sostanza della domanda. POSIX non fa riferimento al formato ELF o ai simboli deboli in generale. Per i sistemi che implementano dlopen() non c'è nemmeno bisogno di alcuna nozione di simboli deboli.

http://pubs.opengroup.org/onlinepubs/9699919799/functions/dlopen.html

conformità POSIX è parte di un altro standard, il Linux Standard Base. Le distribuzioni Linux possono o meno scegliere di seguire questi standard e possono o non possono andare nel merito di essere certificati. Per esempio, capisco che una certificazione formale Unix da parte di Open Group è piuttosto costosa - da qui l'abbondanza di sistemi "Unix-like".

Un punto interessante sulla conformità degli standard di dlopen() viene effettuato su Wikipedia article for dynamic loading. dlopen(), come richiesto da POSIX, restituisce un vuoto *, ma C, come richiesto da ISO, afferma che un vuoto * è un puntatore a un oggetto e tale puntatore non è necessariamente compatibile con un puntatore a funzione.

Resta il fatto che qualsiasi conversione tra funzione e oggetto puntatori deve essere considerato come un (intrinsecamente non portabile) estensione attuazione, e che non esiste alcun modo "corretto" per un conversione diretta, poiché in questo riguardo gli standard POSIX e ISO si contraddicono a vicenda.

Gli standard esistenti sono in contraddizione e in ogni caso i documenti standard non possono essere particolarmente significativi. Ecco Ulrich Drepper che scrive del suo disprezzo per Open Group e le loro "specifiche".

http://udrepper.livejournal.com/8511.html

sentimento simile è espressa nel post legati da Rodrigo.

La ragione per cui ho fatto questo cambiamento non è davvero di essere più conforme (è bello, ma c'è ragione dal momento che nessuno si è lamentato per il vecchio comportamento).

Dopo aver guardato in esso, credo che la risposta corretta alla domanda che hai chiesto è che non v'è alcun diritto o un comportamento sbagliato per dlopen() in questo senso. Probabilmente, una volta che una ricerca ha risolto un simbolo, non è più indefinito e nelle ricerche successive il caricatore dinamico non tenterà di risolvere il simbolo già definito.

Infine, come si afferma nei commenti, ciò che si descrive nel post originale non è corretto. Le librerie condivise caricate dinamicamente possono essere utilizzate per risolvere i simboli non definiti in librerie condivise caricate in precedenza in modo dinamico. In realtà, questo non è limitato ai simboli non definiti nel codice caricato dinamicamente. Ecco un esempio in cui l'eseguibile stesso ha un simbolo indefinito che viene risolto tramite il caricamento dinamico.

main.c

#include <dlfcn.h> 

void say_hi(void); 

int main(void) { 
    void* symbols_b = dlopen("./dyload.so", RTLD_NOW | RTLD_GLOBAL); 
    /* uh-oh, forgot to define this function */ 
    /* better remember to define it in dyload.so */ 
    say_hi(); 
    return 0; 
} 

dyload.c

#include <stdio.h> 
void say_hi(void) { 
    puts("dyload.so: hi"); 
} 

compilare ed eseguire.

gcc-4.8 main -fpic -ldl -Wl,--unresolved-symbols=ignore-all -o main 
gcc-4.8 dyload.c -shared -fpic -o dyload.so 
$ ./main 
dyload.so: hi 

Si noti che l'eseguibile principale stesso è stato compilato come PIC.

+0

Questo è ciò che chiamo una risposta eccellente! – paulotorrens