5.- L'édition
de liens sous Linux
Tous les programmes sont constitués de modules
liés pour former un exécutable. L'éditeur de liens
sous Linux est l'utilitaire ld(1).
ld(1) peut être invoqué avec de nombreuses options
qui modifient son fonctionnement ; nous nous restreindrons ici aux options
qui concernent les bibliothèques d'une manière générale.
ld(1) n'est pas appelé directement par l'utilisateur, mais
par le compilateur gcc(1). Une connaissance élémentaire
de son modus operandis nous aidera à mieux comprendre l'utilisation
des bibliothèques sous Linux.
ld(1) maintient une liste d'objets qui doivent être liés
au programme. Ces objets peuvent être donnés et appelés
dans n'importe quel ordre (*) tant que la convention précédente
est repectée, c'est à dire que le nom des bibliothèques
statiques se termine par .a et celui des bibliothèques
partagées par .so (pas .so.xxx.yyy). Bien entendu,
le nom des fichiers objets simples se termine toujours par .o.
(*) Ce n'est pas tout à fait exact. ld(1)inclue
uniquement les modules qui résolvent les références
au moment de l'inclusion, donc il pourrait demeurer une référence
produite par un module inclu ultérieurement qui, puisqu'il n'apparaît
pas encore lors de l'inclusion, peut rendre significatif l'ordre d'inclusion
des bibliothèques.
D'autre part, ld(1) autorise l'ajout de bibliothèques
standards grâce aux options -l et -L.
Mais qu'entendons-nous par "bibliothèque standard", quelle est
la différence ? Aucune. Simplement, ld(1) recherche les
bibliothèques standard dans des endroits particuliers, alors quele
code objet est recherché à partir du nom de fichier.
Les bibliothèques sont recherchées par défaut dans
les répertoires /lib et /usr/lib (j'ai néanmoins
entendu dire que selon la version ou l'implémentation de ld(1),
la recherche pouvait s'effectuer dans des répertoires supplémentaires).
L'option -L permet d'ajouter des répertoires au chemin
de recherche par défaut. On l'utilise en ajoutant -L repertoire
pour chaque repertoire concerné. Les bibliothèques
standard sont indiquées par l'option -l Nom (ou Nom
précise la bibliothèque à charger) et ld(1)cherchera,
dans l'ordre, dans les répertoires correspondants, le fichier libNom.so.
Si ce fichier n'existe pas, ld(1)essaiera libNom.a, c'est
à dire la version statique.
Selon que ld(1) trouve libNom.so ou libNom.a,
l'édition des liens est effectuée en tant que bibliothèque
partagée ou statique.
6.- L'édition
dynamique des liens et le chargement des bibliothèques partagées
L'édition dynamique des liens est effectuée
au chargement de l'exécutable par un module spécial (d'ailleurs
ce module est lui-même une bibliothèque partagée),
appelé /lib/ld-linux.so.
En réalité, il y a deux modules responsables du chargement
des bibliothèques partagées : /lib/ld.so (pour les
bibliothèques à l'ancien format a.out), et /lib/ld-linux.so
(pour les bibliothèques au nouveau format ELF).
Ces modules sont particuliers, car ils doivent être chargés
à chaque fois qu'un programme utilisant une bibliothèque
partagée est lancé. Leurs noms sont pré-définis
(ils ne doivent donc pas être renommés ou déplacés
du répertoire /lib). Si quelqu'un modifiait le nom /lib/ld-linux.so,
le système deviendrait incapable d'exécuter tous les programmes
faisant appel à des bibliothèques partagées car ce
module prend en charge la résolution de toutes les références
indéterminées au moment de l'exécution.
Ce module est secondé par le fichier /etc/ld.so.cache,
qui indique pour chaque bibliothèque le fichier exécutable
le plus approprié. Nous reviendrons à ce problème
plus loin.
7.- soname. Versions
de bibliothèques partagées. Compatibilité.
Nous atteignons ici le point le plus délicat
concernant les bibliothèques partagées.
Qui n'a jamais recu un message du type "library libX11.so.3 notfound"
? Il est parfaitement possible de disposer de la bibliothèque libX11.so.6,
mais néanmoins de ne rien pouvoir faire. Comment se fait-il que
ld.so(8) reconnaisse comme interchangeables les bibliothèques
libfoo.so.45.0.1 et libtoto.so.45.22.8 et ne reconnaisse
pas libfoo.46.5.7 ?
Sous Linux (et tous les systèmes d'exploitation implémentant
le format exécutable ELF), les bibliothèques sont identifiées
par une chaîne de caractères qui les distinguent les unes
des autres : le soname.
Le soname est incorporé dans la bibliothèque elle-même
et la chaîne est déterminée à l'édition
des liens des objets formant la bibliothèque. A la création
de la bibliothèque partagée, il faut utiliser l'option -soname
pour donner une valeur à la chaîne de caractères.
La chaîne de caractères est utilisée au chargement
pour identifier la bibliothèque partagée à charger
et l'exécutable correspondant. Le déroulement est le suivant:
ld-linux.so détecte que le programme nécessite une
bibliothèque et détermine son soname. Intervient ensuite/etc/ld.so.cache
qui fournit le nom de fichier. Finalement, il compare le soname
au nom contenu dans la bibliothèque, et s'ils sont identiques c'est
fini! Sinon, la recherche continue. En cas d'échec, une erreur est
renvoyée.
Le soname permet d'assurer que la bibliothèque correspond
bien car ld-linux.so vérifie sa concordance avec le fichier
utilisé. En cas de non concordance, nous obtenons le fameux message"libXXX.so.Y
not found". Ce que ld-linux.so recherche est le soname
et c'est à lui que fait référence le message d'erreur.
Cela porte à confusion lorsqu'on change le nom de fichier dela
bibliothèque, et que l'erreur demeure. Mais ce serait une mauvaise
idée de modifier le soname, car il existe une convention
dans la communauté Linux pour l'assigner.
Par convention, le soname d'une bibliothèque doit identifier
une bibliothèque et son INTERFACE. Si des modifications sont apportées
qui n'affectent que le fonctionnement interne de la bibliothèque,
et donc que l'interface entière demeure intacte (nombre de fonctions,
variables, paramètres et valeur renvoyée pour les fonctions),
alors les deux bibliothèques sont interchangeables, et les modifications
apportées sont qualifiées de mineures (les bibliothèques
sont compatibles et peuvent être utilisées indifféremment).
Généralement dans ce cas, le numéro mineur de version
de bibliothèque (qui n'apparaît pas dans soname) est modifié,
et une bibliothèque peut à priori etre remplacée par
l'autre sans problème majeur.
Par contre, si l'on ajoute ou supprime des fonctions, ou d'une manière
plus générale si on MODIFIE L'INTERFACE de la bibliothèque,
alors il est impossible de maintenir la compatibilité entre les
deux versions (par exemple, l'évolution de X11R5 à X11R6
se traduit par le passage de libX11.so.3 à libX11.so.6, qui définit
de nouvelle fonctions et donc modifie l'interface). Le changement de X11R6-v3.1.2
à X11R6-v3.1.3 n'apportera probablement pas de modification de l'interface,
et la bibliothèque portera le même soname -bien que
pour éviter d'écraser l'ancienne bibliothèque, la
nouvelle portera un autre nom (pour cette raison, le numéro de version
complet apparaît dans le nom de la bibliothèque, mais seul
le numéro majeur est utilisé dans le soname).
8.- ldconfig(8)
Comme indiqué plus haut, /etc/ld.so.cache
permet à ld-linux.so de retrouver les bibliothèques.
Ce fichier au format binaire pour plus d'efficacité est créé
par ldconfig(8).
Pour toutes les bibliothèques partagées trouvées
dans les répertoires spécifiés /etc/ld.so.conf,ldconfig(8)
génère un lien symbolique appelé par le soname
de la librairie. Il réalise cela de sorte que lorsque ld-linux.so
demande un nom de fichier, il sélectionne dans la liste des répertoires
un fichier avec le soname recherché. Ainsi, il est inutile
d'exécuter ldconfig(8) à chaque ajout d'une bibliothèque
partagée ; on ne le lance que lorsqu'un répertoire est ajouté
à la liste.
9.- Créer une
bibliothèque partagée
Avant de créer une bibliothèque dynamique,
il faut s'interroger sur l'utilité réelle. Il faut savoir
que l'utilisation de bibliothèques dynamiques génère
une surcharge du système pour différentes raisons :
Le chargement du programme s'effectue en plusieurs étapes ;
une première pour le chargement du programme principal, et une autre
pour chaque bibliothèque dynamique que le programme utilise.
Les bibliothèques dynamiques doivent contenir du code relogeable,
puisque l'adresse réellement affectée à l'intérieur
de l'espace des adresses virtuelles n'est connue qu'au chargement. Le compilateur
est obligé de réserver un registre pour mémoriser
la position de la bibliothèque, ce qui diminue d'autant le nombre
de registres disponibles pour l'exécution du code. C'est un moindre
mal, et la surcharge correspondante est estimée à moins de
5% dans la plupart des cas.
Une bibliothèque dynamique est adaptée lorsqu'elle est utilisée
la plupart du temps par un ou plusieurs programmes (ce qui évite
le rechargement de la bibliothèque à la fin du programme
l'ayant appelée, puisqu'elle demeure en mémoire tant qu'un
programme l'utilise).
La bibliothèque partagée est totalement chargée
en mémoire (et non pas uniquement les modules nécessaires),ce
qui implique qu'elle doit être utilisée dans sa totalité.
Lepire exemple serait une bibliothèque partagée dont seule
une fonction serait utilisée, et dont 90% du code ne servirait presque
jamais.
Un bon exemple de bibliothèque dynamique est la bibliotheque
C standard (utilisée par tous les programmes écrits en C).
En moyenne, toutes les fonctions sont utilisées ici ou là.
Dans une bibliothèque statique, il est inutile d'inclure les
fonctions rarement utilisées ; il suffit de placer ces fonctions
dans leur propre module, et elles ne seront pas liées dans les programmes
qui ne les référencent pas.
9.1.- Compilation du source
La compilation du code source est effectuée comme d'habitude, excepté
l'option '-f PIC' (Position Independant Code, code relogeable)
pour produire un code objet qui pourra être chargé à
différentes positions dans l'espace des adresses virtuelles du programme.
Cette étape est fondamentale car pour un programme lié
statiquement, la position des objets des bibliothèques est déterminée
à l'édition des liens, c'est à dire une fois pour
toutes. L'ancien format d'exécutable a.out ne permettait
pas de réaliser ce traitement, en conséquence chaque bibliothèque
partagée etait située à une adresse fixe de l'espace
des adresses virtuelles. Il en résultait des conflits quand un programme
essayait d'utiliser deux bibliothèques partagées et les chargeait
à des régions non disjointes de la mémoire virtuelle.
Cela obligeait à maintenir une liste indiquant pour chaque bibliothèque
dynamique l'étendue des adresses occupées afin que personne
d'autre ne l'utilise aussi.
Comme nous l'avons déjà mentionné, l'inscription
d'une bibliothèque dynamique dans une liste officielle est inutile
car la position réellement affectée est donnée dynamiquement
au chargement. D'où la nécessité d'un code relogeable.
9.2.- Edition des liens
Après compilation de tous les objets, il faut les lier en invoquant
une option permettant de produire un objet susceptible d'être chargé
dynamiquement.
gcc -shared -o libName.so.xxx.yyy.zzz -Wl, -soname, libName.so.xxx
Comme le lecteur peut le constater , l'édition de liens est classique,
excepté l'ajout d'une série d'options qui justement conduisent
à produire une bibliothèque partagée. Détaillons
les maintenant :
-shared,
cette option indique à l'éditeur de liens qu'il doit
produire une bibliothèque partagée, et donc qu'il y aura
un type particulier d'exécutable dans le fichier de sortie correspondant
à la bibliothèque.
-o libName.so.xxx.yyy.zzz,
est le nom du fichier de sortie. La convention de nom n'est pas obligatoire,
mais est néanmoins conseillée en particulier si cette librairie
est destinée à devenir standard.
-Wl, -soname, libName.so.xxx,
l'option -Wl indique au compilateur que les options suivantes (séparées
par des virgules) sont destinées à l'éditeur de liens.
C'est le mécanisme utilisé par gcc(1) pour passer
des options à ld(1). Ci-dessus nous passons les options
suivantes à l'éditeur de lien :
-soname libName.so.xxx.
Cette option fixe le soname de la bibliothèque, de facon
qu'il puisse être appelé par les programmes la requérant.
9.3.- Installation de la bibliothèque
Nous disposons désormais du code exécutable de la bibliothèque.
Il faut maintenant l'installer à un endroit approprié afin
de pouvoir l'utiliser.
Pour compiler un programme utilisant notre nouvelle librairie, la ligne
de commande serait :
gcc -o program libName.so.xxx.yyy.zzz
ou plus simplement, si la bibliothèque a été installée
à la bonne place (/usr/lib):
gcc -o program -lName
(si la librairie avait été installée dans /usr/local/lib,
il aurait suffit d'ajouter l'option -L/usr/local/lib). Les étapes
suivantes permettent d'installer la bibliothèque :
Copier la bibliothèque dans le répertoire /lib ou
/usr/lib. Si vous décidez de l'installer dans un répertoire
différent (par exemple /usr/local/lib), l'éditeur
de lien ld(1) ne la trouvera peut être pas automatiquement.
Exécuter ldconfig(1) pour créer le lien symboliquede
libName.so.xxx.yyy.zzz vers libName.so.xxx. Cette étape
permettra également de vérifier que les étapes précédentes
ont été réalisées avec succès,et que
la bibliothèque est reconnue comme bibliothèque dynamique.
La facon dont les programmes sont liés à l'exécution
n'est pas touchée par cette étape, seul le chargement des
bibliothèques est affecté.
Créer un lien symbolique de libName.so.xxx.yyyy.zzz(ou
libName.so.xxx le soname) vers libName.so de sorte
que l'éditeur de lien trouve la bibliothèque avec l'option
-l. pour que ce mécanisme fonctionne correctement, il est
nécessaire que le nom de la bibliothèque soit de la forme
libName.so.
10.- Créer une
bibliothèque statique
Si au contraire vous souhaitez créer une
bibliothèque statique (ou si les deux versions sont nécessaires
afin de pouvoir offrir des programmes liés statiquement), nous allons
maintenant décrire les étapes à suivre.
Remarque : l'éditeur de liens commence à chercher la
bibliotheque Nom dans un fichier libNom.so puis ensuite
libNom.a. Si les deux versions (statique et dynamique)
de la librairie portent le même nom, il ne sera généralement
pas possible de déterminer laquelle a été utilisée
par l'éditeur de liens .
Pour cette raison, quand les deux versions d'une même bibliothèque
sont nécessaires, il est recommandé d'utiliser le nom libName_s.a
pour la version statique, et libName.so pour la
version partagée. Ainsi, la version statique sera produite
en invoquant:
gcc -o program -lName_s,
tandis que la version partagée sera obtenue par :
gcc -o program -lName.
10.1.- Compilation du source
Aucune mesure spéciale n'est à prendre pour compiler le(s)
code(s) source. Puisque la position des objets est déterminée
à l'édition des liens, il n'est pas nécessaire d'utiliser
l'option -f PIC (néanmoins, conserver cette option ne causera pas
d'erreur).
10.2.- Edition des liens
Pour les bibliothèques statiques, il n'y a pas d'édition
des liens. Tous les codes objets sont archivés dans un fichier par
l'utilitaire ar(1). Ensuite, afin d'accélérer la
résolution des références, il est conseillé
d'exécuter ranlib(1) sur la bibliothèque. Ne pas
exécuter cette commande peut dans certains cas conduire à
la perte des liens d'un module de l'exécutable. En effet,
quand le module est traité par l'éditeur de liens lors de
la construction de la bibliothèque, toutes les références
indirectes ne sont pas résolues immédiatement ; si le module
est utilisé par un autre module de la bibliothèque, il faudra
plusieurs passages sur la bibliothèque pour résoudre toutes
les références.
10.3.- Installation de la bibliothèque
La bibliothèque statique est nommée libName.a si
cette seule version suffit. Dans le cas où deux versions sont nécessaires,
je recommande de différencier les noms en appelant libName_s.a
la bibliothèque statique. Il sera ainsi plus facile de contrôler
quelle version de bibliothèque est effectivement utilisée
à l'édition de liens.
L'édition des liens permet d'inclure l'option -static.
Cette option contrôle le chargement du module /lib/ld-linux.so,
et n'affecte pas l'ordre de recherche des librairies. En effet, si l'option
-static est utilisée et si ld(1) trouve
une librairie partagée, il l'utilisera (au lieu de continuer à
chercher la librairie statique). Cela conduit à des erreurs à
l'exécution à cause de l'appel de routines qui n'appartiennent
pas au code exécutable -le module pour le chargement automatique
n'est pas lié et donc ce traitement ne peut intervenir.
11.- Liens statiques
ou dynamiques ?
Supposons que l'on souhaite distribuer un programme
qui utilise une librairie que l'on est autorisé à distribuer
seulement si elle est inclue statiquement dans le programme, et pas sous
une autre forme (c'est par exemple le cas pour les applications developpées
avec Motif).
A priori, deux options sont possibles pour distribuer ce type de logiciel.
La première consiste à créer un exécutable
lié statiquement (en utilisant des bibliothèques .a). Lesprogrammes
de ce type sont chargés en une seule fois et ne repose sur aucune
bibliothèque du système (pas même /lib/ld-linux.so).
Cependant, ils ont l'inconvénient d'être des programmes très
volumineux puisque le fichier doit contenir absolument tout le code nécessaire
au fonctionnement du programme. La deuxième option consiste à
réaliser un programme lié dynamiquement, où l'environnement
d'exécution doit fournir toutes les bibliothèques dynamiques
nécessaires. Le fichier exécutable peut alors avoir une très
petite taille, avec cependant le risque que certaines bibliothèques
ne soient pas installées (par exemple tout le monde ne dispose pas
de Motif).
En fait il existe une autre option, à mi chemin entre les deux
précédentes. Certaines librairies peuvent être liées
statiquement, les autres dynamiquement. Il suffit alors de lier statiquement
les bibliothèques posant problème, et dynamiquement toutes
les autres. Cette dernière solution est une façon très
efficace de distribuer des logiciels.
Par exemple, on peut compiler trois versions différentes d'un
programme de la manière suivante :
gcc -static -o program.static program.o
-lm_s -lXm_s -lXt_s -lX11_s\
-lXmu_s -lXpm_s
gcc -o program.dynamic program.o
-lm -lXm -lXt -lX11 -lXmu -lXpm
gcc -o program.mixed program.o
-lm -lXm_s -lXt -lX11 -lXmu -lXpm
Dans le troisième cas seul la librairie Motif (-lXm_s)est
liée statiquement, les autres sont liées dynamiquement. L'environnement
d'exécution doit fournir les versions appropriées des bibliothèques
libm.so.xx libXt.so.xx libX11.so.xx libXmu.so.xxy libXpm.so.xx
.
|