5.- L'édition de liens sous LinuxTous 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éesL'é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éeAvant 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 sourceLa 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 liensAprè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èqueNous 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 statiqueSi 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 sourceAucune 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 liensPour 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èqueLa 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 .
|