web-dev-qa-db-fra.com

Lier statistiquement libstdc ++: des pièges?

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, livrée avec une version considérablement plus ancienne de libstdc ++.

Actuellement, je compile avec -static-libstdc++ -static-libgcc, comme suggéré dans cet article de blog: Lier statistiquement libstdc ++ . L'auteur met en garde contre l'utilisation de code C++ chargé dynamiquement lors de la compilation de libstdc ++ de manière statique, ce que je n'ai pas encore vérifié. Malgré tout, tout semble se dérouler sans heurts jusqu'à présent: je peux utiliser les fonctionnalités de C++ 11 sur Ubuntu 10.04, ce que je recherchais.

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

80
Nick Hutchinson

Ce billet de blog est assez inexact.

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

Pas vrai. Les seules modifications apportées à ABI C++ depuis GCC 3.4 ont été rétro-compatibles, ce qui signifie que l’ABI C++ est stable depuis près de neuf ans.

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

Les différences entre les versions corrigées de GCC dans les distributions sont mineures et non pas une modification ABI, par exemple. Fedora 4.6.3 20120306 (Red Hat 4.6.3-2) est compatible ABI avec les versions 4.6.x de la FSF en amont et très certainement avec toutes les 4.6.x de toute autre distribution.

Sous GNU/Linux, les bibliothèques d’exécution de GCC utilisent la gestion des versions de symboles ELF. Il est donc facile de vérifier les versions de symboles nécessaires aux objets et aux bibliothèques. Si vous avez un libstdc++.so Fournissant ces symboles, cela fonctionnera, peu importe si c'est une version légèrement différente de celle d'une autre version de votre distribution.

mais aucun code C++ (ni aucun 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 à libstdc++.a Est une option pour vous.

Si vous chargez dynamiquement une bibliothèque (avec dlopen), cela pourrait ne pas fonctionner car les symboles libstdc ++ dont il dépend n'ont peut-être pas été utiles à votre application lorsque vous l'avez liée (de manière statique); ces symboles ne seront donc pas présent dans votre exécutable. Ce problème peut être résolu en liant dynamiquement la bibliothèque partagée à libstdc++.so (Ce qui est bien de toute façon, si cela dépend.) L'interposition de symboles ELF signifie que les symboles présents dans votre exécutable seront utilisés par le bibliothèque partagée, mais d’autres non présentes dans votre exécutable se trouvent dans le lien libstdc++.so. Si votre application n'utilise pas dlopen, vous n'avez pas à vous en soucier.

Une autre option (et celle que je préfère) consiste à déployer le plus récent libstdc++.so À côté de votre application et à vous assurer qu'il se trouve avant le système par défaut libstdc++.so, Ce qui peut être fait en forçant l'éditeur de liens dynamique à rechercher au bon endroit, soit en utilisant la variable d’environnement $LD_LIBRARY_PATH au moment de l’exécution, soit en définissant un RPATH dans l’exécutable au moment de la liaison. Je préfère utiliser RPATH car cela ne dépend pas de la définition correcte de l'environnement pour que l'application fonctionne. Si vous liez votre application à '-Wl,-rpath,$Origin' (Notez les guillemets simples pour empêcher le shell d'essayer de développer $Origin), L'exécutable aura alors un RPATH de $Origin ce qui indique à l'éditeur de liens dynamique de 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écent libstdc++.so Dans le même répertoire que l'exécutable, celui-ci sera trouvé au moment de l'exécution, problème résolu. (Une autre option consiste à placer l'exécutable dans /some/path/bin/ Et la nouvelle libstdc ++. So dans /some/path/lib/ Et à les associer à '-Wl,-rpath,$Origin/../lib' Ou à tout autre emplacement fixe relatif à l'exécutable, et définir le RPATH relatif à $Origin)

120
Jonathan Wakely

Un ajout à l'excellente réponse de Jonathan Wakely, pourquoi dlopen () pose problème:

En raison du nouveau pool de traitement des exceptions dans GCC 5 (voir PR 64535 et PR 65434 ), si vous dlopen et dlclose une bibliothèque liée statiquement à libstdc ++, vous obtiendrez une fuite de mémoire (de l'objet pool) à chaque fois. Donc, s’il existe une chance que vous utilisiez jamais dlopen, il semble être une très mauvaise idée de lier statiquement libstdc ++. Notez qu'il s'agit d'une véritable fuite par opposition à la bénigne mentionnée dans PR 65434 .

9
Emil Styrke

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

Un exercice supplémentaire consisterait à créer un ensemble d’exemples C++ 11 impliqués à l’aide de cette méthodologie et à tester les fichiers binaires résultants sur un système réel 10.04. Dans la plupart des cas, à moins de faire quelque chose de bizarre avec le chargement dynamique, vous saurez tout de suite si le programme fonctionne ou s'il se bloque.

2

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

En jouant autour de -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 plug-in libstdc++ 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 de libstdc++.so, 'A' ne peut plus utiliser les 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 plug-ins pouvant être liés dynamiquement à libstdc++, L'application doit également être liée de manière dynamique. Ceci est juste mon observation et j'aimerais avoir vos commentaires.

0
Fedorov7890

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

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

Par exemple, disons que vous avez une application App.exe qui a une dépendance liée de manière dynamique sur libstdc ++. So.x pour GCC 4.9. Cette dépendance a été résolue dans App.exe via RPATH, c.-à-d.

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

Supposons maintenant qu’il existe une autre bibliothèque Dependency.so, qui dépend de libstdc ++. So.y de façon dynamique pour GCC 5.5. La dépendance ici est résolue via le RPATH de la bibliothèque, c.-à-d.

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

Lorsque App.exe charge Dependency.so, il n’ajoute ni ne préfixe 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, le chargement de la bibliothèque échouera.

Ceci est juste comme un avertissement, puisque j'ai moi-même rencontré ces problèmes dans le passé. RPATH est un outil très utile mais sa mise en œuvre présente encore quelques pièges.

0
Jonathan McDevitt