6. Edition de liens

Contenu de cette section

Entre les deux formats de binaires incompatibles, bibliothèques statiques et dynamiques, on peut comparer l'opération d'édition de lien en fait à un jeu ou l'on se demanderait qu'est-ce qui se passe lorsque je lance le programme ? Cette section n'est pas vraiment simple...

Pour dissiper la confusion qui règne, nous allons nous baser sur ce qui se passe lors d'exécution d'un programme, avec le chargement dynamique. Vous verrez également la description de l'édition de liens dynamiques, mais plus tard. Cette section est dédiée à l'édition de liens qui intervient à la fin de la compilation.

6.1 Bibliothèques partagées contre bibliothèques statiques

La dernière phase de construction d'un programme est de réaliser l'édition de liens, ce qui consiste à assembler tous les morceaux du programme et de chercher ceux qui sont manquants. Bien évidement, beaucoup de programmes réalisent les mêmes opérations comme ouvrir des fichiers par exemple, et ces pièces qui réalisent ce genre d'opérations sont fournies sous la forme de bibliothèques. Sous Linux, ces bibliothèques peuvent être trouvées dans les répertoires /lib et/usr/lib/ entre autres.

Lorsque vous utilisez une bibliothèque statique, l'éditeur de liens cherche le code dont votre programme a besoin et en effectue une copie dans le programme physique généré. Pour les bibliothèques partagées, c'est le contraire : l'éditeur de liens laisse du code qui lors du lancement du programme chargera automatiquement la bibliothèque. Il est évident que ces bibliothèques permettent d'obtenir un exécutable plus petit; elles permettent également d'utiliser moins de mémoire et moins de place disque. Linux effectue par défaut une édition de liens dynamique s'il peut trouver les bibliothèques de ce type sinon, il effectue une édition de liens statique. Si vous obtenez des binaires statiques alors que vous les voulez dynamiques vérifiez que les bibliothèques existent (*.sa pour le format a.out, et *.so pour le format ELF) et que vous possédez les droits suffisants pour y accéder (lecture).

Sous Linux, les bibliothèques statiques ont pour nom libnom.a, alors que les bibliothèques dynamiques sont appelées libnnom.so.x.y.zx.y.z représente le numéro de version. Les bibliothèques dynamiques ont souvent des liens logiques qui pointent dessus, et qui sont très importants. Normalement, les bibliothèques standards sont livrées sous la double forme dynamique et statique.

Vous pouvez savoir de quelles bibliothèques dynamiques un programme a besoin en utilisant la commande ldd (List Dynamic Dependencies)

$ ldd /usr/bin/lynx
        libncurses.so.1 => /usr/lib/libncurses.so.1.9.6
        libc.so.5 => /lib/libc.so.5.2.18

Cela indique sur mon système que l'outil lynx (outil WWW) a besoin des bibliothèques dynamiques libc.so.5 (la bibliothèque C) et de libncurses.so.1 (nécessaire pour le contrôle du terminal). Si un programme ne possède pas de dépendances, ldd indiquera `statically linked' (édition de liens statique).

6.2 A la recherche des fonctions... ou dans quelle bibliothèque se trouve la fonction sin() ?')

nm nomdebibliothèque vous donne tous les symboles référencés dans la bibliothèque. Cela fonctionne que cela soit du code statique ou dynamique. Supposez que vous vouliez savoir où se trouve définie la fonction tcgetattr() :

$ nm libncurses.so.1 |grep tcget
         U tcgetattr

La lettre U vous indique que c'est indéfini (Undefined) --- cela indique que la bibliothèque ncurses l'utilise mais ne la définit pas. Vous pouvez également faire :

$ nm libc.so.5 | grep tcget
00010fe8 T __tcgetattr
00010fe8 W tcgetattr
00068718 T tcgetpgrp

La lettre `W' indique que le symbole est défini mais de telle manière qu'il peut être surchargé par une autre définition de la fonction dans une autre bibliothèque (W pour weak : faible). Une définition normale est marquée par la lettre `T' (comme pour tcgetpgrp).

La réponse à la question située dans le titre est libm.(so|a). Toutes les fonctions définies dans le fichier d'en-tête <math.h> sont implémentées dans la bibliothèque mathématique donc vous devrez effectuer l'édition de liens grâce à -lm.

6.3 Trouver les fichiers

Supposons que vous ayez le message d'erreur suivant de la part de l'éditeur de liens :

ld: Output file requires shared library `libfoo.so.1`

La stratégie de recherche de fichiers de ld ou de ses copains diffère de la version utilisée, mais vous pouvez être sûr que les fichiers situés dans le répertoire /usr/lib seront trouvés. Si vous désirez que des fichiers situés à un endroit différent soient trouvés, il est préférable d'ajouter l'option -L à gcc ou ld.

Si cela ne vous aide pas clairement, vérifiez que vous avez le bon fichier à l'endroit spécifié. Pour un système a.out, effectuer l'édition de liens avec -ltruc implique que ld recherche les bibliothèques libtruc.sa (bibliothèques partagées), et si elle n'existe pas, il recherche libtruc.a (statique). Pour le format ELF, il cherche libtruc.so puis libtruc.a. libtruc.so est généralement un lien symbolique vers libtruc.so.x.

6.4 Compiler votre propre bibliothèque

Numéro de la version

Comme tout programme, les bibliothèques ont tendance à avoir quelques bogues qui sont corrigés au fur et à mesure. De nouvelles fonctionnalités sont ajoutées et qui peuvent changer l'effet de celles qui existent ou bien certaines anciennes peuvent êtres supprimées. Cela peut être un problème pour les programmes qui les utilisent.

Donc, nous introduisons la notion de numéro de version. Nous répertorions les modifications effectuées dans la bibliothèques comme étant soit mineures soit majeures. Cela signifie qu'une modification mineure ne peut pas modifier le fonctionnement d'un programme (en bref, il continue à fonctionner comme avant). Vous pouvez identifier le numéro de la version de la bibliothèque en regardant son nom (en fait c'est un mensonge pour les bibliothèques ELF... mais continuez à faire comme si !) : libtruc.so.1.2 a pour version majeure 1 et mineure 2. Le numéro de version mineur peut être plus ou moins élevé --- la bibliothèque C met un numéro de patch, ce qui produit un nom tel que libc.so.5.2.18, et c'est également courant d'y trouver des lettres ou des blancs soulignés ou tout autre caractère ASCII affichable.

Une des principales différences entre les formats ELF et a.out se trouve dans la manière de construire la bibliothèque partagée. Nous traiterons les bibliothèques partagées en premier car c'est plus simple.

ELF, qu'est-ce que c'est ?

ELF (Executable and Linking Format) est format de binaire initialement conçu et développé par USL (UNIX System Laboratories) et utilisé dans les systèmes Solaris et System R4. En raison de sa facilité d'utilisation par rapport à l'ancien format dit a.out qu'utilisait Linux, les développeurs de GCC et de la bibliothèque C ont décidé l'année dernière de basculer tout le système sous le format ELF. ELF est désormais le format binaire standard sous Linux.

ELF, le retour !

Ce paragraphe provient du groupe '/news-archives/comp.sys.sun.misc'.

ELF (Executable Linking Format) est le " nouveau et plus performant " format de fichier introduit dans SVR4. ELF est beaucoup plus puissant que le sacro-saint format COFF, dans le sens où il est extensible. ELF voit un fichier objet comme une longue liste de sections (plutôt qu'un tableau de taille fixe d'éléments). Ces sections, à la différence de COFF ne se trouvent pas à un endroit constant et ne sont pas dans un ordre particulier, etc. Les utilisateurs peuvent ajouter une nouvelle section à ces fichiers objets s'il désirent y mettre de nouvelles données. ELS possède un format de débogage plus puissant appelé DWARF (Debugging With Attribute Record Format) - par encore entièrement géré par Linux (mais on y travaille !). Une liste chaînée de " DWARF DIEs " (ou Debugging Information Entries - NdT... le lecteur aura sûrement noté le jeu de mot assez noir : dwarf = nain; dies = morts) forment la section .debug dans ELF. Au lieu d'avoir une liste de petits enregistrements d'information de taille fixes, les DWARF DIEs contiennent chacun une longue liste complexe d'attributs et sont écrits sous la forme d'un arbre de données. Les DIEs peuvent contenir une plus grande quantité d'information que la section .debug du format COFF ne le pouvait (un peu comme les graphes d'héritages du C++).
Les fichiers ELF sont accessibles grâce à la bibliothèque d'accès de SVR4 (Solaris 2.0 peut-être ?), qui fournit une interface simple et rapide aux parties les plus complexes d'ELF. Une des aubaines que permet la bibliothèque d'accès ELF est que vous n'avez jamais besoin de connaître les méandres du format ELF. Pour accéder à un fichier Unix, on utilise un Elf *, retourné par un appel à elf_open(). Ensuite, vous effectuez des appels à elf_foobar() pour obtenir les différents composants au lieu d'avoir à triturer le fichier physique sur le disque (chose que beaucoup d'utilisateurs de COFF ont fait...).

Les arguments pour ou contre ELF, et les problèmes liés à la mise à jour d'un système a.out vers un système ELF sont décrits dans le ELF-HOWTO et je ne veux pas effectuer de copier coller ici (NdT: ce HowTo est également traduit en français). Ce HowTo se trouve au même endroit que les autres.

Les bibliothèque partagées ELF

Pour construire libtruc.so comme une bibliothèque dynamique, il suffit de suivre les étapes suivantes :

$ gcc -fPIC -c *.c
$ gcc -shared -Wl,-soname,libtruc.so.1 -o libtruc.so.1.0 *.o
$ ln -s libtruc.so.1.0 libtruc.so.1
$ ln -s libtruc.so.1 libtruc.so
$ LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH ; export LD_LIBRARY_PATH

Cela va générer une bibliothèque partagée appelée libtruc.so.1.0, les liens appropriés pour ld (libtruc.so) et le chargeur dynamique (libtruc.so.1) pour le trouver. Pour tester, nous ajoutons le répertoire actuel à la variable d'environnement LD_LIBRARY_PATH.

Lorsque vous êtes satisfait et que la bibliothèque fonctionne, vous n'avez plus qu'à la déplacer dans le répertoire par exemple, /usr/local/lib, et de recréer les liens appropriés. Le lien de libtruc.so.1 sur libtruc.so.1.0 est enregistré par ldconfig, qui sur bon nombre de systèmes est lancé lors du processus d'amorçage. Le lien libfoo.so doit être mis à jour à la main. Si vous faites attention lors de la mise à jour de la bibliothèque la chose la plus simple à réaliser est de créer le lien libfoo.so -> libfoo.so.1, pour que ldconfig conserve les liens actuels. Si vous ne faites pas cela, vous aurez des problèmes plus tard. Ne me dites pas que l'on ne vous a pas prévenu !

$ /bin/su
# cp libtruc.so.1.0 /usr/local/lib
# /sbin/ldconfig
# ( cd /usr/local/lib ; ln -s libtruc.so.1 libtruc.so )

Les numéros de version, les noms et les liens

Chaque bibliothèque possède un nom propre (soname). Lorsque l'éditeur de liens en trouve un qui correspond à un nom cherché, il enregistre le nom de la bibliothèque dans le code binaire au lieu d'y mettre le nom du fichier de la bibliothèque. Lors de l'exécution, le chargeur dynamique va alors chercher un fichier ayant pour nom le nom propre de la bibliothèque, et pas le nom du fichier de la bibliothèque. Par exemple, une bibliothèque ayant pour nom libtruc.so peut avoir comme nom propre libbar.so, et tous les programmes liés avec vont alors chercher libbar.so lors de leur exécution.

Cela semble être une nuance un peu pointilleuse mais c'est la clef de la compréhension de la coexistence de plusieurs versions différentes de la même bibliothèque sur le même système. On a pour habitude sous Linux d'appeler une bibliothèque libtruc.so.1.2 par exemple, et de lui donner comme nom propre libtruc.so.1. Si cette bibliothèque est rajoutée dans un répertoire standard (par exemple dans /usr/lib), le programme ldconfig va créer un lien symbolique entre libtruc.so.1 -> libtruc.so.1.2 pour que l'image appropriée soit trouvée lors de l'exécution. Vous aurez également besoin d'un lien symbolique libtruc.so -> libtruc.so.1 pour que ld trouve le nom propre lors de l'édition de liens.

Donc, lorsque vous corrigez des erreurs dans la bibliothèque ou bien lorsque vous ajoutez de nouvelles fonctions (en fait, pour toute modification qui n'affecte pas l'exécution des programmes déjà existants), vous reconstruisez la bibliothèque, conservez le nom propre tel qu'il était et changez le nom du fichier. Lorsque vous effectuez des modifications que peuvent modifier le déroulement des programmes existants, vous pouvez tout simplement incrémenter le nombre situé dans le nom propre --- dans ce cas, appelez la nouvelle version de la bibliothèque libtruc.so.2.0, et donnez-lui comme nom propre libtruc.so.2. Maintenant, faites pointer le lien de libfoo.so vers la nouvelle version et tout est bien dans le meilleur des mondes !

Il est utile de remarquer que vous n'êtes pas obligé de nommer les bibliothèques de cette manière, mais c'est une bonne convention. Elf vous donne une certaine liberté pour nommer des bibliothèques tant et si bien que cela peut perturber certains utilisateurs, mais cela ne veut pas dire que vous êtes obligé de le faire.

Résumé : supposons que choisissiez d'adopter la méthode traditionnelle avec les mises à jour majeures qui peuvent ne pas être compatibles avec les versions précédentes et les mises à jour mineures qui ne posent pas ce problème. Il suffit de créer la bibliothèque de cette manière :

gcc -shared -Wl,-soname,libtruc.so.majeur -o libtruc.so.majeur.mineur
et tout devrait être parfait !

a.out. Le bon vieux format

La facilité de construire des bibliothèque partagées est la raison principale de passer à ELF. Ceci dit, il est toujours possible de créer des bibliothèques dynamiques au format a.out. Récupérez le fichier archive ftp://tsx-11.mit.edu/pub/linux/packages/GCC/src/tools-2.17.tar.gz et lisez les 20 pages de documentation que vous trouverez dedans après l'avoir désarchivé. Je n'aime pas avoir l'air d'être aussi partisan, mais il est clair que je n'ai jamais aimé ce format :-).

ZMAGIC contre QMAGIC

QMAGIC est le format des exécutables qui ressemble un peu aux vieux binaires a.out (également connu comme ZMAGIC), mais qui laisse la première page libre. Cela permet plus facilement de récupérer les adresses non affectées (comme NULL) dans l'intervalle 0-4096 (NdT : Linux utilise des pages de 4Ko).

Les éditeurs de liens désuets ne gèrent que le format ZMAGIC, ceux un peu moins rustiques gèrent les deux, et les plus récents uniquement le QMAGIC. Cela importe peu car le noyau gère les deux types.

La commande file est capable d'identifier si un programme est de type QMAGIC.

Gestion des fichiers

Une bibliothèque dynamique a.out (DLL) est composée de deux fichiers et d'un lien symbolique. Supposons que l'on utilise la bibliothèque truc, les fichiers seraient les suivants : libtruc.sa et libtruc.so.1.2; et le lien symbolique aurait pour nom libtruc.so.1 et pointerait sur le dernier des fichiers. Mais à quoi servent-ils ?

Lors de la compilation, ld cherche libtruc.sa. C'est le fichier de description de la bibliothèque : il contient toutes les données exportées et les pointeurs vers les fonctions nécessaires pour l'édition de liens.

Lors de l'exécution, le chargeur dynamique cherche libtruc.so.1. C'est un lien symbolique plutôt qu'un réel fichier pour que les bibliothèques puissent être mise à jour sans avoir à casser les applications qui utilisent la bibliothèque. Après la mise à jour, disons que l'on est passé à la version libfoo.so.1.3, le lancement de ldconfig va positionner le lien. Comme cette opération est atomique, aucune application fonctionnant n'aura de problème.

Les bibliothèques DLL (Je sais que c'est une tautologie... mais pardon !) semblent être très souvent plus importantes que leur équivalent statique. En fait, c'est qu'elles réservent de la place pour les extensions ultérieures sous la simple forme de trous qui sont fait de telle manière qu'ils n'occupent pas de place disque (NdT : un peu comme les fichiers core). Toutefois, un simple appel à cp ou à makehole les remplira... Vous pouvez effectuer une opération de strip après la construction de la bibliothèque, comme les adresses sont à des endroits fixes. Ne faites pas la même opération avec les bibliothèques ELF !

" libc-lite " ?

Une " libc-lite " (contraction de libc et little) est une version épurée et réduite de la bibliothèque libc construite de telle manière qu'elle puisse tenir sur une disquette avec un certain nombre d'outil Unix. Elle n'inclut pas curses, dbm, termcap, ... Si votre /lib/libc.so.4 est liée avec une bibliothèque de ce genre il est très fortement conseillé de la remplacer avec une version complète.

Edition de liens : problème courants

Envoyez-les moi !

Des programmes statiques lorsque vous les voulez partagés

Vérifiez que vous avez les bons liens pour que ld puisse trouver les bibliothèques partagées. Pour ELF cela veut dire que libtruc.so est un lien symbolique sur son image, pour a.out un fichier libtruc.sa. Beaucoup de personnes ont eu ce problème après être passés des outils ELF 2.5 à 2.6 (binutils) --- la dernière version effectue une recherche plus intelligente pour les bibliothèques dynamiques et donc ils n'avaient pas créé tous les liens symboliques nécessaires. Cette caractéristique avait été supprimée pour des raisons de compatibilité avec d'autres architectures et parce qu'assez souvent cela ne marchait pas bien. En bref, cela posait plus de problèmes qu'autre chose.

Le programme `mkimage' n'arrive pas à trouver libgcc

Comme libc.so.4.5.x et suivantes, libgcc n'est pas une bibliothèque partagée. Vous devez remplacer les `-lgcc' sur la ligne de commande par `gcc -print-libgcc-file-name` (entre quotes)

Egalement, détruisez tous les fichiers situés dans /usr/lib/libgcc*. C'est important.

Le message __NEEDS_SHRLIB_libc_4 multiply defined

Sont une conséquence du même problème.

Le message ``Assertion failure'' apparaît lorsque vous reconstruisez une DLL

Ce message énigmatique signifie qu'un élément de votre table jump a dépassé la table car trop peu de place était réservée dans le fichier jump.vars file. Vous pouvez trouver le(s) coupable(s) en lançant la commande getsize fournie dans le paquetage tools-2.17.tar.gz. La seule solution est de passer à une nouvelle version majeure, même si elle sera incompatible avec les précédentes.

ld: output file needs shared library libc.so.4

Cela arrive lorsque vous effectuez l'édition de liens avec des bibliothèques différentes de la libc (comme les bibliothèques X) et que vous utilisez l'option -g sans utiliser l'option -static.

Les fichiers .sa pour les bibliothèques dynamiques ont un symbole non résolu _NEEDS_SHRLIB_libc_4 qui est défini dans libc.sa. Or, lorsque vous utilisez -g vous faites l'édition de liens avec libg.a ou libc.a et donc ce symbole n'est jamais défini.

Donc, pour résoudre le problème, ajoutez l'option -static lorsque vous compilez avec l'option -g, ou n'utilisez pas -g lors de l'édition de liens !


Chapitre suivant, Chapitre Précédent

Table des matières de ce chapitre, Table des matières générale

Début du document, Début de ce chapitre