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.
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.z
où x.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).
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
.
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
.
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 (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.
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.
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 )
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 !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 :-).
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.
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 !
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.
Envoyez-les moi !
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.
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.
__NEEDS_SHRLIB_libc_4 multiply defined
Sont une conséquence du même problème.
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 !