dlclose, dlopen, dlmopen - Ouvrir et fermer un objet partagé
Bibliothèque de liens dynamiques (
libdl,
-ldl)
#include <dlfcn.h>
void *dlopen(const char *filename, int flags);
int dlclose(void *handle);
#define _GNU_SOURCE
#include <dlfcn.h>
void *dlmopen(Lmid_t lmid, const char *filename, int flags);
La fonction
dlopen() charge la bibliothèque dynamique dont le nom
est fourni dans la chaîne
filename (terminée par l'octet
NULL) et renvoie un descripteur opaque
(« handle ») représentant la
bibliothèque dynamique. Ce descripteur est utilisé avec d'autres
fonctions dans l'API dlopen, telles que
dlsym(3),
dladdr(3),
dlinfo(3) et
dlclose().
Si l'argument
filename est un pointeur NULL, le descripteur
renvoyé correspond au programme principal. Si
filename contient
une barre oblique (« / »), il est
interprété comme un chemin (relatif ou absolu). Autrement,
l'éditeur dynamique de liens cherche la bibliothèque de la
façon suivante (consultez
ld.so(8) pour plus de
détails) :
- •
- (ELF seulement) Si l'objet appelant (c'est-à-dire la
bibliothèque partagée ou l'exécutable depuis lequel
dlopen() est appelée) contient la balise DT_RPATH mais pas
la balise DT_RUNPATH, les répertoires listés dans la balise
DT_RPATH seront parcourus.
- •
- Si à l'instant où le programme est
démarré, la variable d'environnement LD_LIBRARY_PATH
est définie et contient une liste de répertoires
séparés par des deux-points
« : », ces répertoires seront
parcourus. Par mesure de sécurité, cette variable est
ignorée dans le cas de programmes set-user-ID et set-group-ID.
- •
- (ELF seulement) Si l'objet appelant contient la balise
DT_RUNPATH, les répertoires listés dans cette balise seront
parcourus.
- •
- Le fichier de cache /etc/ld.so.cache (entretenu par
ldconfig(8)) est vérifié pour voir s'il contient une
entrée correspondant à filename.
- •
- Les répertoires /lib et /usr/lib sont
parcourus (dans cet ordre).
Si l'objet indiqué dans
filename a des dépendances sur
d'autres objets partagés, ceux-ci seront automatiquement chargés
par l'éditeur dynamique de liens, en utilisant les mêmes
règles. Le processus peut être récursif si ces objets
ont, à leur tour, des dépendances, et ainsi de suite.
L'une des deux valeurs suivantes doit être incluse dans
flag :
- RTLD_LAZY
- Effectuer des liaisons paresseuses. Ne résoudre les
symboles que lorsque le code qui les référence est
exécuté. Si le symbole n'est jamais
référencé, alors il n'est jamais résolu. Les
liaisons paresseuses ne sont effectuées que pour les
références de fonctions ; les
références de variables sont toujours immédiatement
liées quand l'objet partagé est chargé. Depuis la
version 2.1.1 de la glibc, ce drapeau est supplanté par
l'effet de la variable d'environnment LD_BIND_NOW.
- RTLD_NOW
- Si cette valeur est spécifiée ou si la
variable d'environnement LD_BIND_NOW est définie avec une
chaîne non vide, tous les symboles non définis de l'objet
partagé sont résolus avant le retour de dlopen(). Si
cela ne peut pas être fait, une erreur est renvoyée.
Zéro ou plusieurs des valeurs suivantes peuvent être
spécifiées avec un OU binaire dans
flag :
- RTLD_GLOBAL
- Les symboles définis par cet objet partagé
seront rendus disponibles pour la résolution des symboles des
objets partagés chargés ultérieurement.
- RTLD_LOCAL
- C'est la réciproque de RTLD_GLOBAL et le
comportement par défaut si aucun des drapeaux n'est
spécifié. Les symboles définis dans cet objet
partagé ne sont pas rendus disponibles pour résoudre les
références des objets partagés chargés
ultérieurement.
-
RTLD_NODELETE (depuis la glibc 2.2)
- Ne pas décharger l'objet partagé lors de
dlclose(). En conséquence, les variables statiques de
l'objet ne sont pas réinitialisées si l'objet est
chargé ultérieurement avec dlopen().
-
RTLD_NOLOAD (depuis la glibc 2.2)
- Ne pas charger l'objet partagé. Cela peut
être utilisé pour tester si l'objet partagé n'est pas
déjà chargé ( dlopen() renvoie NULL s'il n'est
pas chargé, ou le descripteur de l'objet partagé s'il est
déjà chargé). Ce drapeau peut aussi être
utilisé pour promouvoir les drapeaux d'un objet partagé
déjà chargé. Par exemple, un objet partagé qui
a été chargé avec RTLD_LOCAL peut être
de nouveau ouvert avec
RTLD_NOLOAD | RTLD_GLOBAL.
-
RTLD_DEEPBIND (depuis la glibc 2.3.4)
- Placer l'espace de recherche des symboles de cet objet
partagé avant l'espace global. Cela signifie qu'un objet autonome
utilisera de préférence ses propres symboles aux symboles
globaux de même noms contenus dans les objets déjà
chargés.
Si l'argument
filename est un pointeur NULL, le descripteur
renvoyé correspond au programme principal. Lorsqu'il est passé
à
dlsym(), ce descripteur provoque la recherche d'un symbole
dans le programme principal, puis dans tous les objets partagés
chargés au démarrage du programme, puis dans toutes les objets
partagés chargés par
dlopen() avec l'attribut
RTLD_GLOBAL.
Les références aux symboles de l'objet partagé sont
résolues en utilisant (dans l'ordre) : les symboles dans la
table de liens des objets chargés pour le programme principal et ses
dépendances, les symboles dans les objets partagés (et leurs
dépendances) qui ont été ouverts par un appel
antérieur à
dlopen() avec le drapeau
RTLD_GLOBAL
et les définitions dans l'objet partagé lui-même (ainsi
que toute dépendance ayant été chargée pour cet
objet).
Tout symbole global dans l'exécutable qui a été
placé dans sa table de symboles dynamiques par
ld(1) peut aussi
être utilisé pour résoudre les références
dans un objet partagé dynamiquement chargé. Les symboles peuvent
être placés dans la table de symbole soit parce que les liens de
l'exécutable ont été édités avec le drapeau
« -rdynamic » (ou de façon synonyme
« --export-dynamic »), qui fait que tous les
symboles globaux de l'exécutable sont placés dans la table de
symboles dynamiques, soit parce que
ld(1) a identifié une
dépendance sur un symbole dans un autre objet durant l'édition
de liens statiques.
Si le même objet partagé est chargé une nouvelle fois avec
dlopen(), le même descripteur sera renvoyé. Un
décompte du nombre de chargements est toutefois conservé par
l'éditeur dynamique de liens afin d'éviter de le
décharger avant que la fonction
dlclose() n'ait
été appelée autant de fois que
dlopen() a
réussi. Les constructeurs (voir ci-dessous) sont seulement
appelés lorsque l'objet est réellement chargé en
mémoire (c'est-à-dire lorsque le compteur de
références est augmenté et passe à 1).
Un appel ultérieur à
dlopen() qui charge le même
objet partagé avec
RTLD_NOW peut forcer la résolution de
symboles pour un objet partagé chargé antérieurement avec
RTLD_LAZY. De façon similaire, un objet préalablement
ouvert avec
RTLD_LOCAL peut être promu à
RTLD_GLOBAL lors d'un appel ultérieur à
dlopen().
Si
dlopen() échoue pour une raison quelconque, elle renvoie NULL.
Cette fonction effectue la même tâche que
dlopen() ;
les arguments
filename et
flags, de même que la valeur de
renvoi, sont les mêmes à l'exception des différences
décrites plus bas.
La fonction
dlmopen() diffère de la fonction
dlopen()
principalement parce qu'elle accepte un argument supplémentaire,
lmid, qui indique la liste de tables de liens (aussi appelée
espace de noms) dans laquelle l'objet partagé doit être
chargé. En comparaison,
dlopen() ajoute l'objet partagé
dynamiquement chargé au même espace de noms que l'objet
partagé pour lequel l'appel
dlopen() est fait. Le type
Lmid_t est un gestionnaire opaque qui fait référence
à un espace de noms.
L'argument
lmid est soit l'ID d'un espace de noms existant (pouvant
être obtenu en utilisant la requête
dlinfo(3)
RTLD_DI_LMID) ou l'une des valeurs spéciales suivantes :
- LM_ID_BASE
- Charger l'objet partagé dans l'espace de noms
initial (c'est-à-dire l'espace de noms de l'application).
- LM_ID_NEWLM
- Créer un nouvel espace de noms et y charger l'objet
partagé. Les liens de l'objet doivent avoir été
liés pour référencer tous les autres objets
partagés dont il a besoin puisque l'espace de noms est initialement
vide.
Si
filename est vide, alors l'unique valeur autorisée pour
lmid est
LM_ID_BASE.
La fonction
dlclose() décrémente le compteur de
références de l'objet partagé chargé dynamiquement
et indiqué par
handle.
Si le compteur de références de cet objet tombe en dessous de
zéro et qu'aucun symbole dans cet objet n'est requis par un autre
objet, alors l'objet est déchargé après avoir
appelé tous les destructeurs définis pour l'objet. Des symboles
dans cet objet peuvent être requis par un autre objet parce qu'il a
été ouvert avec le drapeau
RTLD_GLOBAL et que l'un de ses
symboles a permis une relocalisation dans un autre objet.
Tous les objets partagés qui ont été chargés
automatiquement lorsque
dlopen() a été invoquée
sur l'objet référencé par
handle sont
fermés récursivement de la même façon.
Un renvoi réussi de
dlclose() ne garantit que les symboles
associés avec
handle sont supprimés de l'espace
d'adressage de l'appelant. En plus de références
résultant d'appels explicites à
dlopen(), un objet
partagé a peut-être été chargé de
façon implicite (et les références prises en compte)
à cause de références dans d'autres objets
partagés. Ce n'est que lorsque toutes les références sont
relachées que l'objet partagé peut être supprimé
de l'espace d'adressage.
En cas de succès,
dlopen() et
dlmopen() renvoient un
gestionnaire non nul pour l'objet chargé. En cas d'erreur (le fichier
ne peut pas être trouvé, il n'est pas lisible, a le mauvais
format ou bien a provoqué des erreurs lors de son chargement), ces
fonctions renvoient NULL.
En cas de succès,
dlclose() renvoie
0, en cas d'erreur une
valeur non nulle est renvoyée.
Les erreurs de ces fonctions peuvent être diagnostiquées en
utilisant
dlerror(3).
dlopen() et
dlclose() sont présentes dans la
version 2.0 et suivantes de la glibc.
dlmopen() est apparue pour
la première fois dans la version 2.3.4 de la glibc.
Pour une explication des termes utilisés dans cette section, consulter
attributes(7).
Interface |
Attribut |
Valeur |
dlopen(), dlmopen(), dlclose() |
Sécurité des threads |
MT-Safe |
POSIX.1-2001 décrit
dlclose() et
dlopen(). La fonction
dlmopen() est une extension GNU.
Les drapeaux
RTLD_NOLOAD,
RTLD_NODELETE et
RTLD_DEEPBIND
sont des extensions GNU ; les deux premiers sont également
présents sur Solaris.
Une liste de table de liens définit un espace de noms isolé pour
la résolution de symboles par l'éditeur dynamique de liens.
À l'intérieur d'un espace de noms, les objets partagés
dépendants sont implicitement chargés selon les règles
usuelles, et les références aux symboles sont résolues
selon les règles usuelles, mais un telle résolution est
limitée aux définitions fournies aux objets qui ont
été chargés (explicitement et implicitement) dans
l'espace de noms.
La fonction
dlmopen() permet une isolation de chargement d'objet,
c'est-à-dire la capacité à charger un objet
partagé dans un nouvel espace de noms sans exposer le reste de
l'application aux symboles rendus disponibles par le nouvel objet. Notez que
l'utilisation du drapeau
RTLD_LOCAL n'est pas suffisante pour
réaliser cela puisque qu'il empêche les symboles des objets
partagés d'être disponibles à
tout autre objet
partagé. Dans certains cas, il peut être souhaitable de rendre
les symboles fournis par un objet partagé chargé dynamiquement
disponibles à d'autres objets (ou à un sous-ensemble)
partagés sans exposer ces symboles à l'application
entière. Cela peut être réalisé par l'utilisation
d'un espace de noms séparé et du drapeau
RTLD_GLOBAL.
La fonction
dlmopen() peut également être utilisée
pour fournir une meilleure isolation que le drapeau
RTLD_LOCAL. En
particulier, les objets partagés chargés avec
RTLD_LOCAL
peuvent être promus à
RTLD_GLOBAL s'ils sont des
dépendances d'un autre objet partagé chargé avec
RTLD_GLOBAL mis à part dans le cas (peu commun) où l'on a
un contrôle explicite sur les dépendances de tous les objets
partagés.
Les cas possibles d'utilisation de
dlmopen() sont des greffons où
l'auteur du cadriciel de chargement de greffon ne peut pas faire confiance aux
auteurs du greffon et ne souhaite pas que des symboles non définis du
cadriciel greffon soient résolus en symboles du greffon. Une autre
utilisation est de charger le même objet plus d'une fois. Sans
l'utilisation de
dlmopen(), cela exigerait la création de copies
distinctes du fichier de l'objet partagé. Grâce à
l'utilisation de
dlmopen(), cela peut être réalisé
par le chargement du même fichier d'objet partagé dans
différents espaces de noms.
L'implémentation de la glibc prend en charge un nombre maximal de
16 espaces de noms.
Les objets partagés peuvent exporter des fonctions en utilisant les
attributs de fonction
__attribute__((constructor)) et
__attribute__((destructor)). Les fonctions de construction sont
exécutées avant que
dlopen() ne renvoie, et les fonctions
de destruction sont exécutées avant que
dlclose() ne
renvoie. Un objet partagé peut exporter plusieurs constructeurs et
destructeurs et des priorités peuvent être associées
à chaque fonction pour déterminer l'ordre dans lequel elles
s'exécutent. Consultez les pages d'information de
gcc (sous
« Attributs de fonction ») pour plus
d'informations.
Une méthode plus ancienne d'obtenir (partiellement) le même
résultat passe par l'utilisation de deux symboles spéciaux
reconnus par l'éditeur de liens :
_init et
_fini.
Si un objet partagé chargé dynamiquement exporte une routine
nommée
_init(), alors son code est exécuté
après le chargement d'un objet partagé, avant le retour de
dlopen(). Si l'objet partagé exporte une routine nommée
_fini, elle est appelée juste avant le déchargement de
l'objet. Dans ce cas, vous voudrez éviter de lier l'exécutable
avec les fichiers de démarrage du système, qui contiennent des
versions par défaut de ces fichiers ; pour cela, vous pouvez
spécifier l'option
-nostartfiles à la ligne de commande
de
gcc(1).
L'utilisation de
_init et
_fini est rendue obsolète en
faveur des constructeurs et destructeurs susmentionnés, ce qui entre
autres avantages, permet la définition de plusieurs fonctions
d'initialisation et de finalisation.
Depuis la glibc 2.2.3,
atexit(3) peut être utilisée
pour enregistrer un gestionnaire de sortie qui sera automatiquement
appelé quand un objet partagé est déchargé.
Ces fonctions font partie de l'API dlopen, dérivée de SunOS.
Pour la version 2.24 de la glibc, spécifier le drapeau
RTLD_GLOBAL lors de l'appel à
dlmopen()
génère une erreur. De plus, spécifier
RTLD_GLOBAL
lors d'un appel à
dlopen() résulte en un plantage du
programme (
SIGSEGV) si l'appel est effectué depuis n'importe
quel objet chargé dans un autre espace de noms que celui initial.
Le programme suivant charge la bibliothèque de maths (de la glibc),
recherche l'adresse de la fonction
cos(3) et affiche le cosinus
de 2.0. Ci-dessous, un exemple de construction et d'exécution du
programme :
$ cc dlopen_demo.c -ldl
$ ./a.out
-0.416147
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <gnu/lib-names.h> /* Defines LIBM_SO (which will be a
string such as "libm.so.6") */
int
main(void)
{
void *handle;
double (*cosine)(double);
char *error;
handle = dlopen(LIBM_SO, RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
dlerror(); /* Supprime une erreur existante */
cosine = (double (*)(double)) dlsym(handle, "cos");
/* D'après le standard ISO C, la conversion de type entre un pointeur
de fonction et « void * », comme effectuée ci-dessus, produit des
résultats indéfinis.
POSIX.1-2003 et POSIX.1-2008 ont admis cet état de fait et proposé
le contournement ci-dessous :
*(void **) (&cosine) = dlsym(handle, "cos");
Cette conversion (lourde) de type est conforme au standard
ISO C and évitera tout avertissement du compilateur.
La révision technique 2013 de POSIX.1-2008 a amélioré la
situation en exigeant que les implémentations prennent en charge
la conversion du type « void * » vers un pointeur de fonction.
Cependant, certains compilateurs (par exemple gcc avec
l'option « -pedantic ») peuvent se plaindre de la conversion
effectuée dans ce programme. */
error = dlerror();
if (error != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
printf("%f\n", (*cosine)(2.0));
dlclose(handle);
exit(EXIT_SUCCESS);
}
ld(1),
ldd(1),
pldd(1),
dl_iterate_phdr(3),
dladdr(3),
dlerror(3),
dlinfo(3),
dlsym(3),
rtld-audit(7),
ld.so(8),
ldconfig(8)
pages Info de ld, pages Info de gcc
La traduction française de cette page de manuel a été
créée par Christophe Blaess
<
https://www.blaess.fr/christophe/>, Stéphan Rafin
<
[email protected]>, Thierry Vignaud
<
[email protected]>, François Micaux, Alain Portal
<
[email protected]>, Jean-Philippe Guérard
<
[email protected]>, Jean-Luc Coulon (f5ibh)
<
[email protected]>, Julien Cristau
<
[email protected]>, Thomas Huriaux <
[email protected]>,
Nicolas François <
[email protected]>, Florentin
Duneau <
[email protected]>, Simon Paillard
<
[email protected]>, Denis Barbier
<
[email protected]>, David Prévot <
[email protected]> et
Grégoire Scano <
[email protected]>
Cette traduction est une documentation libre ; veuillez vous reporter
à la
GNU
General Public License version 3 concernant les conditions de copie
et de distribution. Il n'y a aucune RESPONSABILITÉ LÉGALE.
Si vous découvrez un bogue dans la traduction de cette page de manuel,
veuillez envoyer un message à
[email protected]