Niveau :
Résumé : ldd ; readelf ; ld-linux
Savez vous ce qui se passe quand vous lancez un logiciel ? Si c'est à travers votre shell, il analyse votre ligne de commande puis d'une façon ou d'une autre appelle execve qui demande au noyau de charger le fichier en question et de l'exécuter. Si ce n'est pas le shell, un autre fera à peu près le même chose.
Vous pensez probablement que le binaire est mis en mémoire à un endroit donné et que le point d'entrée correspondant est appelé et que c'est tout. Vous auriez eu raison il y a 15 ans. Mais le format de fichier aout a été remplacé par le format elf et les choses ne se passent plus tout à fait de cette façon.
Le format elf a été inventé entre autre pour permettre le chargement dynamique de bibliothèque. Pour lancer un fichier elf, le noyau lance d'abord ce qu'il appelle un interpréteur. L'interpréteur est une information donnée dans le programme lui-même. Il apparaît dans a liste des bibliothèques chargées avec un programme qu'on voit avec la commande ldd :
$ ldd /bin/ls
Vous pouvez aussi le voir avec la commande readelf :
$ readelf -l /bin/ls
Donc en pratique lorsque vous tapez ls, le noyau comprend la commande suivante :
$ /lib64/ld-linux-x86-64.so.2 /bin/ls # vous constatez que je suis en architecture 64 bits
D'ailleurs vous pouvez aussi lancer la commande telle quelle.
Ld-linux va à son tour lire les en-têtes elf et charger toutes les bibliothèques dont dépend le programme en question. C'est lui qui appellera le point de départ du programme. Les symboles (comme par exemple les fonctions) seront eux résolus lorsqu'ils seront appelés, le premier appel d'une fonction est donc toujours légèrement plus long que les suivants.
Sachez que vous pouvez influencer le comportement de ld-linux avec des variables d'environnement, attention des restrictions s'appliquent pour les programmes setuid. Les variables les plus courantes sont les 2 premières :
- LD_LIBRARY_PATH : une liste de répertoires dans lesquels chercher les bibliothèques en question, pratique quand vous développez et que vos bibliothèques ne sont pas encore installée sur le système
- LD_PRELOAD : une liste de bibliothèques à charger avant toutes les autres, pratique quand vous voulez intercepter des appels de fonction.
- LD_TRACE_LOADED_OBJECTS : force à lister les bibliothèques chargées plutôt que de le lancer le programme. C'est ce que fait ldd en pratique.
- LD_BIND_NOW : résout les symboles dès le chargement des bibliothèques plutôt que lorsqu'ils sont appelés.
- LD_WARN : affiche les symboles non résolus s'il y en a, pratique pour détecter les problèmes de version sur des bibliothèques.
- LD_DEBUG : permet de débugger le fonctionnement de ld-linux, de débugger des bibliothèques ou de mesurer le temps perdu à cause du chargement dynamique
- LD_DEBUG_OUTPUT : le pote du précédent pour enregistrer les infos quelque part
- LD_ASSUME_KERNEL : certaines bibliothèques peuvent exiger une version minimum du noyau, cette variable permet de les charger quand même
- LD_VERBOSE : affiche les versions des symboles s'il y a lieu
Les suivantes ne servent que pour le aout, qui est dépassé
- LD_AOUT_LIBRARY_PATH
- LD_AOUT_PRELOAD
- LD_NOWARN
- LD_KEEPDIR
C'est bien gentil me direz-vous, mais a quoi ça sert ? Tout d'abord à précharger des bibliothèques pour intercepter et modifier des appels comme dans trickle
Ensuite c'est très utile lorsque vous développez des bibliothèques pour ne pas être obligé de les installer sur le système pour les utiliser.
Enfin c'est utile lorsque vous venez d'installer un logiciel et que vous avez des problèmes de dépendances avec des bibliothèques.
Maintenant imaginez que ce soit vous qui développiez ld-linux. Qu'y rajouteriez vous ? Pour ma part, j'y ajouterais :
- une résolution automatique des symboles non résolus : on appelle un handler qui fait un apt-get qui va bien pour installer la bibliothèque manquante, pratique pour les logiciels binaires installés à la main (imaginez un os dont les bibliothèques pourraient être n'importe où sur le réseau).
- un système de statistique des bibliothèques (voire des binaires) non utilisées pour en permettre la suppression
- un système de statistique des fonctions non utilisée pour que le développeurs puisse être au courant de son code presque mort
Comments