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

111

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 ancienne glibc, la commande échoue en disant qu'il n'y a pas de glibc 2.11 ...

Autant que je sache, la glibc utilise la gestion des versions de symboles. Puis-je forcer gcc à se lier à une version de symbole spécifique?

Dans mon utilisation concrète, j'essaye de compiler une chaîne d'outils croisée gcc pour ARM.

falstaff
la source
58
Argh c'est l'un de ces problèmes Linux vraiment ennuyeux comme 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 résolu".
Timmmm
4
Les gens se sont plaints de l'enfer des DLL sous Windows. Je me souviens que certains aficionados de Linux essayaient de présenter cela comme un exemple particulièrement horrible du monde Windows. Quand j'ai rencontré pour la première fois ce développement de Linux il y a plus de dix ans, tout ce que j'ai fait a été d'enterrer mon visage entre mes mains.
0xC0000022L

Réponses:

70

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

Une option consiste à 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 un compilateur croisé glibc- new => glibc- old .

Selon l' article de blog http://www.trevorpounds.com Linking to Older Versioned Symbols (glibc) , il est possible de forcer n'importe quel symbole à être lié à un ancien tant qu'il est valide en utilisant le même .symverpseudo -op qui est utilisé pour définir les symboles versionnés en premier lieu. L'exemple suivant est extrait du billet de blog .

L'exemple suivant utilise le chemin réel 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;
}
jschmier
la source
18
la glibc ne prend pas en charge les liaisons statiques - les programmes de glibc liés statiquement ne fonctionnent généralement pas sur des systèmes avec différentes versions de libc.
Rappelez
5
la glibc libc.acontinue d'exister, la glibc le supporte dans certains cas, bien que ce ne soit pas recommandé (Drepper) . Vous aurez des problèmes avec les programmes non triviaux, en particulier tout ce qui utilise NSS (solution de contournement dans la FAQ ).
mr.spuratic
20

Lien avec -static . Lorsque vous créez un lien avec -static, l'éditeur de liens incorpore la bibliothèque dans 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 car le programme utilisera sa propre bibliothèque au lieu de celle du système .

Iacchus
la source
55
Souvent, la raison pour laquelle vous souhaitez 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 (cela nécessiterait que vous publiiez tout votre code source), vous devez donc faire attention avec -static.
Malvineous
3
Pendant ce temps, au moins on peut souvent recourir à musl-libc, mais avec les programmes C ++, les choses peuvent devenir plus compliquées, donc spécifier une version de symbole peut être encore nécessaire.
0xC0000022L
16

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

Puisqu'il semble impossible de faire uniquement avec des hacks de version de symboles, allons un peu plus loin et compilons la glibc nous-mêmes.

Cette configuration peut fonctionner et est rapide car elle ne recompile pas l'ensemble de la chaîne d'outils GCC, juste la glibc.

Mais il n'est pas fiable car il utilise des objets d' exécution hôte C tels que crt1.o, crti.oet crtn.ofourni 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 font une configuration précoce sur laquelle la glibc s'appuie, donc je ne serais pas surpris si les choses se bloquaient de manière merveilleuse et des manières incroyablement subtiles.

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

Construisez la glibc et installez 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`

Configuration 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.
     * /programming/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);
}

Compilez et exécutez 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 l'a --sysrootfait échouer avec:

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

alors je l'ai enlevé.

lddLa sortie confirme que les lddbibliothèques et que nous venons de créer sont en fait 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)

La gccsortie de débogage de compilation montre que mes objets d'exécution hôte ont été utilisés, ce qui est mauvais comme mentionné précédemment, mais je ne sais pas comment contourner cela, 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

Modifions maintenant 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)");

Ensuite, recompilez et réinstallez la glibc, puis recompilez et réexécutez notre programme:

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

et nous voyons hackedimprimé quelques fois comme prévu.

Cela confirme en outre que nous avons effectivement utilisé la glibc que nous avons compilée et non l'hôte.

Testé sur Ubuntu 18.04.

Configuration 2: configuration vierge du crosstool-NG

Ceci est une alternative à la configuration 1, et il est le plus correct configuration que j'ai accompli jusqu'à présent: tout est correct pour autant que je peux observer, y compris l'exécution de C des objets tels que crt1.o, crti.oet crtn.o.

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

Le seul inconvénient de cette méthode est que la construction prendra plus de temps. Mais je ne risquerais pas une configuration de production avec rien de moins.

crosstool-NG est un ensemble de scripts qui télécharge et compile tout à partir de la source pour nous, y compris GCC, glibc et binutils.

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

Cette configuration n'est tout simplement pas parfaite car crosstool-NG ne prend pas en charge la construction des exécutables sans -Wldrapeaux supplémentaires , ce qui semble étrange depuis que 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 peux voir, c'est de faire correspondre la version de votre noyau hôte pour utiliser les en-têtes de noyau corrects. Trouvez la version de votre noyau hôte avec:

uname -a

ce qui me montre:

4.15.0-34-generic

donc dans menuconfigje fais:

  • Operating System
    • Version of linux

donc je sélectionne:

4.14.71

qui est la première version égale ou antérieure. Il doit être plus ancien car le noyau est rétrocompatible.

Vous pouvez maintenant construire avec:

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

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

Configuration 2: configurations optionnelles

Le .configque nous avons généré avec ./ct-ng x86_64-unknown-linux-gnua:

CT_GLIBC_V_2_27=y

Pour changer cela, menuconfigfaites:

  • C-library
  • Version of glibc

enregistrez le .config, et poursuivez la construction.

Ou, si vous souhaitez utiliser votre propre source glibc, par exemple pour utiliser glibc à partir du dernier git, procédez comme suit :

  • Paths and misc options
    • Try features marked as EXPERIMENTAL: défini sur true
  • C-library
    • Source of glibc
      • Custom location: dis oui
      • Custom location
        • Custom source location: pointez sur un répertoire contenant votre source glibc

où la glibc a été clonée comme:

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 souhaitez, 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 le crosstool-NG, comme expliqué ci-dessous.

Si vous venez de reconstruire;

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

alors vos modifications apportées à l'emplacement de la source glibc personnalisée sont prises en compte, mais il construit tout à partir de zéro, le rendant inutilisable pour le développement itératif.

Si nous faisons:

./ct-ng list-steps

cela donne un bon aperçu des étapes de 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 GCC, notamment libc_start_filesvient 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' .configoption "Enregistrer les étapes intermédiaires" dans l' option 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 lors de cette étape. C'est-à-dire que vous aurez une libc reconstruite - mais pas de compilateur final construit avec cette libc (et par conséquent, pas de bibliothèques de compilateurs comme libstdc ++ non plus).

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

De plus, à partir de l' libcétape ne semblait pas recopier la source Custom source location, ce qui rendait cette méthode inutilisable.

Bonus: stdlibc ++

Un bonus si vous êtes également intéressé par la bibliothèque standard C ++: Comment éditer et reconstruire la source de la bibliothèque standard C ++ libstdc ++ de GCC?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
musl-libcest une autre option en ce qui concerne le runtime C.
0xC0000022L
0

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

Construisez simplement sur le système avec le plus ancien GLIBC que vous souhaitez toujours prendre en charge.

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 telle vieille distribution directement sur n'importe quel PC. En détail, pour créer 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 Ubuntu LTS virtuel plus ancien 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 des bibliothèques externes en dehors des bibliothèques fondamentales: comme, vous devriez relier dynamiquement des éléments système au niveau du sol comme la glibc, la libGL, la libxcb / X11 / wayland Things, libasound / libpulseaudio, éventuellement GTK + si vous l'utilisez, mais sinon de préférence, reliez statiquement externe libs / envoyez-les si vous le pouvez. Surtout les bibliothèques autonomes comme les chargeurs d'images, les décodeurs multimédias, etc. peuvent causer moins de casse sur d'autres distributions (une casse peut être causée par exemple si elles ne sont présentes que quelque part dans une version majeure différente) si vous les expédiez de manière statique.

Avec cette approche, vous obtenez un binaire compatible avec l'ancien GLIBC sans aucune modification manuelle des symboles, sans faire un binaire entièrement statique (qui peut casser pour des programmes plus complexes car la glibc déteste cela, et qui peut causer des problèmes de licence pour vous), et sans réglage n'importe quelle chaîne d'outils personnalisée, n'importe quelle copie de glibc personnalisée, ou autre.

ET
la source