Outils pour utilisateurs

Outils du site


jeux:elf_binary

ELF

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)…

Comment ça marche ?

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 :

  1. les répertoires précisés à la compilation avec l'option –rpath-link
  2. les répertoires précisés à la compilation avec l'option –rpath (don't ask)
  3. si les deux précédentes options n'ont pas été utilisées à la compilation, le contenu de la variable d'environnement LD_RUN_PATH
  4. le contenu de la variable LD_LIBRARY_PATH
  5. les répertoires par défaut /lib et /usr/lib
  6. le contenu du fichier /etc/ld.so.conf

Les utilitaires à connaître

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

Pour en venir où ?

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…

pack_libs.sh
#! /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…

jeux/elf_binary.txt · Dernière modification: 2015/12/04 11:40 par cyriac