Collegamento statico di libstdc ++: qualche trucco?


90

Ho bisogno di distribuire un'applicazione C ++ costruita su Ubuntu 12.10 con libstdc ++ di GCC 4.7 su sistemi che eseguono Ubuntu 10.04, che viene fornito con una versione notevolmente precedente di libstdc ++.

Attualmente sto compilando -static-libstdc++ -static-libgcc, come suggerito da questo post del blog: Collegamento statico di libstdc ++ . L'autore mette in guardia dall'usare qualsiasi codice C ++ caricato dinamicamente durante la compilazione statica di libstdc ++, cosa che non ho ancora verificato. Tuttavia, finora sembra tutto filare liscio: posso utilizzare le funzionalità C ++ 11 su Ubuntu 10.04, che è quello che stavo cercando.

Noto che questo articolo è del 2005 e forse molto è cambiato da allora. I suoi consigli sono ancora attuali? Ci sono problemi in agguato di cui dovrei essere a conoscenza?


No, il collegamento statico a libstdc ++ non lo implica. Se implicasse che l' -static-libstdc++opzione non avrebbe senso, useresti semplicemente-static
Jonathan Wakely,

@ JonathanWakely -static otterrà un kernel too olderrore in alcuni sistemi Ubuntu 1404. Glibc.so è come kernel32.dllin window, fa parte dell'interfaccia del sistema operativo, non dovremmo incorporarlo nel nostro binario. Puoi usarlo objdump -T [binary path]per vederlo caricato dinamicamente libstdc++.soo meno. Per il programmatore golang, è possibile aggiungere #cgo linux LDFLAGS: -static-libstdc++ -static-libgccprima dell'importazione "C"
bronze man

@bronzeman, ma stiamo parlando di -static-libstdc++non -staticquindi libc.sonon sarà collegato staticamente.
Jonathan Wakely

1
@ NickHutchinson il post del blog collegato è sparito. Questa domanda SO è un successo di ricerca popolare per i termini pertinenti qui. Puoi riprodurre le informazioni critiche da quel post del blog nella tua domanda o offrire un nuovo collegamento se sai dove è stato spostato?
Brian Cain,

Risposte:


134

Quel post sul blog è piuttosto impreciso.

Per quanto ne so, le modifiche C ++ ABI sono state introdotte con tutte le principali versioni di GCC (cioè quelle con diversi componenti del numero di prima o seconda versione).

Non vero. Le uniche modifiche ABI C ++ introdotte da GCC 3.4 sono state retrocompatibili, il che significa che l'ABI C ++ è rimasta stabile per quasi nove anni.

A peggiorare le cose, la maggior parte delle principali distribuzioni Linux utilizza snapshot di GCC e / o patcha le loro versioni di GCC, rendendo praticamente impossibile sapere esattamente con quali versioni di GCC potresti avere a che fare quando distribuisci i binari.

Le differenze tra le versioni patchate delle distribuzioni di GCC sono minori e non cambiano ABI, ad esempio Fedora 4.6.3 20120306 (Red Hat 4.6.3-2) è ABI compatibile con le versioni upstream di FSF 4.6.x e quasi certamente con qualsiasi 4.6. x da qualsiasi altra distribuzione.

Su GNU / Linux le librerie di runtime di GCC usano il controllo delle versioni dei simboli ELF quindi è facile controllare le versioni dei simboli necessarie per oggetti e librerie, e se hai un libstdc++.soche fornisce quei simboli funzionerà, non importa se è una versione con patch leggermente diversa da un'altra versione della tua distribuzione.

ma nessun codice C ++ (o qualsiasi codice che utilizza il supporto di runtime C ++) può essere collegato dinamicamente se questo deve funzionare.

Anche questo non è vero.

Detto questo, il collegamento statico a libstdc++.aè un'opzione per te.

Il motivo per cui potrebbe non funzionare se carichi dinamicamente una libreria (usando dlopen) è che i simboli libstdc ++ da cui dipende potrebbero non essere stati necessari all'applicazione quando l'hai (staticamente) collegata, quindi quei simboli non saranno presenti nel tuo eseguibile. Ciò può essere risolto collegando dinamicamente la libreria condivisa a libstdc++.so(che è la cosa giusta da fare comunque se dipende da essa.) L'interposizione di simboli ELF significa che i simboli presenti nel tuo eseguibile verranno utilizzati dalla libreria condivisa, ma altri no presente nel tuo eseguibile si troverà in quello a cui libstdc++.sosi collega. Se la tua applicazione non usa, dlopennon devi preoccupartene.

Un'altra opzione (e quella che preferisco) è quella di distribuire il più recente libstdc++.soinsieme alla tua applicazione e assicurarti che venga trovato prima del sistema predefinito libstdc++.so, cosa che può essere eseguita forzando il linker dinamico a guardare nel posto giusto, utilizzando $LD_LIBRARY_PATHla variabile di ambiente in run- time, o impostando un RPATHnell'eseguibile al momento del collegamento. Preferisco usare RPATHin quanto non si basa sull'ambiente impostato correttamente affinché l'applicazione funzioni. Se colleghi la tua applicazione con '-Wl,-rpath,$ORIGIN'(nota le virgolette singole per evitare che la shell tenti di espandersi $ORIGIN), l'eseguibile avrà una RPATHdelle $ORIGINquali dice al linker dinamico di cercare le librerie condivise nella stessa directory dell'eseguibile stesso. Se metti il ​​file più recentelibstdc++.sonella stessa directory dell'eseguibile verrà trovato in fase di esecuzione, problema risolto. (Un'altra opzione è inserire l'eseguibile /some/path/bin/e la più recente libstdc ++. Così dentro /some/path/lib/e collegarsi con '-Wl,-rpath,$ORIGIN/../lib'o qualsiasi altra posizione fissa relativa all'eseguibile e impostare l'RPATH relativo a $ORIGIN)


8
Questa spiegazione, specialmente riguardo a RPATH, è gloriosa.
nilweed

3
Spedire libstdc ++ con la tua app su Linux è un cattivo consiglio. Google per "steam libstdc ++" per vedere tutto il dramma che questo comporta. In breve, se il tuo exe carica le librerie esterne (come, opengl) che vogliono dlopen di nuovo libstdc ++ (come, i driver radeon), quelle librerie useranno la tua libstdc ++ perché è già caricata, invece della loro, che è ciò di cui hanno bisogno e aspettarsi. Quindi sei tornato al punto di partenza.

7
@cap, l'OP chiede specificamente di distribuire su una distribuzione in cui il sistema libstdc ++ è più vecchio. Il problema di Steam è che hanno impacchettato una libstdc ++. Quindi era più vecchia di quella di sistema (presumibilmente era più recente al momento in cui l'hanno raggruppata, ma le distribuzioni sono passate a quelle ancora più nuove). Ciò può essere risolto facendo in modo che RPATH punti a una directory contenente un collegamento libstdc++.so.6simbolico che è impostato al momento dell'installazione per puntare alla libreria in bundle oa quella di sistema se è più recente. Esistono modelli di collegamento misto più complicati, come quelli utilizzati da Red Hat DTS, ma sono difficili da realizzare da soli.
Jonathan Wakely

5
hey amico, mi dispiace se non voglio che il mio modello per la spedizione di binari compatibili con le versioni precedenti includa "fidarsi di altre persone per mantenere libstdc ++ ABI compat" o "collegare in modo condizionale libstdc ++ in fase di esecuzione" ... se questo arruffa alcune piume qui e lì, cosa posso fare, non voglio mancare di rispetto. E se ricordi il dramma memcpy@GLIBC_2.14, non puoi davvero biasimarmi per avere problemi di fiducia con questo :)

6
Ho dovuto usare '-Wl, -rpath, $ ORIGIN' (nota il '-' davanti a rpath). Non riesco a modificare la risposta perché le modifiche devono contenere almeno 6 caratteri ....
user368507

11

Un'aggiunta all'eccellente risposta di Jonathan Wakely, perché dlopen () è problematico:

A causa del nuovo pool di gestione delle eccezioni in GCC 5 (vedere PR 64535 e PR 65434 ), se si dlopen e dlclose una libreria che è staticamente collegata a libstdc ++, si verificherà ogni volta una perdita di memoria (dell'oggetto pool). Quindi, se c'è qualche possibilità che tu possa mai usare dlopen, sembra davvero una cattiva idea collegare staticamente libstdc ++. Si noti che questa è una perdita reale rispetto a quella benigna menzionata nel PR 65434 .


1
La funzione __gnu_cxx::__freeres()sembra fornire almeno un aiuto con questo problema, poiché libera il buffer interno dell'oggetto pool. Ma per me non è abbastanza chiaro quale implicazione abbia una chiamata a questa funzione rispetto alle eccezioni lanciate accidentalmente in seguito.
phlipsy

3

Add-on alla risposta di Jonathan Wakely riguardo all'RPATH:

RPATH funzionerà solo se l'RPATH in questione è l'RPATH dell'applicazione in esecuzione . Se si dispone di una libreria che si collega dinamicamente a qualsiasi libreria tramite il proprio RPATH, l'RPATH della libreria verrà sovrascritto dall'RPATH dell'applicazione che lo carica. Questo è un problema quando non puoi garantire che l'RPATH dell'applicazione sia lo stesso di quello della tua libreria, ad esempio se ti aspetti che le tue dipendenze siano in una particolare directory, ma quella directory non fa parte dell'RPATH dell'applicazione.

Ad esempio, supponiamo di avere un'applicazione App.exe che ha una dipendenza collegata dinamicamente da libstdc ++. So.x per GCC 4.9. L'App.exe ha questa dipendenza risolta tramite RPATH, cioè

App.exe (RPATH=.:./gcc4_9/libstdc++.so.x)

Supponiamo ora che esista un'altra libreria Dependency.so, che ha una dipendenza collegata dinamicamente da libstdc ++. So.y per GCC 5.5. La dipendenza qui viene risolta tramite l'RPATH della libreria, ad es

Dependency.so (RPATH=.:./gcc5_5/libstdc++.so.y)

Quando App.exe carica Dependency.so, non aggiunge né antepone l'RPATH della libreria . Non lo consulta affatto. L'unico RPATH considerato sarà quello dell'applicazione in esecuzione, o App.exe in questo esempio. Ciò significa che se la libreria si basa su simboli che si trovano in gcc5_5 / libstdc ++. So.y ma non in gcc4_9 / libstdc ++. So.x, la libreria non verrà caricata.

Questo è solo un avvertimento, poiché in passato mi sono imbattuto in questi problemi. RPATH è uno strumento molto utile ma la sua implementazione ha ancora alcuni trucchi.


quindi RPATH per le librerie condivise è un po 'inutile! E speravo che abbiano migliorato un po 'Linux sotto questo aspetto negli ultimi 2 decenni ...
Frank Puck

2

Potrebbe anche essere necessario assicurarsi di non dipendere dalla glibc dinamica. Esegui lddsul tuo eseguibile risultante e nota eventuali dipendenze dinamiche (libc / libm / libpthread sono sospetti usuali).

Un esercizio aggiuntivo consisterebbe nella creazione di un gruppo di esempi C ++ 11 coinvolti utilizzando questa metodologia e provando effettivamente i binari risultanti su un vero sistema 10.04. Nella maggior parte dei casi, a meno che tu non faccia qualcosa di strano con il caricamento dinamico, saprai subito se il programma funziona o si blocca.


1
Qual è il problema con la dipendenza dalla glibc dinamica?
Nick Hutchinson

Credo che almeno qualche tempo fa libstdc ++ implicasse una dipendenza da glibc. Non sono sicuro di come stanno le cose oggi.
Alexander L. Belikoff

9
libstdc ++ dipende da glibc (ad esempio iostream sono implementati in termini di printf) ma fintanto che glibc su Ubuntu 10.04 fornisce tutte le funzionalità necessarie alla nuova libstdc ++ non c'è problema a seconda della dinamica glibc, infatti è altamente raccomandato non collegare mai staticamente a glibc
Jonathan Wakely

1

Vorrei aggiungere alla risposta di Jonathan Wakely quanto segue.

Giocando -static-libstdc++su Linux, ho affrontato il problema con dlclose(). Supponiamo di avere un'applicazione "A" collegata staticamente libstdc++e che venga caricata dinamicamente collegata al libstdc++plug-in "P" in fase di esecuzione. Va bene. Ma quando "A" scarica "P", si verifica un errore di segmentazione. La mia ipotesi è che dopo lo scaricamento libstdc++.so, "A" non possa più utilizzare i simboli relativi a libstdc++. Si noti che se sia "A" che "P" sono collegati staticamente a libstdc++, o se "A" è collegato dinamicamente e "P" staticamente, il problema non si verifica.

Riepilogo: se la tua applicazione carica / scarica plug-in che possono collegarsi dinamicamente libstdc++, l'app deve anche essere collegata dinamicamente ad essa. Questa è solo una mia osservazione e mi piacerebbe ricevere i tuoi commenti.


1
Questo è probabilmente simile al mescolare le implementazioni di libc (diciamo il collegamento dinamico a un plugin che a sua volta collega dinamicamente glibc, mentre l'applicazione stessa è staticamente collegata a musl-libc). Rich Felker, autore di musl-libc, afferma che il problema in un simile scenario è che la gestione della memoria glibc (utilizzando sbrk) fa un certo presupposto e si aspetta praticamente di essere solo all'interno di un processo ... non sono sicuro se questo è limitato a un particolare versione di glibc o qualsiasi altra cosa, però.
0xC0000022L

e le persone ancora non vedono i vantaggi dell'interfaccia heap di Windows, che è in grado di gestire più copie indipendenti di libc ++ / libc all'interno di un singolo processo. Queste persone non dovrebbero progettare software.
Frank Puck

@FrankPuck avendo una discreta esperienza sia con Windows che con Linux, posso dirti che il modo in cui "Windows" lo fa non ti aiuterà quando MSVC è la parte che decide quale allocatore viene utilizzato e come. Il vantaggio principale che vedo con gli heap su Windows è che puoi distribuire pezzi e pezzi e poi liberarli in un colpo solo. Ma con MSVC incontrerai ancora più o meno il problema descritto sopra, ad esempio quando passi i puntatori allocati da un altro runtime VC (rilascio vs debug o staticamente vs dinamicamente collegati). Quindi "Windows" non è immune. Bisogna fare attenzione su entrambi i sistemi.
0xC0000022L
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.