169 votes

Comment puis-je créer un lien vers une version spécifique de la glibc ?

Lorsque je compile quelque chose sur mon PC Ubuntu Lucid 10.04, il est lié à la glibc. Lucid utilise la version 2.11 de la glibc. Lorsque j'exécute ce binaire sur un autre PC avec une glibc plus ancienne, la commande échoue en disant qu'il n'y a pas de glibc 2.11...

Pour autant que je sache, la glibc utilise la version des symboles. Puis-je forcer gcc à créer des liens avec une version de symbole spécifique ?

Dans mon cas concret, j'essaie de compiler une chaîne d'outils gcc cross pour ARM.

92 votes

Argh c'est l'un de ces problèmes linux vraiment ennuyeux où la solution est toujours "vous ne devriez pas faire ça", ce qui signifie bien sûr "ça ne marche pas et personne ne l'a encore corrigé".

14 votes

Les gens se plaignaient de l'enfer des DLL sous Windows. Je me souviens que Linux un peu de Les aficionados qui tentent d'en faire un exemple particulièrement horrible dans le monde Windows. Quand j'ai rencontré pour la première fois este en faisant du développement Linux il y a plus de 10 ans, je ne faisais que m'enterrer dans mes mains.

90voto

jschmier Points 8088

Vous avez raison de dire que la glibc utilise le versioning des symboles. Si vous êtes curieux, l'implémentation de la gestion des versions de symboles introduite dans la glibc 2.1 est décrite comme suit aquí et est une extension du schéma de versionnement des symboles de Sun décrit dans le document aquí .

Une option est de lier statiquement votre binaire. C'est probablement l'option la plus simple.

Vous pouvez également construire votre binaire dans un environnement de construction chroot, ou en utilisant une glibc- nouveau \=> glibc- vieux compilateur croisé.

Selon le http://www.trevorpounds.com article de blog Liaison avec des symboles d'anciennes versions (glibc) il est possible de forcer n'importe quel symbole à être lié à un symbole plus ancien, pour autant qu'il soit valide, en utilisant la même méthode que celle utilisée pour les symboles. .symver qui est utilisé pour définir les symboles versionnés en premier lieu. L'exemple suivant est extrait du document article de blog .

L'exemple suivant utilise le realpath de la glibc, mais s'assure qu'il est lié à une ancienne version 2.2.5.

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>

__asm__(".symver realpath,realpath@GLIBC_2.2.5");
int main()
{
    const char* unresolved = "/lib64";
    char resolved[PATH_MAX+1];

    if(!realpath(unresolved, resolved))
        { return 1; }

    printf("%s\n", resolved);

    return 0;
}

27 votes

La glibc ne supporte pas l'édition de liens statiques - les programmes glibc liés statiquement ne fonctionnent pas toujours sur des systèmes avec des versions de libc différentes.

11 votes

De la glibc libc.a continue d'exister, la glibc le prend en charge en un peu de cas, bien qu'il soit non recommandé (Drepper) . Vous aurez des difficultés avec les programmes non triviaux, en particulier tout ce qui utilise le NSS (solution de contournement dans la FAQ ).

6 votes

Pourquoi gcc/ld ne supporte-t-il pas cela à un niveau global ? Disons que je veux faire un lien avec la version 2.16 des symboles, quelle que soit la dernière version installée ? Cela semble être un besoin commun et éviterait de "construire sur un ancien système".

33voto

Ciro Santilli Points 3341

Configuration 1 : compilez votre propre glibc sans GCC dédié et utilisez-la

Comme il semble impossible de se contenter des bidouillages de versions de symboles, allons plus loin et compilons nous-mêmes la glibc.

Cette configuration peut fonctionner et est rapide car elle ne recompile pas toute la chaîne d'outils GCC, seulement la glibc.

Mais il n'est pas fiable car il utilise des objets d'exécution C hôtes tels que crt1.o , crti.o et crtn.o fournie par la glibc. Ceci est mentionné à : https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location Ces objets effectuent des réglages précoces sur lesquels la glibc s'appuie, je ne serais donc pas surpris que les choses se plantent de manière merveilleusement subtile.

Pour une configuration plus fiable, voir la configuration 2 ci-dessous.

Construire la glibc et l'installer localement :

export glibc_install="$(pwd)/glibc/build/install"

git clone git://sourceware.org/git/glibc.git
cd glibc
git checkout glibc-2.28
mkdir build
cd build
../configure --prefix "$glibc_install"
make -j `nproc`
make install -j `nproc`

Setup 1 : vérifier la construction

test_glibc.c

#define _GNU_SOURCE
#include <assert.h>
#include <gnu/libc-version.h>
#include <stdatomic.h>
#include <stdio.h>
#include <threads.h>

atomic_int acnt;
int cnt;

int f(void* thr_data) {
    for(int n = 0; n < 1000; ++n) {
        ++cnt;
        ++acnt;
    }
    return 0;
}

int main(int argc, char **argv) {
    /* Basic library version check. */
    printf("gnu_get_libc_version() = %s\n", gnu_get_libc_version());

    /* Exercise thrd_create from -pthread,
     * which is not present in glibc 2.27 in Ubuntu 18.04.
     * https://stackoverflow.com/questions/56810/how-do-i-start-threads-in-plain-c/52453291#52453291 */
    thrd_t thr[10];
    for(int n = 0; n < 10; ++n)
        thrd_create(&thr[n], f, NULL);
    for(int n = 0; n < 10; ++n)
        thrd_join(thr[n], NULL);
    printf("The atomic counter is %u\n", acnt);
    printf("The non-atomic counter is %u\n", cnt);
}

Compiler et exécuter avec test_glibc.sh :

#!/usr/bin/env bash
set -eux
gcc \
  -L "${glibc_install}/lib" \
  -I "${glibc_install}/include" \
  -Wl,--rpath="${glibc_install}/lib" \
  -Wl,--dynamic-linker="${glibc_install}/lib/ld-linux-x86-64.so.2" \
  -std=c11 \
  -o test_glibc.out \
  -v \
  test_glibc.c \
  -pthread \
;
ldd ./test_glibc.out
./test_glibc.out

Le programme produit les résultats attendus :

gnu_get_libc_version() = 2.28
The atomic counter is 10000
The non-atomic counter is 8674

Commande adaptée de https://sourceware.org/glibc/wiki/Testing/Builds?action=recall&rev=21#Compile_against_glibc_in_an_installed_location mais --sysroot l'a fait échouer avec :

cannot find /home/ciro/glibc/build/install/lib/libc.so.6 inside /home/ciro/glibc/build/install

alors je l'ai enlevé.

ldd confirme que le ldd et les bibliothèques que nous venons de construire sont effectivement utilisées comme prévu :

+ ldd test_glibc.out
        linux-vdso.so.1 (0x00007ffe4bfd3000)
        libpthread.so.0 => /home/ciro/glibc/build/install/lib/libpthread.so.0 (0x00007fc12ed92000)
        libc.so.6 => /home/ciro/glibc/build/install/lib/libc.so.6 (0x00007fc12e9dc000)
        /home/ciro/glibc/build/install/lib/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fc12f1b3000)

El gcc La sortie de débogage de la compilation montre que les objets d'exécution de mon hôte ont été utilisés, ce qui est mauvais comme mentionné précédemment, mais je ne sais pas comment le contourner, par exemple, il contient :

COLLECT_GCC_OPTIONS=/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crt1.o

Configuration 1 : modifier la glibc

Maintenant, modifions la glibc avec :

diff --git a/nptl/thrd_create.c b/nptl/thrd_create.c
index 113ba0d93e..b00f088abb 100644
--- a/nptl/thrd_create.c
+++ b/nptl/thrd_create.c
@@ -16,11 +16,14 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */

+#include <stdio.h>
+
 #include "thrd_priv.h"

 int
 thrd_create (thrd_t *thr, thrd_start_t func, void *arg)
 {
+  puts("hacked");
   _Static_assert (sizeof (thr) == sizeof (pthread_t),
                   "sizeof (thr) != sizeof (pthread_t)");

Puis recompiler et réinstaller la glibc, et recompiler et ré-exécuter notre programme :

cd glibc/build
make -j `nproc`
make -j `nproc` install
./test_glibc.sh

et nous voyons hacked a été imprimé quelques fois, comme prévu.

Cela confirme que nous avons bien utilisé la glibc que nous avons compilée et non celle de l'hôte.

Testé sur Ubuntu 18.04.

Configuration 2 : configuration pristine crosstool-NG

C'est une alternative à la configuration 1, et c'est la configuration la plus correcte que j'ai réalisée jusqu'à présent : tout est correct pour autant que je puisse observer, y compris les objets d'exécution C tels que crt1.o , crti.o et crtn.o .

Dans cette configuration, nous allons compiler une chaîne d'outils GCC complète et dédiée qui utilise la glibc que nous voulons.

Le seul inconvénient de cette méthode est que la construction sera plus longue. Mais je ne voudrais pas risquer une installation de production avec quelque chose de moins.

crosstool-NG est un ensemble de scripts qui télécharge et compile tout depuis les sources pour nous, y compris GCC, glibc et binutils.

Oui, le système de construction GCC est si mauvais que nous avons besoin d'un projet séparé pour cela.

Cette configuration n'est pas parfaite car crosstool-NG ne prend pas en charge la construction des exécutables sans -Wl drapeaux ce qui est bizarre puisque nous avons construit GCC lui-même. Mais tout semble fonctionner, donc ce n'est qu'un inconvénient.

Obtenez crosstool-NG et configurez-le :

git clone https://github.com/crosstool-ng/crosstool-ng
cd crosstool-ng
git checkout a6580b8e8b55345a5a342b5bd96e42c83e640ac5
export CT_PREFIX="$(pwd)/.build/install"
export PATH="/usr/lib/ccache:${PATH}"
./bootstrap
./configure --enable-local
make -j `nproc`
./ct-ng x86_64-unknown-linux-gnu
./ct-ng menuconfig

La seule option obligatoire que je vois est de le faire correspondre à la version de votre noyau hôte pour utiliser les bons en-têtes du noyau. Trouvez la version de votre noyau hôte avec :

uname -a

qui me montre :

4.15.0-34-generic

donc en menuconfig Je le fais :

  • Operating System
    • Version of linux

alors je sélectionne :

4.14.71

qui est la première version égale ou plus ancienne. Elle doit être plus ancienne car le noyau est rétrocompatible.

Vous pouvez maintenant construire avec :

env -u LD_LIBRARY_PATH time ./ct-ng build CT_JOBS=`nproc`

et maintenant attendez environ trente minutes à deux heures pour la compilation.

Setup 2 : configurations optionnelles

El .config que nous avons généré avec ./ct-ng x86_64-unknown-linux-gnu a :

CT_GLIBC_V_2_27=y

Pour changer cela, dans menuconfig faire :

  • C-library
  • Version of glibc

sauver le .config et continuer la construction.

Ou, si vous voulez utiliser votre propre source de glibc, par exemple pour utiliser la glibc de la dernière version de git, procédez comme suit comme ceci :

  • Paths and misc options
    • Try features marked as EXPERIMENTAL : défini comme vrai
  • C-library
    • Source of glibc
      • Custom location : dire oui
      • Custom location
        • Custom source location : pointer vers un répertoire contenant les sources de votre glibc

où la glibc a été clonée en tant que :

git clone git://sourceware.org/git/glibc.git
cd glibc
git checkout glibc-2.28

Configuration 2 : testez-le

Une fois que vous avez construit la chaîne d'outils que vous voulez, testez-la avec :

#!/usr/bin/env bash
set -eux
install_dir="${CT_PREFIX}/x86_64-unknown-linux-gnu"
PATH="${PATH}:${install_dir}/bin" \
  x86_64-unknown-linux-gnu-gcc \
  -Wl,--dynamic-linker="${install_dir}/x86_64-unknown-linux-gnu/sysroot/lib/ld-linux-x86-64.so.2" \
  -Wl,--rpath="${install_dir}/x86_64-unknown-linux-gnu/sysroot/lib" \
  -v \
  -o test_glibc.out \
  test_glibc.c \
  -pthread \
;
ldd test_glibc.out
./test_glibc.out

Tout semble fonctionner comme dans la configuration 1, sauf que maintenant les objets d'exécution corrects ont été utilisés :

COLLECT_GCC_OPTIONS=/home/ciro/crosstool-ng/.build/install/x86_64-unknown-linux-gnu/bin/../x86_64-unknown-linux-gnu/sysroot/usr/lib/../lib64/crt1.o

Configuration 2 : échec de la tentative de recompilation efficace de la glibc

Cela ne semble pas possible avec crosstool-NG, comme expliqué ci-dessous.

Si tu ne fais que reconstruire ;

env -u LD_LIBRARY_PATH time ./ct-ng build CT_JOBS=`nproc`

alors vos modifications de l'emplacement des sources de la glibc personnalisée sont prises en compte, mais il construit tout à partir de zéro, ce qui le rend inutilisable pour le développement itératif.

Si nous le faisons :

./ct-ng list-steps

il donne un bon aperçu des étapes de la construction :

Available build steps, in order:
  - companion_tools_for_build
  - companion_libs_for_build
  - binutils_for_build
  - companion_tools_for_host
  - companion_libs_for_host
  - binutils_for_host
  - cc_core_pass_1
  - kernel_headers
  - libc_start_files
  - cc_core_pass_2
  - libc
  - cc_for_build
  - cc_for_host
  - libc_post_cc
  - companion_libs_for_target
  - binutils_for_target
  - debug
  - test_suite
  - finish
Use "<step>" as action to execute only that step.
Use "+<step>" as action to execute up to that step.
Use "<step>+" as action to execute from that step onward.

Par conséquent, nous voyons qu'il y a des étapes de la glibc entrelacées avec plusieurs étapes de la GCC, plus particulièrement libc_start_files vient avant cc_core_pass_2 ce qui est probablement l'étape la plus coûteuse avec cc_core_pass_1 .

Afin de ne construire qu'une seule étape, vous devez d'abord définir l'option "Enregistrer les étapes intermédiaires" dans .config pour la construction initiale :

  • Paths and misc options
    • Debug crosstool-NG
      • Save intermediate steps

et ensuite vous pouvez essayer :

env -u LD_LIBRARY_PATH time ./ct-ng libc+ -j`nproc`

mais malheureusement, le + requis comme mentionné à : https://github.com/crosstool-ng/crosstool-ng/issues/1033#issuecomment-424877536

Notez cependant que le redémarrage à une étape intermédiaire réinitialise le répertoire d'installation à l'état qu'il avait pendant cette étape. C'est-à-dire que vous aurez une libc reconstruite - mais pas de compilateur final construit avec cette libc (et donc, pas de bibliothèques de compilation comme libstdc++ non plus).

et fondamentalement rend toujours la reconstruction trop lente pour être faisable pour le développement, et je ne vois pas comment surmonter cela sans Parcheando crosstool-NG.

En outre, en partant de la libc n'a pas semblé recopier la source à partir de Custom source location ce qui rend cette méthode inutilisable.

Bonus : stdlibc++

Un bonus si vous vous intéressez également à la bibliothèque standard C++ : Comment éditer et recompiler les sources de la bibliothèque standard C++ libstdc++ de GCC ?

22voto

Kassius Points 176

Lien avec -statique . Lorsque vous établissez un lien avec -statique le linker incorpore la bibliothèque à l'intérieur de l'exécutable, donc l'exécutable sera plus gros, mais il peut être exécuté sur un système avec une ancienne version de la glibc parce que le programme utilisera sa propre bibliothèque au lieu de celle du système.

72 votes

Souvent, la raison pour laquelle vous voulez faire cela est que vous distribuez une application à source fermée. Dans ce cas, il n'est souvent pas permis d'établir des liens statiques pour des raisons de licence (si vous le faites, vous devrez publier tout votre code source) ; vous devez donc être prudent avec -static.

3 votes

En attendant, on peut souvent recourir à musl-libc, mais avec les programmes C++, les choses peuvent devenir plus compliquées, et il peut être nécessaire de spécifier une version du symbole.

18voto

Jonas Thiem Points 62

À mon avis, la solution la plus paresseuse (surtout si vous ne dépendez pas des dernières fonctionnalités de pointe de C/C++ ou des dernières fonctionnalités du compilateur) n'a pas encore été mentionnée, alors la voici :

Il suffit de construire sur le système avec le GLIBC le plus ancien que vous voulez encore supporter.

C'est en fait assez facile à faire de nos jours avec des technologies comme chroot, ou KVM/Virtualbox, ou docker, même si vous ne voulez pas vraiment utiliser une si vieille distro directement sur un ordinateur. En détail, pour faire un binaire portable maximum de votre logiciel, je recommande de suivre ces étapes :

  1. Choisissez simplement votre poison de sandbox/virtualisation/... peu importe, et utilisez-le pour vous procurer un ancien Ubuntu LTS virtuel et compilez avec le gcc/g++ qu'il contient par défaut. Cela limite automatiquement votre GLIBC à celui disponible dans cet environnement.

  2. Évitez de dépendre de librairies externes en dehors des librairies de base : par exemple, vous devriez lier dynamiquement les éléments du système de base comme glibc, libGL, libxcb/X11/wayland, libasound/libpulseaudio, éventuellement GTK+ si vous l'utilisez, mais sinon, liez de préférence statiquement les librairies externes/transportez-les si vous le pouvez. En particulier, les librairies les plus autonomes comme les chargeurs d'images, les décodeurs multimédia, etc. peuvent causer moins de casse sur d'autres distributions (la casse peut être causée par exemple si elle n'est présente que quelque part dans une version majeure différente) si vous les expédiez statiquement.

Avec cette approche, vous obtenez un binaire compatible avec l'ancienne GLIBC sans aucune modification manuelle des symboles, sans faire un binaire entièrement statique (qui peut être cassé pour des programmes plus complexes parce que la glibc déteste cela, et qui peut causer des problèmes de licence pour vous), et sans mettre en place une chaîne d'outils personnalisée, une copie personnalisée de la glibc, ou autre.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X