1. NOM

dladdr, dlclose, dlerror, dlopen, dlsym, dlvsym - Interface de programmation pour le chargeur de bibliothèques dynamiques

2. SYNOPSIS

#include <dlfcn.h>

void *dlopen(const char *filename, int flag);

char *dlerror(void);

void *dlsym(void *handle, const char *symbol);

int dlclose(void *handle);

Effectuez l'édition des liens avec l'option -ldl.

3. DESCRIPTION

Les quatre fonctions dlopen(), dlsym(), dlclose(), dlerror() implémentent l'interface pour le chargeur de bibliothèques dynamiques.

3.1. dlerror()

La fonction dlerror() renvoie une chaîne de caractères, compréhensible par l'homme, décrivant la dernière erreur survenue dans dlopen(), dlsym() ou dlclose() depuis le dernier appel à dlerror(). Elle renvoie NULL si aucune erreur n'est survenue depuis l'initialisation ou depuis son dernier appel.

3.2. dlopen()

La fonction dlopen() charge la bibliothèque dynamique dont le nom est fourni dans la chaîne filename (terminée par un caractère nul) et renvoie un descripteur opaque (« handle ») représentant la bibliothèque dynamique. 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, le chargeur dynamique cherche la bibliothèque de la façon suivante (consultez ld.so(8) pour plus de détails) :

o 4 (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-UID et set-GID).

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 (maintenu 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 la bibliothèque a des dépendances sur d'autres bibliothèques partagées, celles-ci seront automatiquement chargées par le chargeur dynamique, en utilisant les mêmes règles. (Le processus peut être récursif si ces bibliothèques 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. Résoudre seulement les symboles dont 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 bindings paresseux ne sont seulement effectués que pour les références de fonctions ; les références de variables sont toujours immédiatement liées quand la bibliothèque est chargée).
  • RTLD_NOW
        Si cette valeur est spécifiée, ou que la variable d'environnement LD_BIND_NOW est définie avec une chaîne non vide, tous les symboles non définis de la bibliothèque 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 cette bibliothèque seront disponibles pour la résolution des symboles des futurs chargements de bibliothèques.
  • RTLD_LOCAL
        C'est la réciproque de RTLD_GLOBAL, et le comportement par défaut si aucun drapeau n'est spécifié. Les symboles définis dans cette bibliothèque ne sont pas disponibles pour résoudre les références des chargements de bibliothèques futurs.
  • RTLD_NODELETE (depuis la glibc 2.2) Ne pas décharger la bibliothèque lors de dlclose(). En conséquence, les variables statiques de la bibliothèque ne sont pas réinitialisées si la bibliothèque est chargée ultérieurement avec dlopen() . Ce drapeau n'est pas spécifié dans POSIX.1-2001.
  • RTLD_NOLOAD (depuis la glibc 2.2) Ne pas charger la bibliothèque. Ceci peut être utilisé pour tester si la bibliothèque n'est pas déjà chargée (dlopen() renvoie NULL si elle n'est pas chargée, ou le descripteur de la bibliothèque si elle déjà chargée). Ce drapeau peut aussi être utilisé pour promouvoir les drapeaux d'une bibliothèque déjà chargée. Par exemple, une bibliothèque qui a été chargée avec RTLD_LOCAL peut être de nouveau ouverte avec RTLD_NOLOAD | RTLD_GLOBAL. Ce drapeau n'est pas spécifié dans POSIX.1-2001.
  • RTLD_DEEPBIND (depuis la glibc 2.3.4) Placer l'espace de recherche des symboles de cette bibliothèque avant l'espace global. Cela signifie qu'une bibliothèque autonome utilisera ses propres symboles de préférence aux symboles globaux de même noms contenus dans les bibliothèques déjà chargées. Ce drapeau n'est pas spécifié dans POSIX.1-2001.

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 toutes les bibliothèques partagées chargées au démarrage du programme, puis dans toutes les bibliothèques partagées chargées par dlopen() avec l'attribut RTLD_GLOBAL.

Les références externes de la bibliothèque sont résolues en utilisant les bibliothèques mentionnées dans sa liste de dépendances, et toutes les autres bibliothèques éventuellement ouvertes auparavant avec le drapeau RTLD_GLOBAL. Si l'édition des liens de l'exécutable a été faite avec l'option « -rdynamic » (ou, de manière synonyme, avec « --export-dynamic »), alors les symboles globaux du programme seront également utilisés pour résoudre les références d'une bibliothèque chargée dynamiquement.

Si la même bibliothèque est chargée une nouvelle fois avec dlopen(), le même descripteur sera renvoyé. Un compte du nombre de chargements est toutefois conservé afin d'éviter de la décharger avant que la fonction dlclose() n'ait été appelée autant de fois que dlopen() a réussi. La routine _init, si elle existe, est appelée une seule fois. Mais un appel postérieur avec RTLD_NOW peut forcer la résolution des symboles pour une bibliothèque précédemment chargée avec RTLD_LAZY.

Si dlopen() échoue pour une raison quelconque, elle renvoie NULL.

3.3. dlsym()

La fonction dlsym() prend comme arguments, un « descripteur » de bibliothèque dynamique renvoyé par dlopen() et un nom de symbole terminé par un caractère nul, et renvoie l'adresse où ce symbole a été chargé en mémoire. Si le symbole n'est pas trouvé, soit dans la bibliothèque spécifiée, soit dans n'importe quelle bibliothèque chargée automatiquement par dlopen() lorsque cette bibliothèque a été chargée, dlsym() renvoie NULL. (La recherche effectuée par dlsym() est d'abord en largeur à travers l'arbre des dépendances de ces bibliothèques). Le symbole pouvant légitimement avoir la valeur NULL (la valeur NULL renvoyée par dlsym() n'indique pas nécessairement une erreur), la bonne manière de vérifier si une erreur s'est produite est d'appeler dlerror() pour effacer toute ancienne condition d'erreur, puis d'appeler dlsym() et appeler une nouvelle fois dlerror() en sauvegardant sa valeur de retour dans une variable et vérifier si la valeur sauvegardée n'est pas NULL.

Il y a deux pseudodescripteurs spéciaux : RTLD_DEFAULT et RTLD_NEXT. Le premier recherche la première occurrence du symbole désiré en utilisant l'ordre de recherche des bibliothèques par défaut. Le second recherche l'occurrence suivante d'une fonction à partir de la bibliothèque en cours. Ceci permet de fournir une enveloppe pour une fonction se trouvant dans une autre bibliothèque partagée.

3.4. 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.

La fonction dlclose() renvoie 0 si elle réussit, et une valeur non nulle en cas d'erreur.

3.5. Les symboles obsolètes _init() et _fini()

L'éditeur de liens reconnaît les symboles spéciaux _init et _fini. Si une bibliothèque dynamique exporte une routine nommée _init(), alors son code est exécuté après le chargement, avant le retour de dlopen(). Si la bibliothèque exporte une routine nommée _fini, elle est appelée juste avant le déchargement. Au cas où vous voudriez éviter de lier l'exécutable avec les fichiers de démarrage du système, vous pouvez spécifier le paramètre -nostartfiles à la ligne de commande de gcc(1).

L'utilisation de ces routines ou des options gcc -nostartfiles ou -nostdlib n'est pas recommandée. Il peut en résulter un comportement non désiré tant que les routines constructeur/destructeur ne sont pas exécutées (à moins que des mesures spéciales ne soient prises).

À la place, les bibliothèques devraient exporter les routines en utilisant les fonctions attribut __attribute__((constructor)) et __attribute__((destructor)). Consultez la documentation de gcc au format Info pour plus d'information sur celles-ci. Les routines constructeur sont exécutées avant que dlopen revienne et les routines destructeur sont exécutées avant que dlclose revienne.

3.6. Extensions de la glibc :dladdr() et dlvsym()

La glibc a ajouté deux fonctions, qui ne sont pas décrites par POSIX, dont les prototypes sont :

 
Sélectionnez
#define _GNU_SOURCE         /* Consultez feature_test_macros(7) */
#include <dlfcn.h>
int dladdr(void *addr, Dl_info *info);
void *dlvsym(void *handle, char *symbol, char *version);

La fonction dladdr() prend un pointeur vers une fonction et essaie de résoudre le nom et le fichier où il se trouve. L'information est stockée dans une structure Dl_info :

 
Sélectionnez
typedef struct {
    const char *dli_fname; /* Chemin du fichier de l'objet partagé
                              contenant l'adresse */
    void       *dli_fbase; /* Adresse à laquelle l'objet partagé
                              est chargé */
    const char *dli_sname; /* Nom du symbole le plus proche avec
                              une adresse inférieure à addr */
    void       *dli_saddr; /* Adresse exacte du symbole dont
                              le nom est dli_sname */
} Dl_info;

Si aucun symbole correspondant à l'adresse addr ne peut être trouvé, dli_sname et dli_saddr sont définis à NULL.

dladdr() renvoie 0 en cas d'erreur et une valeur non nulle en cas de succès.

La fonction dlvsym(), fournie par la glibc depuis la version 2.1, effectue la même chose que dlsym() mais prend une version sous forme de chaîne comme argument supplémentaire.

4. CONFORMITÉ

POSIX.1-2001 décrit dlclose(), dlerror(), dlopen() et dlsym().

5. NOTES

Les symboles RTLD_DEFAULT et RTLD_NEXT sont définis dans <dlfcn.h> seulement si _GNU_SOURCE a été définie avant l'inclusion. Depuis la glibc 2.2.3, atexit(3) peut être utilisée pour enregistrer un gestionnaire de sortie qui sera automatiquement appelé quand une bibliothèque sera déchargée.

5.1. Historique

L'interface standard dlopen provient de SunOS. Ce système possède également dladdr() mais pas dlvsym().

6. BOGUES

Quelquefois, les pointeurs de fonctions passés à dladdr() peuvent vous surprendre. Sur certaines architectures (notablement i386 et x86_64), dli_fname et dli_fbase peuvent pointés sur l'objet depuis lequel vous appelez dladdr(), même si la fonction utilisée en paramètre semble provenir d'une bibliothèque liée dynamiquement.

Le problème est que le pointeur de fonction ne sera résolu que lors de la compilation, mais pointe simplement vers la section de l'objet original plt (table de procédure d'édition des liens), qui redirige l'appel après avoir demandé à l'éditeur de liens dynamique de résoudre le symbole). Un contournement consiste à compiler le code pour qu'il soit indépendant de son adressage : dans ce cas le compilateur ne peut pas préparer le pointeur à la compilation, et de nos jours, gcc(1) générera du code qui chargera juste l'adresse finale du symbole depuis la table GOT (table d'offset globale) lors de l'exécution, avant de la passer à dladdr().

7. EXEMPLE

Charger la bibliothèque mathématique et afficher le cosinus de 2,0 :

 
Sélectionnez
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int
main(int argc, char **argv)
{
    void *handle;
    double (*cosine)(double);
    char *error;
    handle = dlopen("libm.so", RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "%s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    dlerror();    /* Clear any existing error */
    /* Writing: cosine = (double (*)(double)) dlsym(handle, "cos");
       would seem more natural, but the C99 standard leaves
       casting from "void *" to a function pointer undefined.
       The assignment used below is the POSIX.1-2003 (Technical
       Corrigendum 1) workaround; see the Rationale for the
       POSIX specification of dlsym(). */
    *(void **) (&cosine) = dlsym(handle, "cos");
    if ((error = dlerror()) != NULL)  {
        fprintf(stderr, "%s\n", error);
        exit(EXIT_FAILURE);
    }
    printf("%f\n", (*cosine)(2.0));
    dlclose(handle);
    exit(EXIT_SUCCESS);
}

Supposons que le programme s'appelle « foo.c », on doit le compiler ainsi :

gcc -rdynamic -o foo foo.c -ldl

Une bibliothèque (bar.c dans l'exemple suivant) qui exporte _init() et _fini() sera compilée comme suit :

gcc -shared -nostartfiles -o bar bar.c

8. VOIR AUSSI

ld(1), ldd(1), dl_iterate_phdr(3), rtld-audit(7), ld.so(8), ldconfig(8) les pages Info de ld.so, gcc, ld

9. COLOPHON

Cette page fait partie de la publication 3.52 du projet man-pages Linux. Une description du projet et des instructions pour signaler des anomalies peuvent être trouvées à l'adresse http://www.kernel.org/doc/man-pages/.

10. TRADUCTION

Depuis 2010, cette traduction est maintenue à l'aide de l'outil po4a <http://po4a.alioth.debian.org/> par l'équipe de traduction francophone au sein du projet perkamon <http://perkamon.alioth.debian.org/>.

Christophe Blaess <http://www.blaess.fr/christophe/> (1996-2003), Alain Portal <http://manpagesfr.free.fr/> (2003-2006). Florentin Duneau et l'équipe francophone de traduction de Debian (2006-2009).

Veuillez signaler toute erreur de traduction en écrivant à < >.

Vous pouvez toujours avoir accès à la version anglaise de ce document en utilisant la commande « LC_ALL=C man <section> <page_de_man> ».