web-dev-qa-db-fra.com

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 2.11 de la glibc. Lorsque j'exécute ce fichier binaire sur un autre ordinateur doté d'une ancienne glibc, la commande échoue en indiquant 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 un lien avec une version de symbole spécifique?

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

96
falstaff

Vous avez raison de dire que la glibc utilise la version 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 ici et est une extension du schéma de gestion des versions des symboles de Sun décrite ici .

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

Vous pouvez également créer votre fichier binaire dans un environnement de construction chroot ou utiliser une glibc - nouvelle => glibc - ancienne compilateur croisé.

Selon le http://www.trevorpounds.com article de blog Lien vers d'anciens symboles versionnés (glibc) = , il est possible de lier n'importe quel symbole à un symbole plus ancien, à condition qu'il soit valide en utilisant le même .symver pseudo-op utilisé pour définir les symboles versionnés en premier lieu. L'exemple suivant est extrait de article de blog .

L’exemple suivant utilise le realpath de la glibc, mais veille à ce qu’il soit lié à une version 2.2.5 plus ancienne.

#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;
}
61
jschmier

Lien avec - statique. Lorsque vous établissez un lien avec - static, l'éditeur de liens incorpore la bibliothèque dans l'exécutable. L'exécutable sera donc plus volumineux, mais il peut être exécuté sur un système doté d'une ancienne version de glibc car le programme utilisera sa propre version. bibliothèque au lieu de celle du système.

18
Iacchus

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

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

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

Mais il n’est pas fiable car il utilise des objets d’exécution de l’hôte C tels que crt1.o, crti.o Et crtn.o Fournis 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 sont installés de manière précoce, glibc s'appuie sur, alors je ne le ferais pas soyez surpris si les choses se brisent de manière merveilleuse et étonnamment subtile.

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

Construisez 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`

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);
}

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 --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é.

La sortie ldd confirme que les bibliothèques ldd et celles 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)

La sortie de débogage de la compilation gcc montre que mes objets d’exécution de l’hôte ont été utilisés. 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)");

Recompilez et réinstallez glibc, puis recompilez et exécutez à nouveau notre programme:

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

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

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

Testé sur Ubuntu 18.04.

Configuration 2: configuration vierge de 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, dans la mesure où je peux l'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 entièrement dédiée qui utilise la glibc que nous voulons.

Le seul inconvénient de cette méthode est que la compilation 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 depuis la source, y compris GCC, glibc et binutils.

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

Cette configuration n’est pas seulement parfaite parce que crosstool-NG ne prend pas en charge la création d’exécutables sans indicateurs supplémentaires -Wl , ce qui est étrange depuis que nous avons créé GCC. Mais tout semble fonctionner, ce n’est donc 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 puisse voir consiste à faire correspondre la version de votre noyau hôte à l'utilisation des 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 menuconfig je fais:

  • Operating System
    • Version of linux

alors je sélectionne:

4.14.71

qui est la première version égale ou plus ancienne. Il doit être plus ancien puisque le noyau est rétro-compatible.

Maintenant, vous pouvez 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

Le .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, faites:

  • C-library
  • Version of glibc

enregistrez le .config et continuez avec la construction.

Ou, si vous souhaitez utiliser votre propre source glibc, par exemple. Pour utiliser la glibc du dernier git, continuez comme ceci :

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

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

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

Setup 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 les objets d'exécution corrects ont maintenant é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 vous venez de reconstruire;

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

ensuite, vos modifications apportées à l'emplacement source glibc personnalisé sont prises en compte, mais tout est construit à partir de zéro, ce qui le rend inutilisable pour un 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 de GCC, notamment libc_start_files vient avant cc_core_pass_2, qui est probablement l'étape la plus chère avec cc_core_pass_1.

Afin de ne construire qu'une é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 alors vous pouvez essayer:

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

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

Notez toutefois 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, aucune bibliothèque de compilateur comme libstdc ++ non plus).

et, fondamentalement, rend la reconstruction trop lente pour être réalisable pour le développement, et je ne vois pas comment résoudre ce problème sans appliquer de correctif à Crosstool-NG.

De plus, à partir de l'étape libc, il semble que le code source ne soit plus copié de Custom source location, Rendant ainsi 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 GCC libstdc ++ C++?

gcc -m32 -Wl,--hash-style=both 9545.c -o 9545
0
chris_cx