DLOPEN(3)

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

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);

Lier avec -ldl.

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

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

  dlopen()
La fonction dlopen() charge la bibliothèque dynamique depuis le fichier dont le nom est fourni dans la chaîne filename (terminée par un octet 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 (voir 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 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 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.)

Une des deux valeurs suivantes doit être incluse dans flag :

RTLD_LAZY
    Effectuer un liage fainéant. 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é, il n'est jamais résolu. (le liage fainéant n'est effectué que pour les références aux fonctions ; les références aux variables sont toujours immédiatement liées lorsque 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 positionnée avec une chaîne non vide, tous les symboles non définis 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 dans cette bibliothèque seront disponibles pour les bibliothèques chargées ultérieurement.

RTLD_LOCAL
    c'est le contraire de RTLD_GLOBAL, et la valeur par défaut si aucun attribut n'est spécifié. Les symboles définis dans cette bibliothèque ne seront pas disponibles pour la résolution de référence dans les bibliothèques chargées ultérieurement.

RTLD_NODELETE (depuis la glibc 2.2)
    Ne pas décharger la bibliothèque lors d'un dlclose(). En conséquence, les variables statiques de la bibliothèque ne sont pas réinitialisées si la bibliothèque est rechargée avec dlopen() plus tard. Cet attribut n'est pas spécifié dans POSIX.1-2001.

RTLD_NOLOAD (depuis la glibc 2.2)
    Ne pas charger la bibliothèque. Cela peut être utilisé pour tester si la bibliothèque est déjà résidente (dlopen() renvoie NULL si ce n'est pas le cas, ou le descripteur de la bibliothèque si elle est résidente). Cet attribut peut être utilisé pour promouvoir les attributs sur une bibliothèque qui est déjà chargée. Par exemple, une bibliothèque qui a été précédemment chargée avec RTLD_LOCAL peut être réouverte avec RTLD_NOLOAD | RTLD_GLOBAL. Cet attribut n'est pas spécifié dans POSIX.1-2001.

RTLD_DEEPBIND (depuis glibc 2.3.4)
    Placer la portée de consultation des symboles dans cette bibliothèque devant la portée globale. Cela signifie qu'une bibliothèque autonome utilisera ses propres symboles de préférence aux symboles globaux de même nom contenus dans des bibliothèques qui ont déjà été chargées. Cet attribut 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 l'attribut RTLD_GLOBAL. Si l'édition des liens de l'exécutable a été faite avec l'option « -rdynamic » (ou, de manière synonyme, « --export-dynamic »), alors ses symboles globaux seront également employés pour résoudre les références de la 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 de symboles pour une bibliothèque précédemment chargée avec RTLD_LAZY.

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

  dlsym()
La fonction dlsym() prend un descripteur de bibliothèque dynamique renvoyée par dlopen() et un nom de symbole terminé par un octet nul, et renvoie l'adresse où ce symbole a été chargé. 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 en largeur d'abord à 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 pseudo-descripteurs 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 un encadrement pour une fonction se trouvant dans une autre bibliothèque partagée.

  dlclose()

La fonction dlclose() décrémente le nombre de références sur la bibliothèque dynamique dont le descripteur est handle. Si ce nombre descend à 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 si une erreur est survenue.

  Les symboles obsolètes _init() et _fini()
L'éditeur de liens reconnait les symboles spéciaux _init et _fini. Si une bibliothèque dynamique exporte une routine nommée _init(), 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 le lien avec les fichiers de démarrage du système, vous pouvez préciser le paramètre -nostartfiles sur 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 attributs __attribute__((constructor)) et __attribute__((destructor)). Voir les pages info de gcc 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.

  Extensions glibc : dladdr() et dlvsym()
La glibc a ajouté deux fonctions, qui ne sont pas décrites par POSIX, dont les prototypes sont :

#define _GNU_SOURCE
#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ù elle se trouve. L'information est stockée dans une structure Dl_info :

typedef struct {
    const char *dli_fname; /* File name of defining object */
    void       *dli_fbase; /* Load address of that object */
    const char *dli_sname; /* Name of nearest lower symbol */
    void       *dli_saddr; /* Exact value of nearest symbol */
} Dl_info;

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

La fonction dlvsym() effectue la même chose que dlsym() mais prend une chaîne version comme argument supplémentaire.

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

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é pour enregistrer un gestionnaire de sortie qui sera automatiquement appelé lorsque la bibliothèque sera déchargé.

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

EXEMPLE
Charger la bibliothèque mathématique et afficher le cosinus de 2.0 :
#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

Les bibliothèques qui exportent _init() et _fini() seront compilées comme suit, en prenant bar.c comme exemple :

    gcc -shared -nostartfiles -o bar bar.c

VOIR AUSSI
ld(1), ldd(1), dl_iterate_phdr(3), ld.so(8), feature_test_macros(7), ldconfig(8)
ld.so info pages, gcc info pages, ld info pages

TRADUCTION

Ce document est une traduction réalisée par Christophe Blaess <http://www.blaess.fr/christophe/> le 30 août 2000 et révisée le 23 décembre 2007.

L'équipe de traduction a fait le maximum pour réaliser une adaptation française de qualité. La version anglaise la plus à jour de ce document est toujours consultable via la commande : « LANG=C man 3 dlopen ». N'hésitez pas à signaler à l'auteur ou au traducteur, selon le cas, toute erreur dans cette page de manuel.