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 :
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 :
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 ?
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.