Why is it, that ld MUST be able to locate liba.so
when linking test
? Because to me it doesn't seem like ld is doing much else than confirming liba.so
's existence. For instance, running readelf --dynamic ./test
only lists libb.so
as needed, so I guess the dynamic linker must discover the libb.so -> liba.so
dependency on its own, and make it's own search for liba.so
.
Beh, se ho capito correttamente processo di collegamento, ld in realtà non ha bisogno di individuare anche libb.so
. Potrebbe semplicemente ignorare tutti i riferimenti non risolti in test
sperando che il linker dinamico li risolva durante il caricamento di libb.so
in fase di runtime. Ma se ld si comportasse in questo modo, molti errori di "riferimento non definito" non verrebbero rilevati al momento del collegamento, ma verrebbero rilevati quando si tenta di caricare test
in runtime. Quindi ld esegue semplicemente il controllo che tutti i simboli non trovati nello stesso test
possono essere trovati nelle librerie condivise che dipendono da test
. Quindi, se il programma test
ha un errore "riferimento non definito" (alcune variabili o funzioni non trovate in test
e nessuna in libb.so
), ciò diventa ovvio al momento del collegamento, non solo in fase di runtime. Quindi tale comportamento è solo un ulteriore controllo di sanità mentale.
Ma ld va anche oltre. Quando si collegano test
, ld controlla inoltre che tutti i riferimenti non risolti in libb.so
si trovano nelle librerie condivise che libb.so
dipende (nel nostro caso libb.so
dipende liba.so
, quindi richiede liba.so
per essere situato in fase di collegamento). Bene, in realtà ld ha già eseguito questo controllo, quando stava collegando libb.so
. Perché lo fa controllando la seconda volta ...Forse gli sviluppatori di ld hanno trovato questo doppio controllo utile per rilevare dipendenze non funzionanti quando si tenta di collegare il programma a una libreria obsoleta che potrebbe essere caricata nei tempi in cui era collegata, ma ora non può essere caricata perché le librerie dipendono on sono aggiornati (ad esempio, liba.so
è stato successivamente rielaborato e parte della funzione è stata rimossa da esso).
UPD
appena fatto alcuni esperimenti. Sembra che il mio assunto "in realtà ld ha già eseguito questo controllo, quando stava collegando libb.so
" è errato.
Supponiamo il liba.c
ha il seguente contenuto:
int liba_func(int i)
{
return i + 1;
}
e libb.c
ha la seguente:
int liba_func(int i);
int liba_nonexistent_func(int i);
int libb_func(int i)
{
return liba_func(i + 1) + liba_nonexistent_func(i + 2);
}
e test.c
#include <stdio.h>
int libb_func(int i);
int main(int argc, char *argv[])
{
fprintf(stdout, "%d\n", libb_func(argc));
return 0;
}
Quando si collega libb.so
:
0.123.516,410617 millions
gcc -o libb.so -fPIC -shared libb.c liba.so
linker non genera alcun messaggio di errore che liba_nonexistent_func
non può essere risolto, invece genera semplicemente una libreria condivisa interrotta libb.so
. Il comportamento è lo stesso di come si crea una libreria statica (libb.a
) con ar che non risolve anche i simboli della libreria generata.
Ma quando si tenta di collegare test
:
gcc -o test -Wl,-rpath-link=./ test.c libb.so
si ottiene l'errore:
libb.so: undefined reference to `liba_nonexistent_func'
collect2: ld returned 1 exit status
Rilevamento errore del genere non sarebbe possibile se ld non ha la scansione in modo ricorsivo tutte le condiviso librerie. Sembra quindi che la risposta alla domanda sia la stessa di cui ho detto sopra: ld, -rpath-link per assicurarsi che l'eseguibile collegato possa essere caricato in seguito da un caricamento dinamico. Solo un controllo di sanità.
UPD2
Avrebbe senso per verificare la presenza di riferimenti non risolti il più presto possibile (durante il collegamento libb.so
), ma ld per alcuni motivi non lo fa. Probabilmente è per consentire di creare dipendenze cicliche per le librerie condivise.
liba.c
può avere la seguente implementazione:
int libb_func(int i);
int liba_func(int i)
{
int (*func_ptr)(int) = libb_func;
return i + (int)func_ptr;
}
Così liba.so
utilizza libb.so
e libb.so
utilizza liba.so
(meglio non fare mai una cosa del genere).Questo compila con successo e funziona:
$ gcc -o liba.so -fPIC -shared liba.c
$ gcc -o libb.so -fPIC -shared libb.c liba.so
$ gcc -o test test.c -Wl,-rpath=./ libb.so
$ ./test
-1217026998
Anche se readelf dice che non ha bisogno di liba.so
libb.so
:
$ readelf -d liba.so | grep NEEDED
0x00000001 (NEEDED) Shared library: [libc.so.6]
$ readelf -d libb.so | grep NEEDED
0x00000001 (NEEDED) Shared library: [liba.so]
0x00000001 (NEEDED) Shared library: [libc.so.6]
Se ld controllato per i simboli non risolti durante il collegamento di una libreria condivisa, la il collegamento di liba.so
non sarebbe possibile.
Nota che ho usato chiave -rpath invece di -rpath-link. La differenza è che -rpath-link viene utilizzato in fase di collegamento solo per verificare che tutti i simboli del eseguibile finale possono essere risolti, mentre -rpath ingloba in realtà il percorso specificato come parametro nel ELF:
$ readelf -d test | grep RPATH
0x0000000f (RPATH) Library rpath: [./]
Quindi è ora possibile eseguire test
se le librerie condivise (liba.so
e libb.so
) si trovano nella directory di lavoro corrente (./
). Se hai appena utilizzato -rpath-link, non ci sarebbe nessuna voce in test
ELF e dovresti aggiungere il percorso alle librerie condivise al fileo alla variabile di ambiente LD_LIBRARY_PATH
.
UPD3
In realtà è possibile verificare la presenza di simboli non risolti durante il collegamento libreria condivisa, --no-undefined
opzione deve essere usata per fare quello:
$ gcc -Wl,--no-undefined -o libb.so -fPIC -shared libb.c liba.so
/tmp/cc1D6uiS.o: In function `libb_func':
libb.c:(.text+0x2d): undefined reference to `liba_nonexistent_func'
collect2: ld returned 1 exit status
Inoltre ho trovato un buon articolo che chiarisce molti aspetti del collegamento di librerie condivise che dipendono da altre librerie condivise: Better understanding Linux secondary dependencies solving with examples.
Imparato molto. Grazie. Il collegamento non funziona più però. –
@SurajeetBharati il link dovrebbe essere corretto una volta che la mia modifica è approvata (in pratica, sostituire la barra finale con '.html') – Edward
Funziona ora. :) –