table of contents
DLOPEN(3) | Manuel du programmeur Linux | DLOPEN(3) |
NOM¶
dlclose, dlopen, dlmopen - Ouvrir et fermer un objet partagé
SYNOPSIS¶
#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);
Effectuer l'édition des liens avec l'option -ldl.
DESCRIPTION¶
dlopen()¶
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) :
- o
- (ELF seulement) si le fichier exécutable pour le programme appelant contient la balise DT_RPATH mais pas la balise DT_RUNPATH, les répertoires listés dans la balise DT_RPATH seront parcourus.
- o
- 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.
- o
- (ELF seulement) si le fichier exécutable pour le programme appelant contient la balise DT_RUNPATH, les répertoires listés dans cette balise seront parcourus.
- o
- 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.
- o
- 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 rechargé 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 tous les objets partagés chargés par dlopen() avec l'attribut RTLD_GLOBAL.
Les références externes dans l'objet partagé sont résolues en utilisant les objets partagés dans la liste de dépendances de l'objet et de n'importe quel autre objet précédemment ouvert avec le drapeau RTLD_GLOBAL. Si les liens de l'exécutable ont été édités avec le drapeau « -rdynamic » (ou, de façon synonyme, « --export-dynamic »), alors les symboles globaux dans l'exécutable seront aussi utilisés pour résoudre les références dans un objet partagé chargé dynamiquement.
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. Tout renvoi d'initialisation (voir ci-dessous) est appelé une seule fois. Néanmoins, un appel ultérieur de dlopen() qui charge le même objet partagé avec RTLD_NOW peut forcer une résolution de symbole pour un objet partagé chargé antérieurement avec RTLD_LAZY.
Si dlopen() échoue pour une raison quelconque, elle renvoie NULL.
dlmopen()¶
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.
dlclose()¶
La fonction dlclose() décrémente le nombre de références d'une bibliothèque dynamique dont le descripteur est handle. Si ce nombre atteint zéro et si aucune autre bibliothèque n'emploie des symboles exportés par celle-ci, elle est déchargée. Tous les objets partagés qui ont été automatiquement chargés lorsque dlopen() a été appelé sur l'objet référencé par handle sont fermés 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.
VALEUR RENVOYÉE¶
En cas de succès, dlopen() et dlmopen() renvoient un gestionnaire non nul pour la bibliothèque chargée. 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).
VERSIONS¶
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.
ATTRIBUTS¶
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 |
CONFORMITɶ
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.
NOTES¶
dlmopen() et espace de noms¶
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.
Fonctions d'initialisation et de finalisation¶
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é.
Historique¶
Ces fonctions font partie de l'API dlopen, dérivée de SunOS.
BOGUES¶
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.
EXEMPLE¶
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
Source du programme¶
#include <stdio.h> #include <stdlib.h> #include <dlfcn.h> #include <gnu/lib-names.h> /* Définit LIBM_SO (qui est une
chaîne comme "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 (aussi appelée
POSIX.1-2013) 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); }
VOIR AUSSI¶
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
COLOPHON¶
Cette page fait partie de la publication 4.16 du projet man-pages Linux. Une description du projet et des instructions pour signaler des anomalies et la dernière version de cette page peuvent être trouvées à l'adresse https://www.kernel.org/doc/man-pages/.
TRADUCTION¶
La traduction française de cette page de manuel a été créée par Christophe Blaess <https://www.blaess.fr/christophe/>, Stéphan Rafin <stephan.rafin@laposte.net>, Thierry Vignaud <tvignaud@mandriva.com>, François Micaux, Alain Portal <aportal@univ-montp2.fr>, Jean-Philippe Guérard <fevrier@tigreraye.org>, Jean-Luc Coulon (f5ibh) <jean-luc.coulon@wanadoo.fr>, Julien Cristau <jcristau@debian.org>, Thomas Huriaux <thomas.huriaux@gmail.com>, Nicolas François <nicolas.francois@centraliens.net>, Florentin Duneau <fduneau@gmail.com>, Simon Paillard <simon.paillard@resel.enst-bretagne.fr>, Denis Barbier <barbier@debian.org>, David Prévot <david@tilapin.org> et Grégoire Scano <gregoire.scano@malloc.fr>
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 à debian-l10n-french@lists.debian.org.
15 septembre 2017 | Linux |