Il s'agit du format des binaires sous Linux. La preuve (sur une architecture 64 bits) :
file /bin/ls /bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0xfa1974ebeba3ef537757f31e40b38095755af9e0, stripped
Je n'ai pas la prétention de parfaitement comprendre ce format, mais j'ai appris 2-3 petites choses au fil du temps.
D'abord, ce format de binaire permet d'avoir des bibliothèques dynamiques (un peu l'équivalent des .dll sous Windows) : un bout de code qui est partagé par plusieurs applications, ce qui permet de consommer moins de place, moins de mémoire et de profiter du travail de codeurs compétents dans plusieurs projets.
L'inconvénient principal est que lorsque vous compilez un programme sur une machine, pour tourner correctement sur une autre il faut que toutes les bibliothèques soient présentes dans une version (majeure) identique.
En gros, si votre programme utilise ma_super_lib_0.1.2, il faut que vos utilisateurs potentiels possèdent également ma_super_lib_0.1.2 si il veulent utiliser votre appli.
Il y a aussi parfois un soucis d'appels dynamiques récursifs (une bibliothèque en appelle une autre, etc).
Bref, pour démêler le tout, le plus simple serait encore de toujours créer des programmes statiques et comme ça, le problème serait réglé.
Mais ce n'est pas toujours possible (vous utilisez une bibliothèque qui n'est pas prévue pour être compilée en statique)…
En gros, les binaires savent où chercher les bibliothèques dynamiques (/usr/lib
par exemple) et tout va pour le mieux dans le meilleur des mondes.
Dans le détail (mais pas trop), à la compilation, le linker fournit les informations nécessaire, et à l'utilisation, ld
est toujours là pour fournir le même type de renseignements.
Dans la pratique, vos binaires vont chercher les bibliothèques dynamiques dans tous les répertoires contenus dans le fichier /etc/ld.so.conf
:
cat /etc/ld.so.conf /usr/lib/libfakeroot /usr/lib32 /usr/lib/perl5/core_perl/CORE
ou dans la variable LD_LIBRARY_PATH
.
Il est donc possible pour un utilisateur lambda (comprendre : pas root) de configurer tout ça comme suit :
LD_LIBRARY_PATH=./libs32:${LD_LIBRARY_PATH} mon_programme
Pour votre information, il est aussi possible lors de la compilation de préciser un chemin spécifique pour la recherche des bibliothèques dynamiques et, du coup, de fournir vos propres bibliothèques dans la version que vous désirez (ceci aura précédence sur ld.so.conf
ou' LD_LIBRARY_PATH
). Pour cela, voir les options –rpath
et –rpath-link
de ld
.
Voici pour finir l'ordre dans lequel sont cherchées les bibliothèques dynamiques :
–rpath-link
–rpath
(don't ask)LD_RUN_PATH
LD_LIBRARY_PATH
/lib
et /usr/lib
/etc/ld.so.conf
Pour savoir ce dont à besoin un programme pour tourner sous Linux, utiliser readelf
, en choisissant de lire les informations “dynamiques” du programme et en cherchant ce qui est “NEEDED”:
readelf -d uplink.bin.x86_64 | grep NEEDED 0x0000000000000001 (NEEDED) Shared library: [libpthread.so.0] 0x0000000000000001 (NEEDED) Shared library: [libGL.so.1] 0x0000000000000001 (NEEDED) Shared library: [libGLU.so.1] 0x0000000000000001 (NEEDED) Shared library: [libSDL-1.2.so.0] 0x0000000000000001 (NEEDED) Shared library: [libSDL_mixer-1.2.so.0] 0x0000000000000001 (NEEDED) Shared library: [libmikmod.so.2] 0x0000000000000001 (NEEDED) Shared library: [libfreetype.so.6] 0x0000000000000001 (NEEDED) Shared library: [libtiff.so.3] 0x0000000000000001 (NEEDED) Shared library: [libjpeg.so.62] 0x0000000000000001 (NEEDED) Shared library: [libz.so.1] 0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6] 0x0000000000000001 (NEEDED) Shared library: [libm.so.6] 0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
Pour savoir si le programme va utiliser des chemins qui lui sont propres, vous pouvez utiliser la même commande, en cherchant les “RPATH” :
readelf -d uplink.bin.x86_64 | grep RPATH 0x000000000000000f (RPATH) Library rpath: [$ORIGIN/lib64]
Enfin pour vraiment savoir ce dont vous avez besoin pour faire tourner correctement un programme, le plus simple est d'utiliser ldd
(vous pouvez voir que certaines bibliothèques sont trouvées dans le répertoire local) :
ldd uplink.bin.x86_64 linux-vdso.so.1 => (0x00007fff9b55a000) libpthread.so.0 => /lib/libpthread.so.0 (0x00007fdff7047000) libGL.so.1 => /usr/lib/libGL.so.1 (0x00007fdff6de9000) libGLU.so.1 => /usr/lib/libGLU.so.1 (0x00007fdff6b7b000) libSDL-1.2.so.0 => /home/cyriac/Games/Uplink/lib64/libSDL-1.2.so.0 (0x00007fdff68e3000) libSDL_mixer-1.2.so.0 => /home/cyriac/Games/Uplink/lib64/libSDL_mixer-1.2.so.0 (0x00007fdff66d4000) libmikmod.so.2 => /home/cyriac/Games/Uplink/lib64/libmikmod.so.2 (0x00007fdff648b000) libfreetype.so.6 => /usr/lib/libfreetype.so.6 (0x00007fdff61ec000) libtiff.so.3 => /home/cyriac/Games/Uplink/lib64/libtiff.so.3 (0x00007fdff5f92000) libjpeg.so.62 => /home/cyriac/Games/Uplink/lib64/libjpeg.so.62 (0x00007fdff5d71000) libz.so.1 => /usr/lib/libz.so.1 (0x00007fdff5b5b000) libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007fdff5857000) libm.so.6 => /lib/libm.so.6 (0x00007fdff5562000) libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007fdff534d000) libc.so.6 => /lib/libc.so.6 (0x00007fdff4fac000) /lib64/ld-linux-x86-64.so.2 (0x00007fdff7263000) libglapi.so.0 => /usr/lib/libglapi.so.0 (0x00007fdff4d87000) libXext.so.6 => /usr/lib/libXext.so.6 (0x00007fdff4b74000) libXdamage.so.1 => /usr/lib/libXdamage.so.1 (0x00007fdff4971000) libXfixes.so.3 => /usr/lib/libXfixes.so.3 (0x00007fdff476a000) libX11-xcb.so.1 => /usr/lib/libX11-xcb.so.1 (0x00007fdff4568000) libX11.so.6 => /usr/lib/libX11.so.6 (0x00007fdff422e000) libxcb-glx.so.0 => /usr/lib/libxcb-glx.so.0 (0x00007fdff4018000) libxcb.so.1 => /usr/lib/libxcb.so.1 (0x00007fdff3dfa000) libXxf86vm.so.1 => /usr/lib/libXxf86vm.so.1 (0x00007fdff3bf4000) libdrm.so.2 => /usr/lib/libdrm.so.2 (0x00007fdff39e9000) libdl.so.2 => /lib/libdl.so.2 (0x00007fdff37e5000) libbz2.so.1.0 => /usr/lib/libbz2.so.1.0 (0x00007fdff35d5000) libXau.so.6 => /usr/lib/libXau.so.6 (0x00007fdff33d1000) libXdmcp.so.6 => /usr/lib/libXdmcp.so.6 (0x00007fdff31cb000) librt.so.1 => /lib/librt.so.1 (0x00007fdff2fc3000)
Logiquement, vous devriez avoir toutes les information
A quoi servent ces informations ?
Et bien imaginez que vous réussissiez à faire tourner un jeu sous Linux. Qui vous dit que dans 5 ou 6 ans, les bibliothèques seront toujours disponibles pour que vous les utilisiez, et surtout, qu'elles soient dans la bonne version ?
L'idéal serait de packager le tout pour que cela devienne “portable”.
Je me suis penché sur ce “problème”, et voici ma solution maison (qui n'est pas idéale car si nous passons un jour à une architecture 96 bits, rien ne dit que ce sera toujours compatible…). J'ai créé un script qui recherche les bibliothèques utilisées par un programme et copie la bonne version dans un répertoire (en suivant les liens symboliques – je ne m'étendrais pas là-dessus), histoire d'avoir tout ça sous le coude pour un jour prochain.
Ensuite, à vous d'exploiter correctement LD_LIBRARY_PATH
pour que votre binaire fonctionne encore dans dix ans…
#! /bin/bash function syntax () { cat <<__EoF__ Syntax: $0 binary_file libdir libdir may or may not exist. __EoF__ exit 1 } declare -r RED="\e[31;1m" declare -r GREEN="\e[32;1m" declare -r YELLOW="\e[33;1m" declare -r NORM="\e[0m" declare -i COUNT=0 BAD=0 ERRORS=0 declare DIR BAD LIST_BAD OLD_IFS IFS if [ "$#" -ne 2 ]; then syntax fi file "$1" | grep ELF 2>&1 > /dev/null if [ $? != 0 ]; then echo "$1 is not an ELF binary, can't do anything about it..." exit 2 else if [ ! -d "$2" ]; then echo "Creating $2" mkdir -p "$2" else echo "$2 exists" fi DIR="$2" OLD_IFS="$IFS" IFS=$'\n' while read lib; do # Bash substitution will get rid of trailing spaces cp `readlink -f "${lib%% }"` "$DIR/`basename $lib`" if [ $? -eq 0 ];then ((COUNT++)) else ((ERROR++)) fi done < <( ldd "$1" | cut -d' ' -f3- | cut -d'(' -f1 | grep "^/" ) # Old version couldn't handle spaces in dir name #done < <( ldd "$1" | awk '{ print $3 }' | grep "^/" ) IFS="$OLDIFS" LIST_BAD=$(ldd "$1" | grep -i "not found" | cut -d' ' -f1) BAD=$(echo -n $LIST_BAD | tr ' ' '\n' | wc -l) fi echo echo -e "${GREEN}$COUNT libs${NORM} copied" [[ $ERROR -ne 0 ]] && echo -e "${YELLOW}$ERROR errors${NORM}" [[ $BAD -ne 0 ]] && echo -e "${RED}$((BAD+1)) libs${NORM} not found:\n $LIST_BAD" cat <<__EoF__ In order to use this, you should create a script like this: #! /bin/bash export LD_LIBRARY_PATH="./$DIR":\${LD_LIBRARY_PATH} exec "$1" #EoF __EoF__ exit 0 # EoF
Vous pouvez voir ici que 2 bibliothèques sont manquantes à l'appel. Si le jeu fonctionne, elles doivent se trouver dans un répertoire local. A vous de faire un find
ou autre pour les retrouver…