Objectif
Les bibliothèques partagées permettent de placer du code commun dans un fichier séparé qui peut être utilisé par plusieurs programmes.
Elles permettent notamment :
- de réduire la taille des exécutables ;
- d'éviter de dupliquer le même code dans plusieurs programmes ;
- de partager en mémoire certaines pages de code entre plusieurs processus ;
- de mettre à jour une bibliothèque sans forcément relier tous les exécutables ;
- de charger du code à l'exécution.
Code: Select all
.so
Code: Select all
libm.so
libc.so.6
libpthread.so.0
1. Fichiers objets et édition de liens
Un fichier source C est d'abord compilé en fichier objet :
Code: Select all
gcc -c prog.c
gcc -c mod1.c
gcc -c mod2.c
Code: Select all
prog.o
mod1.o
mod2.o
Code: Select all
gcc -o prog prog.o mod1.o mod2.o
Schéma :
Code: Select all
source .c
|
v
compilation
|
v
fichier objet .o
|
v
édition de liens
|
v
exécutable
2. Bibliothèques statiques
Une bibliothèque statique regroupe plusieurs fichiers objets dans une archive :
Code: Select all
libdemo.a
Code: Select all
gcc -c mod1.c mod2.c
ar rcs libdemo.a mod1.o mod2.o
Code: Select all
gcc -o prog prog.o -L. -ldemo
Code: Select all
gcc -o prog prog.o ./libdemo.a
Lors de l'édition de liens, le code nécessaire de la bibliothèque statique est intégré dans l'exécutable.
Conséquences :
- l'exécutable est plus gros ;
- chaque programme possède sa propre copie du code ;
- pas besoin du fichier .a au moment de l'exécution ;
- une mise à jour de la bibliothèque nécessite généralement de relier à nouveau le programme.
3. Bibliothèques partagées
Une bibliothèque partagée est chargée séparément de l'exécutable.
Exemple :
Code: Select all
libdemo.so
L'exécutable contient surtout une référence vers la bibliothèque dont il dépend.
Avantages :
- moins d'espace disque ;
- moins de duplication du code en mémoire ;
- plusieurs processus peuvent partager les pages de code en lecture seule ;
- mise à jour plus simple d'une bibliothèque compatible ;
- exécutables plus petits.
- chargement et résolution de symboles plus complexes ;
- dépendance au bon fichier .so présent sur le système ;
- risque de problèmes de compatibilité ABI ;
- coût de certaines relocations ;
- possibilité d'erreurs au démarrage si une bibliothèque manque.
4. Création d'une bibliothèque partagée
On compile généralement les fichiers objets avec :
Code: Select all
-fPIC
Code: Select all
gcc -fPIC -c mod1.c
gcc -fPIC -c mod2.c
Code: Select all
gcc -shared -o libdemo.so mod1.o mod2.o
Code: Select all
gcc -fPIC -shared -o libdemo.so mod1.c mod2.c
5. Position Independent Code : -fPIC
PIC = Position Independent Code
Le code d'une bibliothèque partagée peut être chargé à différentes adresses virtuelles selon les processus.
Il faut donc éviter que le code contienne inutilement des adresses absolues fixes.
L'option :
Code: Select all
-fPIC
Cette propriété est fondamentale pour les bibliothèques partagées.
Schéma :
Code: Select all
même libdemo.so
processus A -> chargée à 0x7f...
processus B -> chargée à 0x7a...
processus C -> chargée à 0x7e...
6. Relocations
Une relocation est une correction effectuée par l'éditeur de liens ou le chargeur afin qu'une référence symbolique pointe vers la bonne adresse.
Exemple conceptuel :
Code: Select all
appel de fonction foo()
|
v
résolution du symbole foo
|
v
adresse réelle de foo
Le PIC réduit ce problème.
7. Utiliser une bibliothèque partagée
Supposons :
Code: Select all
libdemo.so
prog.c
Code: Select all
gcc -o prog prog.c -L. -ldemo
Code: Select all
-L.
Code: Select all
-ldemo
Code: Select all
libdemo.so
Code: Select all
libdemo.a
8. Différence entre édition de liens et chargement dynamique
Il faut distinguer deux moments.
À la compilation / édition de liens :
Le linker vérifie les symboles nécessaires et enregistre les dépendances de l'exécutable.
À l'exécution :
Le chargeur dynamique recherche les bibliothèques réelles et les mappe dans l'espace virtuel du processus.
Schéma :
Code: Select all
gcc / ld
|
v
exécutable ELF
|
v
lancement
|
v
dynamic linker
|
v
chargement des .so
|
v
résolution des symboles
|
v
main()
9. Le chargeur dynamique
Sous Linux ELF, le chargeur dynamique est généralement un programme de type :
Code: Select all
/lib64/ld-linux-x86-64.so.2
Code: Select all
/lib/ld-linux.so.2
- charger les bibliothèques dépendantes ;
- chercher les fichiers .so ;
- effectuer les relocations nécessaires ;
- résoudre les symboles ;
- préparer l'exécution avant le transfert vers le programme.
10. Dépendances ELF : DT_NEEDED
Lorsqu'un exécutable dépend d'une bibliothèque partagée, ELF contient des entrées indiquant les bibliothèques requises.
On peut voir les dépendances avec :
Code: Select all
readelf -d prog
Code: Select all
objdump -p prog
Code: Select all
NEEDED
Code: Select all
Shared library: [libdemo.so.1]
Shared library: [libc.so.6]
11. ldd
La commande :
Code: Select all
ldd prog
Exemple :
Code: Select all
libdemo.so.1 => /usr/lib/libdemo.so.1
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
12. LD_LIBRARY_PATH
La variable d'environnement :
Code: Select all
LD_LIBRARY_PATH
Exemple :
Code: Select all
export LD_LIBRARY_PATH=/home/user/lib
./prog
Code: Select all
LD_LIBRARY_PATH=. ./prog
LD_LIBRARY_PATH est très utile pour les tests et bibliothèques privées, mais n'est généralement pas la meilleure méthode d'installation système en production.
13. Répertoires de bibliothèques
Les bibliothèques peuvent notamment se trouver dans :
Code: Select all
/lib
/usr/lib
/usr/local/lib
/lib64
/usr/lib64
14. Soname
Une bibliothèque partagée possède souvent plusieurs noms.
Exemple :
Code: Select all
libdemo.so.1.2.3
libdemo.so.1
libdemo.so
Real name
Nom du fichier réel :
Code: Select all
libdemo.so.1.2.3
Nom ABI majeur utilisé à l'exécution :
Code: Select all
libdemo.so.1
Nom utilisé par l'éditeur de liens lors de :
Code: Select all
-ldemo
Code: Select all
libdemo.so
15. Créer un soname
Exemple :
Code: Select all
gcc -fPIC -c mod1.c mod2.c
gcc -shared \
-Wl,-soname,libdemo.so.1 \
-o libdemo.so.1.2.3 \
mod1.o mod2.o
Code: Select all
ln -s libdemo.so.1.2.3 libdemo.so.1
ln -s libdemo.so.1 libdemo.so
Code: Select all
libdemo.so
|
v
libdemo.so.1
|
v
libdemo.so.1.2.3
16. Pourquoi le soname est important
Le soname permet de représenter une famille compatible d'une bibliothèque.
Exemple :
Code: Select all
libdemo.so.1.0
libdemo.so.1.1
libdemo.so.1.9
Code: Select all
libdemo.so.1
Code: Select all
libdemo.so.2
Code: Select all
libdemo.so.1
Code: Select all
libdemo.so.2
17. Version majeure et mineure
Convention courante :
Code: Select all
libdemo.so.major.minor
Code: Select all
libdemo.so.2.4
- major change lorsque l'ABI devient incompatible ;
- minor change pour une évolution compatible.
18. Compatibilité ABI
Une bibliothèque reste compatible si le programme déjà compilé peut continuer à l'utiliser correctement.
Des changements généralement dangereux :
- supprimer une fonction publique ;
- changer ses paramètres ;
- changer son type de retour ;
- modifier une structure publique d'une façon incompatible ;
- changer la signification d'un symbole exporté.
- corriger une implémentation interne ;
- ajouter une nouvelle fonction sans supprimer l'ancienne ;
- améliorer les performances sans changer l'interface publique.
API = interface vue par le code source.
ABI = convention binaire réellement utilisée entre modules compilés.
19. Mise à jour d'une bibliothèque
Si une nouvelle version est compatible avec le même soname :
Code: Select all
libdemo.so.1.2.3
->
libdemo.so.1.2.4
Code: Select all
libdemo.so.1
Si l'ABI change :
Code: Select all
libdemo.so.1
->
libdemo.so.2
20. Installation des bibliothèques
Une bibliothèque système est généralement installée dans un répertoire connu du chargeur :
Code: Select all
/lib
/usr/lib
/usr/local/lib
Code: Select all
ldconfig
- analyse les répertoires de bibliothèques ;
- met à jour les liens symboliques appropriés ;
- met à jour le cache des bibliothèques.
21. /etc/ld.so.conf
Le fichier :
Code: Select all
/etc/ld.so.conf
Code: Select all
/etc/ld.so.conf.d/
Après modification :
Code: Select all
sudo ldconfig
22. Cache du chargeur dynamique
Le chargeur peut utiliser un cache généré par :
Code: Select all
ldconfig
23. RPATH et RUNPATH
Un exécutable ELF peut contenir directement des chemins de recherche.
Avec gcc :
Code: Select all
gcc prog.c -L./lib -ldemo \
-Wl,-rpath,/chemin/vers/lib
Code: Select all
readelf -d prog
Code: Select all
DT_RPATH
DT_RUNPATH
Aujourd'hui, RUNPATH est généralement préféré à l'ancien RPATH.
24. $ORIGIN
Le chargeur dynamique comprend le token :
Code: Select all
$ORIGIN
Exemple :
Code: Select all
-Wl,-rpath,'$ORIGIN/lib'
Code: Select all
app/
├── prog
└── lib/
└── libdemo.so
Très pratique pour les applications portables.
25. Recherche des bibliothèques à l'exécution
La recherche exacte dépend de plusieurs paramètres et de la présence de DT_RPATH / DT_RUNPATH.
À retenir conceptuellement :
- chemins intégrés dans l'objet ELF ;
- LD_LIBRARY_PATH ;
- cache du chargeur dynamique ;
- répertoires système standards.
26. Résolution des symboles à l'exécution
Une bibliothèque partagée référence souvent des symboles qui ne sont pas encore résolus définitivement au moment de sa création.
Le chargeur dynamique doit déterminer quelle définition utiliser.
Exemple :
Code: Select all
prog
|
+--> foo()
libA.so
|
+--> foo()
libB.so
|
+--> foo()
27. Interposition de symboles
Un symbole d'une bibliothèque peut parfois être remplacé par une définition trouvée plus tôt dans l'espace global de résolution.
Cette propriété est appelée :
Code: Select all
symbol interposition
28. Lien statique à la place du lien dynamique
On peut demander une liaison statique :
Code: Select all
gcc -static -o prog prog.c
Code: Select all
gcc prog.o /chemin/libdemo.a -o prog
- exécutable plus autonome ;
- pas besoin des bibliothèques dynamiques correspondantes à l'exécution.
- exécutable plus gros ;
- duplication du code ;
- mises à jour de bibliothèques non automatiquement récupérées ;
- certaines bibliothèques ou fonctionnalités système se prêtent mal au lien statique total.
Résumé chapitre 41
Code: Select all
STATIC
libdemo.a
|
v
code intégré dans l'exécutable
SHARED
libdemo.so
|
v
référence enregistrée dans l'exécutable
|
v
chargement par ld-linux au runtime
Code: Select all
gcc -fPIC -c mod.c
gcc -shared -o libdemo.so mod.o
gcc prog.c -L. -ldemo -o prog
ldd prog
readelf -d prog
objdump -p prog
ldconfig
Chapitre 42 — Fonctions avancées des bibliothèques partagées
Le chapitre précédent décrit le chargement automatique des bibliothèques requises par un programme.
Linux permet aussi de charger une bibliothèque manuellement pendant l'exécution.
Cette technique repose principalement sur l'API :
Code: Select all
dlopen()
dlsym()
dlerror()
dlclose()
Code: Select all
#include <dlfcn.h>
Code: Select all
-ldl
1. Chargement dynamique explicite
Principe :
Code: Select all
programme démarre
|
v
dlopen()
|
v
charge libplugin.so
|
v
dlsym()
|
v
cherche une fonction
|
v
appel de la fonction
|
v
dlclose()
- plugins ;
- modules optionnels ;
- backends chargés dynamiquement ;
- extensions ;
- systèmes de pilotes utilisateurs ;
- logiciels pouvant fonctionner avec plusieurs implémentations.
2. dlopen()
Prototype :
Code: Select all
void *dlopen(const char *filename, int flags);
Code: Select all
void *handle;
handle = dlopen("./libdemo.so", RTLD_LAZY);
if (handle == NULL) {
// erreur
}
- handle valide en cas de succès ;
- NULL en cas d'erreur.
3. Recherche du fichier avec dlopen()
Si filename contient un slash :
Code: Select all
./libdemo.so
/home/user/lib/libdemo.so
Sinon :
Code: Select all
dlopen("libdemo.so", ...);
4. Compteur de références
Une même bibliothèque peut être ouverte plusieurs fois.
Le système maintient conceptuellement un compteur de références.
Exemple :
Code: Select all
dlopen()
dlopen()
dlclose()
dlclose()
Une bibliothèque peut aussi rester chargée si elle est encore nécessaire comme dépendance d'un autre objet.
5. RTLD_LAZY
Exemple :
Code: Select all
dlopen("libdemo.so", RTLD_LAZY);
Avantage :
- chargement initial potentiellement plus rapide.
- certaines erreurs de symboles peuvent n'apparaître qu'au premier appel.
6. RTLD_NOW
Exemple :
Code: Select all
dlopen("libdemo.so", RTLD_NOW);
Cela permet de détecter les problèmes plus tôt.
Différence :
Code: Select all
RTLD_LAZY
-> résolution différée de certaines fonctions
RTLD_NOW
-> résolution immédiate
7. RTLD_GLOBAL
Exemple :
Code: Select all
dlopen("libdemo.so", RTLD_NOW | RTLD_GLOBAL);
8. RTLD_LOCAL
Exemple :
Code: Select all
dlopen("libdemo.so", RTLD_NOW | RTLD_LOCAL);
C'est généralement le comportement par défaut.
Résumé :
Code: Select all
RTLD_GLOBAL
-> symboles visibles pour d'autres objets
RTLD_LOCAL
-> symboles limités au groupe chargé
9. RTLD_NOLOAD
RTLD_NOLOAD permet notamment de vérifier si une bibliothèque est déjà chargée sans nécessairement la charger une nouvelle fois.
Exemple conceptuel :
Code: Select all
dlopen("libdemo.so", RTLD_NOLOAD | RTLD_NOW);
10. RTLD_NODELETE
Sous Linux, RTLD_NODELETE permet de demander que la bibliothèque ne soit pas réellement retirée de l'espace d'adressage lors de dlclose().
Cela évite notamment de réinitialiser certaines données statiques lors d'un futur rechargement.
11. RTLD_DEEPBIND
RTLD_DEEPBIND modifie les priorités de résolution afin de favoriser les symboles propres à la bibliothèque chargée par rapport à certains symboles globaux déjà présents.
C'est une extension GNU/Linux et non un mécanisme POSIX portable.
12. dlerror()
Prototype :
Code: Select all
char *dlerror(void);
Exemple :
Code: Select all
void *h = dlopen("./libdemo.so", RTLD_NOW);
if (h == NULL) {
fprintf(stderr, "%s\n", dlerror());
}
Un symbole peut théoriquement avoir une adresse NULL.
Il faut donc utiliser dlerror() correctement pour distinguer :
- symbole trouvé à une valeur NULL ;
- échec réel de dlsym().
Code: Select all
dlerror();
void *p = dlsym(handle, "foo");
char *err = dlerror();
if (err != NULL) {
// erreur
}
13. dlsym()
Prototype :
Code: Select all
void *dlsym(void *handle, const char *symbol);
Code: Select all
void *addr;
addr = dlsym(handle, "foo");
Le symbole peut représenter :
- une fonction ;
- une variable globale.
14. Utiliser une fonction retournée par dlsym()
Exemple :
Code: Select all
int (*func)(int);
func = (int (*)(int)) dlsym(handle, "foo");
if (func != NULL) {
int result = func(10);
}
15. Utiliser une variable retournée par dlsym()
Exemple :
Code: Select all
int *value;
value = (int *) dlsym(handle, "global_value");
if (value != NULL) {
printf("%d\n", *value);
}
16. RTLD_DEFAULT
Au lieu d'un handle obtenu avec dlopen(), dlsym() peut recevoir :
Code: Select all
RTLD_DEFAULT
Exemple :
Code: Select all
void *p = dlsym(RTLD_DEFAULT, "malloc");
17. RTLD_NEXT
RTLD_NEXT permet de chercher la prochaine définition du symbole après l'objet courant.
Très utile lorsqu'on interpose une fonction mais qu'on veut appeler ensuite l'implémentation originale.
Exemple conceptuel :
Code: Select all
malloc() remplacé
|
v
dlsym(RTLD_NEXT, "malloc")
|
v
vrai malloc suivant
18. dlclose()
Prototype :
Code: Select all
int dlclose(void *handle);
Code: Select all
if (dlclose(handle) != 0) {
fprintf(stderr, "%s\n", dlerror());
}
Lorsque le compteur atteint zéro et qu'aucune dépendance ne nécessite encore la bibliothèque, celle-ci peut être déchargée.
19. dladdr()
dladdr() permet d'obtenir des informations concernant l'adresse d'un symbole chargé.
Prototype conceptuel :
Code: Select all
int dladdr(const void *addr, Dl_info *info);
Code: Select all
Dl_info
- nom du fichier contenant l'adresse ;
- adresse de base de la bibliothèque ;
- nom du symbole correspondant ;
- adresse exacte du symbole.
- debug ;
- diagnostic ;
- introspection ;
- trouver à quelle bibliothèque appartient une fonction.
20. Exporter des symboles du programme principal
Normalement, tous les symboles du programme principal ne sont pas forcément visibles par les bibliothèques chargées dynamiquement.
On peut demander leur export avec :
Code: Select all
-rdynamic
Code: Select all
gcc -rdynamic -o prog prog.c -ldl
Code: Select all
-Wl,--export-dynamic
21. Visibilité des symboles
Une bibliothèque ne devrait pas forcément exporter tous ses symboles internes.
Exporter inutilement beaucoup de symboles :
- augmente la taille de la table dynamique ;
- peut créer des collisions ;
- augmente la surface d'ABI publique ;
- peut provoquer des interpositions inattendues.
22. static
En C :
Code: Select all
static void helper(void)
{
}
23. GCC visibility attribute
GCC permet notamment :
Code: Select all
__attribute__((visibility("hidden")))
Code: Select all
__attribute__((visibility("hidden")))
void internal_function(void)
{
}
On peut également contrôler la visibilité générale avec :
Code: Select all
-fvisibility=hidden
24. Linker version scripts
Un version script est un fichier texte donné au linker.
Exemple :
Code: Select all
gcc -shared -o libdemo.so *.o \
-Wl,--version-script,version.map
- les symboles exportés ;
- les symboles cachés ;
- les versions associées aux symboles.
25. Exemple de contrôle de visibilité par script
Fichier :
Code: Select all
version.map
Code: Select all
VERS_1 {
global:
public_function;
local:
*;
};
Code: Select all
public_function
-> exportée
tous les autres symboles
-> locaux/cachés
26. Symbol versioning
Linux permet à une même bibliothèque de fournir plusieurs versions d'un même symbole.
Exemple conceptuel :
Code: Select all
foo@VER_1
foo@@VER_2
- aux anciens programmes de continuer à utiliser l'ancienne ABI ;
- aux nouveaux programmes d'utiliser une nouvelle implémentation ;
- de faire évoluer une bibliothèque sans casser immédiatement les anciens binaires.
27. Version par défaut
Dans la notation ELF :
Code: Select all
foo@VER_1
Code: Select all
foo@@VER_2
28. Dépendances entre versions
Un version script peut exprimer :
Code: Select all
VER_1 {
global:
foo;
};
VER_2 {
global:
bar;
} VER_1;
Code: Select all
VER_2 dépend de VER_1
29. Initialisation automatique d'une bibliothèque
Une fonction peut être exécutée automatiquement lorsqu'une bibliothèque est chargée.
Avec GCC :
Code: Select all
__attribute__((constructor))
static void init_library(void)
{
// initialisation
}
30. Finalisation automatique
Même principe :
Code: Select all
__attribute__((destructor))
static void fini_library(void)
{
// nettoyage
}
31. _init() et _fini()
Historiquement, il était possible d'utiliser :
Code: Select all
_init()
_fini()
Code: Select all
constructor
destructor
32. LD_PRELOAD
La variable :
Code: Select all
LD_PRELOAD
Exemple :
Code: Select all
LD_PRELOAD=./libhook.so ./prog
Applications légitimes :
- debug ;
- instrumentation ;
- profiling ;
- tests ;
- compatibilité ;
- interception de fonctions.
33. Exemple conceptuel d'interposition
Programme :
Code: Select all
malloc()
Code: Select all
prog
|
v
libc malloc()
Code: Select all
LD_PRELOAD=./libhook.so
Code: Select all
prog
|
v
libhook malloc()
|
v
RTLD_NEXT
|
v
libc malloc()
34. Restrictions de sécurité de LD_PRELOAD
Pour les programmes exécutés dans certains modes privilégiés ou sécurisés, le chargeur ignore ou limite des variables telles que :
Code: Select all
LD_PRELOAD
LD_LIBRARY_PATH
LD_DEBUG
35. /etc/ld.so.preload
Linux permet également de précharger des bibliothèques via :
Code: Select all
/etc/ld.so.preload
36. LD_DEBUG
La variable :
Code: Select all
LD_DEBUG
Exemple :
Code: Select all
LD_DEBUG=libs ./prog
Code: Select all
libs
reloc
files
symbols
bindings
versions
statistics
unused
all
help
37. Exemples LD_DEBUG
Voir les recherches de bibliothèques :
Code: Select all
LD_DEBUG=libs ./prog
Code: Select all
LD_DEBUG=symbols ./prog
Code: Select all
LD_DEBUG=bindings ./prog
Code: Select all
LD_DEBUG=all ./prog
38. Rediriger LD_DEBUG
La sortie de LD_DEBUG peut être volumineuse.
Linux permet notamment d'utiliser :
Code: Select all
LD_DEBUG_OUTPUT
Code: Select all
LD_DEBUG=libs LD_DEBUG_OUTPUT=/tmp/trace ./prog
39. Exemple complet dlopen + dlsym
Bibliothèque :
Code: Select all
/* plugin.c */
#include <stdio.h>
void hello(void)
{
printf("Hello depuis le plugin\n");
}
Code: Select all
gcc -fPIC -shared plugin.c -o libplugin.so
Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int main(void)
{
void *handle;
void (*hello)(void);
char *error;
handle = dlopen("./libplugin.so", RTLD_NOW);
if (handle == NULL) {
fprintf(stderr, "%s\n", dlerror());
return EXIT_FAILURE;
}
dlerror();
hello = (void (*)(void)) dlsym(handle, "hello");
error = dlerror();
if (error != NULL) {
fprintf(stderr, "%s\n", error);
dlclose(handle);
return EXIT_FAILURE;
}
hello();
if (dlclose(handle) != 0) {
fprintf(stderr, "%s\n", dlerror());
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Code: Select all
gcc main.c -o prog -ldl
Code: Select all
./prog
40. Schéma global des bibliothèques dynamiques
Code: Select all
COMPILATION
mod1.c ----> mod1.o \
mod2.c ----> mod2.o \
+--> libdemo.so
mod3.c ----> mod3.o /
/
-fPIC
-shared
LINK
prog.c
|
v
prog.o
|
+---- -ldemo
|
v
prog ELF
|
+---- DT_NEEDED = libdemo.so.1
EXÉCUTION
execve()
|
v
kernel
|
v
ld-linux
|
+--> cherche libdemo.so.1
|
+--> mmap bibliothèque
|
+--> relocations
|
+--> résolution symboles
|
+--> constructors
|
v
main()
41. Carte mentale
Code: Select all
BIBLIOTHÈQUES LINUX
|
+-- statiques
| |
| +-- .a
| +-- ar
| +-- code copié dans l'exécutable
|
+-- partagées
|
+-- .so
+-- -fPIC
+-- -shared
|
+-- chargement
| |
| +-- ld-linux
| +-- DT_NEEDED
| +-- LD_LIBRARY_PATH
| +-- RPATH / RUNPATH
| +-- $ORIGIN
| +-- ldconfig
|
+-- versions
| |
| +-- real name
| +-- soname
| +-- linker name
| +-- ABI major
|
+-- outils
| |
| +-- ldd
| +-- readelf
| +-- objdump
|
+-- chargement manuel
| |
| +-- dlopen
| +-- dlsym
| +-- dlerror
| +-- dlclose
| +-- dladdr
|
+-- flags
| |
| +-- RTLD_LAZY
| +-- RTLD_NOW
| +-- RTLD_GLOBAL
| +-- RTLD_LOCAL
| +-- RTLD_NOLOAD
| +-- RTLD_NODELETE
| +-- RTLD_DEEPBIND
|
+-- symboles
| |
| +-- visibilité
| +-- interposition
| +-- version scripts
| +-- symbol versioning
| +-- RTLD_DEFAULT
| +-- RTLD_NEXT
|
+-- hooks / debug
|
+-- LD_PRELOAD
+-- LD_DEBUG
42. Commandes à connaître
Créer un objet PIC :
Code: Select all
gcc -fPIC -c lib.c
Code: Select all
gcc -shared -o libdemo.so lib.o
Code: Select all
gcc -shared \
-Wl,-soname,libdemo.so.1 \
-o libdemo.so.1.0 \
lib.o
Code: Select all
ln -s libdemo.so.1.0 libdemo.so.1
ln -s libdemo.so.1 libdemo.so
Code: Select all
gcc prog.c -L. -ldemo -o prog
Code: Select all
LD_LIBRARY_PATH=. ./prog
Code: Select all
ldd ./prog
Code: Select all
readelf -d ./prog
Code: Select all
readelf -Ws libdemo.so
Code: Select all
nm -D libdemo.so
Code: Select all
sudo ldconfig
Code: Select all
LD_DEBUG=libs ./prog
Code: Select all
LD_PRELOAD=./libhook.so ./prog
43. API essentielles du chapitre 42
Code: Select all
dlopen()
-> charge une bibliothèque
dlsym()
-> récupère l'adresse d'un symbole
dlerror()
-> récupère une erreur dl*
dlclose()
-> libère une référence sur une bibliothèque
dladdr()
-> obtient des informations à partir d'une adresse
Code: Select all
RTLD_LAZY
RTLD_NOW
RTLD_GLOBAL
RTLD_LOCAL
Code: Select all
RTLD_NOLOAD
RTLD_NODELETE
RTLD_DEEPBIND
RTLD_DEFAULT
RTLD_NEXT
44. À retenir absolument
- .a = bibliothèque statique.
- .so = bibliothèque partagée.
- -fPIC produit du code indépendant de sa position.
- -shared crée une bibliothèque partagée.
- -lxxx cherche libxxx.so ou libxxx.a.
- -L ajoute un répertoire de recherche au linker.
- ld-linux charge les bibliothèques au runtime.
- DT_NEEDED stocke les dépendances ELF.
- soname représente la version ABI majeure d'une bibliothèque.
- ldconfig gère le cache et les liens des bibliothèques système.
- LD_LIBRARY_PATH modifie les chemins de recherche au runtime.
- RPATH / RUNPATH stockent des chemins directement dans l'ELF.
- $ORIGIN permet un chemin relatif à l'exécutable ou à l'objet.
- dlopen() charge une .so à la demande.
- dlsym() récupère une fonction ou variable par son nom.
- dlerror() permet de diagnostiquer les erreurs.
- dlclose() retire une référence à la bibliothèque.
- RTLD_NOW résout immédiatement.
- RTLD_LAZY peut différer la résolution des fonctions.
- RTLD_GLOBAL rend les symboles disponibles aux objets chargés ensuite.
- RTLD_LOCAL limite leur visibilité.
- LD_PRELOAD permet l'interposition de symboles.
- LD_DEBUG permet d'observer le travail du linker dynamique.
- version scripts permettent de contrôler les symboles exportés et leur version.
- constructor/destructor exécutent automatiquement du code lors du chargement/déchargement.
Une bibliothèque partagée n'est pas simplement "un fichier contenant des fonctions".
Elle fait partie d'un mécanisme complet impliquant :
Code: Select all
ELF
+
linker
+
dynamic linker
+
PIC
+
relocations
+
symboles
+
ABI
+
soname
+
chemins de recherche
+
chargement dynamique
