Lier libstdc ++ statiquement: des pièges?

90

Je dois déployer une application C ++ construite sur Ubuntu 12.10 avec libstdc ++ de GCC 4.7 sur des systèmes exécutant Ubuntu 10.04, qui est livré avec une version considérablement plus ancienne de libstdc ++.

Actuellement, je compile avec -static-libstdc++ -static-libgcc, comme suggéré par ce billet de blog: Linking libstdc ++ statiquement . L'auteur met en garde contre l'utilisation de tout code C ++ chargé dynamiquement lors de la compilation statique de libstdc ++, ce que je n'ai pas encore vérifié. Pourtant, tout semble aller bien jusqu'à présent: je peux utiliser les fonctionnalités de C ++ 11 sur Ubuntu 10.04, ce que je recherchais.

Je note que cet article date de 2005, et peut-être que beaucoup de choses ont changé depuis. Ses conseils sont-ils toujours d'actualité? Y a-t-il des problèmes cachés dont je devrais être conscient?

Nick Hutchinson
la source
Non, la liaison statique à libstdc ++ n'implique pas cela. Si cela impliquait cela, l' -static-libstdc++option ne servirait à rien, vous utiliseriez simplement-static
Jonathan Wakely
@JonathanWakely -static obtiendra une kernel too olderreur dans certains systèmes ubuntu 1404. Le glibc.so est comme kernel32.dlldans window, il fait partie de l'interface du système d'exploitation, nous ne devons pas l'intégrer dans notre binaire. Vous pouvez utiliser objdump -T [binary path]pour le voir chargé dynamiquement libstdc++.soou non. Pour le programmeur golang, vous pouvez ajouter #cgo linux LDFLAGS: -static-libstdc++ -static-libgccavant l'importation "C"
bronze man
@bronzeman, mais nous ne parlons -static-libstdc++pas -staticdonc libc.sone sera pas lié statiquement.
Jonathan Wakely
1
@NickHutchinson, le billet de blog lié a disparu. Cette question SO est un succès de recherche populaire pour les termes pertinents ici. Pouvez-vous reproduire les informations critiques de cet article de blog dans votre question, ou proposer un nouveau lien si vous savez où il est déplacé?
Brian Cain
1
@BrianCain L'archive Internet l'a: web.archive.org/web/20160313071116/http://www.trilithium.com/…
Rob Keniger

Réponses:

135

Ce billet de blog est assez inexact.

Autant que je sache, des changements ABI C ++ ont été introduits avec chaque version majeure de GCC (c'est-à-dire ceux avec différents composants de premier ou deuxième numéro de version).

Pas vrai. Les seuls changements ABI C ++ introduits depuis GCC 3.4 ont été rétrocompatibles, ce qui signifie que l'ABI C ++ est stable depuis près de neuf ans.

Pour aggraver les choses, la plupart des principales distributions Linux utilisent des instantanés GCC et / ou corrigent leurs versions GCC, ce qui rend pratiquement impossible de savoir exactement à quelles versions GCC vous pourriez avoir affaire lorsque vous distribuez des binaires.

Les différences entre les versions corrigées des distributions de GCC sont mineures et ne changent pas ABI, par exemple la version 4.6.3 20120306 de Fedora (Red Hat 4.6.3-2) est compatible ABI avec les versions amont de FSF 4.6.x et presque certainement avec n'importe quelle version 4.6. x de toute autre distribution.

Sur les bibliothèques d'exécution de GNU / Linux GCC utilisent la gestion des versions des symboles ELF, il est donc facile de vérifier les versions de symboles nécessaires aux objets et aux bibliothèques, et si vous avez un libstdc++.soqui fournit ces symboles, cela fonctionnera, peu importe s'il s'agit d'une version corrigée légèrement différente d'une autre version de votre distribution.

mais aucun code C ++ (ou tout code utilisant le support d'exécution C ++) ne peut être lié dynamiquement si cela doit fonctionner.

Ce n'est pas vrai non plus.

Cela dit, la liaison statique vers libstdc++.aest une option pour vous.

La raison pour laquelle cela peut ne pas fonctionner si vous chargez dynamiquement une bibliothèque (en utilisant dlopen) est que les symboles libstdc ++ dont elle dépend n'ont peut-être pas été nécessaires à votre application lorsque vous l'avez liée (statiquement), donc ces symboles ne seront pas présents dans votre exécutable. Cela peut être résolu en liant dynamiquement la bibliothèque partagée à libstdc++.so(ce qui est la bonne chose à faire de toute façon si cela en dépend.) L'interposition de symboles ELF signifie que les symboles présents dans votre exécutable seront utilisés par la bibliothèque partagée, mais d'autres non présent dans votre exécutable se trouve dans celui vers lequel libstdc++.soil renvoie. Si votre application ne fonctionne pas, dlopenvous n'avez pas besoin de vous en soucier.

Une autre option (et celle que je préfère) est de déployer le plus récent à libstdc++.socôté de votre application et de vous assurer qu'il est trouvé avant le système par défaut libstdc++.so, ce qui peut être fait en forçant l'éditeur de liens dynamique à chercher au bon endroit, soit en utilisant $LD_LIBRARY_PATHla variable d'environnement lors de l'exécution. time, ou en définissant un RPATHdans l'exécutable au moment de la liaison. Je préfère utiliser RPATHcar il ne dépend pas de la configuration correcte de l'environnement pour que l'application fonctionne. Si vous liez votre application avec '-Wl,-rpath,$ORIGIN'(notez les guillemets simples pour empêcher le shell d' essayer d'élargir $ORIGIN) puis l'exécutable aura un RPATHde $ORIGINqui raconte l'éditeur de liens de dynamique pour rechercher des bibliothèques partagées dans le même répertoire que l'exécutable lui - même. Si vous mettez le plus récentlibstdc++.sodans le même répertoire que l'exécutable, il se trouvera au moment de l'exécution, problème résolu. (Une autre option est de mettre l'exécutable dans /some/path/bin/et le plus récent libstdc ++. So dans /some/path/lib/et lier avec '-Wl,-rpath,$ORIGIN/../lib'ou tout autre emplacement fixe par rapport à l'exécutable, et définir le RPATH par rapport à $ORIGIN)

Jonathan Wakely
la source
8
Cette explication, en particulier à propos de RPATH, est glorieuse.
nilweed
3
Envoyer libstdc ++ avec votre application sous Linux est un mauvais conseil. Google pour "steam libstdc ++" pour voir tout le drame que cela apporte. En bref, si votre exe charge des bibliothèques externes (comme, opengl) qui veulent ouvrir à nouveau libstdc ++ (comme, les pilotes radeon), ces bibliothèques utiliseront votre libstdc ++ car il est déjà chargé, au lieu de la leur, ce dont ils ont besoin et attendre. Vous êtes donc de retour à la case départ.
7
@cap, l'OP pose spécifiquement des questions sur le déploiement sur une distribution où le système libstdc ++ est plus ancien. Le problème de Steam est qu'ils ont regroupé un libstdc ++. De sorte qu'il était plus ancien que celui du système (il était probablement plus récent au moment où ils l'ont regroupé, mais les distributions sont passées à des versions encore plus récentes). Cela peut être résolu en faisant pointer le RPATH vers un répertoire contenant un libstdc++.so.6lien symbolique qui est défini au moment de l'installation pour pointer vers la bibliothèque fournie ou vers la bibliothèque système si elle est plus récente. Il existe des modèles de liaison mixte plus compliqués, tels qu'utilisés par Red Hat DTS, mais ils sont difficiles à faire soi-même.
Jonathan Wakely
5
hé mec, je suis désolé si je ne veux pas que mon modèle pour l'envoi de binaires rétrocompatibles inclue "faire confiance à d'autres personnes pour garder libstdc ++ ABI compat" ou "lier conditionnellement libstdc ++ à l'exécution" ... si cela dérange quelques plumes ici et là, que puis-je faire, je ne veux pas manquer de respect. Et si vous vous souvenez du drame memcpy@GLIBC_2.14, vous ne pouvez pas vraiment me reprocher d'avoir des problèmes de confiance avec ça :)
6
J'ai dû utiliser '-Wl, -rpath, $ ORIGIN' (notez le '-' devant rpath). Je ne peux pas modifier la réponse car les modifications doivent comporter au moins 6 caractères ....
user368507
11

Un ajout à l'excellente réponse de Jonathan Wakely, pourquoi dlopen () est problématique:

En raison du nouveau pool de gestion des exceptions dans GCC 5 (voir PR 64535 et PR 65434 ), si vous ouvrez et fermez une bibliothèque liée statiquement à libstdc ++, vous obtiendrez une fuite de mémoire (de l'objet pool) à chaque fois. Donc, s'il y a une chance que vous utilisiez un jour dlopen, cela semble être une très mauvaise idée de lier statiquement libstdc ++. Notez qu'il s'agit d'une vraie fuite par opposition à celle bénigne mentionnée dans le PR 65434 .

Emil Styrke
la source
1
La fonction __gnu_cxx::__freeres()semble apporter au moins une aide sur ce problème, car elle libère le tampon interne de l'objet pool. Mais pour moi, il est assez difficile de savoir quelle implication un appel à cette fonction a par rapport aux exceptions lancées accidentellement par la suite.
phlipsy
3

Complément à la réponse de Jonathan Wakely concernant le RPATH:

RPATH ne fonctionnera que si le RPATH en question est le RPATH de l' application en cours d'exécution . Si vous avez une bibliothèque qui se lie dynamiquement à n'importe quelle bibliothèque via son propre RPATH, le RPATH de la bibliothèque sera écrasé par le RPATH de l'application qui la charge. C'est un problème lorsque vous ne pouvez pas garantir que le RPATH de l'application est le même que celui de votre bibliothèque, par exemple si vous vous attendez à ce que vos dépendances soient dans un répertoire particulier, mais que ce répertoire ne fait pas partie du RPATH de l'application.

Par exemple, supposons que vous ayez une application App.exe qui a une dépendance liée dynamiquement à libstdc ++. So.x pour GCC 4.9. App.exe a cette dépendance résolue via le RPATH, c'est-à-dire

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

Supposons maintenant qu'il existe une autre bibliothèque Dependency.so, qui a une dépendance liée dynamiquement à libstdc ++. So.y pour GCC 5.5. La dépendance ici est résolue via le RPATH de la bibliothèque, c'est-à-dire

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

Lorsque App.exe charge Dependency.so, il n'ajoute ni n'ajoute le RPATH de la bibliothèque . Il ne le consulte pas du tout. Le seul RPATH considéré sera celui de l'application en cours d'exécution, ou App.exe dans cet exemple. Cela signifie que si la bibliothèque repose sur des symboles qui se trouvent dans gcc5_5 / libstdc ++. So.y mais pas dans gcc4_9 / libstdc ++. So.x, la bibliothèque échouera à se charger.

C'est juste un mot d'avertissement, car j'ai moi-même rencontré ces problèmes dans le passé. RPATH est un outil très utile mais son implémentation a encore quelques pièges.

Jonathan McDevitt
la source
donc RPATH pour les bibliothèques partagées est un peu inutile! Et j'espérais, qu'ils ont amélioré un peu Linux à cet égard au cours des 2 dernières décennies ...
Frank Puck
2

Vous devrez peut-être également vous assurer que vous ne dépendez pas de la glibc dynamique. Exécutez lddvotre exécutable résultant et notez toutes les dépendances dynamiques (libc / libm / libpthread sont habituellement suspects).

Un exercice supplémentaire consisterait à construire un tas d'exemples C ++ 11 impliqués en utilisant cette méthodologie et en essayant réellement les binaires résultants sur un vrai système 10.04. Dans la plupart des cas, à moins que vous ne fassiez quelque chose de bizarre avec le chargement dynamique, vous saurez immédiatement si le programme fonctionne ou s'il plante.

Alexander L. Belikoff
la source
1
Quel est le problème en fonction de la glibc dynamique?
Nick Hutchinson
Je crois qu'il y a au moins quelque temps libstdc ++ impliquait une dépendance à la glibc. Je ne sais pas où en sont les choses aujourd'hui.
Alexander L. Belikoff
9
libstdc ++ dépend de la glibc (par exemple, les iostreams sont implémentés en termes de printf) mais tant que la glibc sur Ubuntu 10.04 fournit toutes les fonctionnalités nécessaires à la nouvelle libstdc ++, il n'y a aucun problème avec la glibc dynamique, en fait il est fortement recommandé de ne jamais lier statiquement à la glibc
Jonathan Wakely
1

J'aimerais ajouter à la réponse de Jonathan Wakely ce qui suit.

En jouant -static-libstdc++sur Linux, j'ai rencontré le problème avec dlclose(). Supposons que nous ayons une application 'A' liée statiquement libstdc++et qu'elle se charge dynamiquement liée au libstdc++plugin 'P' au moment de l'exécution. C'est très bien. Mais lorsque «A» décharge «P», une erreur de segmentation se produit. Mon hypothèse est qu'après le déchargement libstdc++.so, «A» ne peut plus utiliser de symboles liés à libstdc++. Notez que si «A» et «P» sont liés statiquement à libstdc++, ou si «A» est lié dynamiquement et «P» statiquement, le problème ne se produit pas.

Résumé: si votre application charge / décharge des plugins qui peuvent libstdc++être liés dynamiquement, l'application doit également y être liée de manière dynamique. Ce n'est que mon observation et j'aimerais avoir vos commentaires.

Fedorov7890
la source
1
Cela s'apparente probablement au mélange d'implémentations de libc (disons un lien dynamique vers un plugin qui à son tour lie dynamiquement la glibc, alors que l'application elle-même est liée statiquement à musl-libc). Rich Felker, auteur de musl-libc, affirme que le problème dans un tel scénario est que la gestion de la mémoire glibc (utilisant sbrk) fait certaines hypothèses et s'attend à peu près à être seul dans un processus ... pas sûr que cela se limite à un une version particulière de la glibc ou autre.
0xC0000022L
et les gens ne voient toujours pas les avantages de l'interface de tas de fenêtres, qui est capable de gérer plusieurs copies indépendantes de libc ++ / libc dans un seul processus. Ces personnes ne devraient pas concevoir de logiciels.
Frank Puck
@FrankPuck ayant une quantité décente d'expérience à la fois Windows et Linux Je peux vous dire que la façon dont "Windows" le fait ne vous aidera pas quand MSVC est la partie qui décide quel allocateur est utilisé et comment. Le principal avantage que je vois avec les tas sur Windows est que vous pouvez distribuer des morceaux et les libérer d'un seul coup. Mais avec MSVC, vous rencontrerez toujours à peu près le problème décrit ci-dessus, par exemple lors du passage de pointeurs alloués par un autre runtime VC (version vs debug ou statiquement vs dynamiquement lié). "Windows" n'est donc pas à l'abri. Il faut faire attention aux deux systèmes.
0xC0000022L