Existe-t-il un moyen élégant d’éviter dlsym lors de l’utilisation de dlopen en C?

J’ai besoin d’ouvrir dynamicment une bibliothèque partagée lib.so si une condition spécifique est remplie au moment de l’exécution. La bibliothèque contient ~ 700 fonctions et je dois charger tous leurs symboles.

Une solution simple consiste à définir les pointeurs de fonction sur tous les symboles contenus dans lib.so , à charger la bibliothèque à l’aide de dlopen et à obtenir les adresses de tous les symboles à l’aide de dlsym . Cependant, compte tenu du nombre de fonctions, le code mettant en œuvre cette solution est très lourd.

Je me demandais s’il existait une solution plus élégante et plus concise, avec éventuellement une utilisation appropriée des macros pour définir les pointeurs de fonction. Merci!

J’ai besoin d’ouvrir dynamicment une bibliothèque partagée lib.so si une condition spécifique est remplie au moment de l’exécution. La bibliothèque contient ~ 700 fonctions et je dois charger tous leurs symboles.

rôle de dlopen et dlsym

Lorsque vous dlopen une bibliothèque, toutes les fonctions définies par cette bibliothèque sont disponibles dans votre espace d’adressage virtuel (car tout le segment de code de cette bibliothèque est ajouté à votre espace d’adressage virtuel par dlopen appelant plusieurs fois mmap (2) ). Donc dlsym n’ajoute pas (ni ne charge) aucun code supplémentaire, il est déjà là. Si votre programme est en cours d’exécution dans le processus du pid 1234, essayez cat /proc/1234/maps après le succès de dlopen .

Ce que dlsym fournit, c’est la possibilité d’obtenir l’adresse de quelque chose dans cette bibliothèque partagée à partir de son nom, en utilisant une table de symboles dynamic dans ce plugin ELF . Si vous n’en avez pas besoin, vous n’avez pas besoin d’appeler dlsym .

Peut-être pourriez-vous simplement avoir, dans votre bibliothèque partagée, un large éventail de toutes les fonctions pertinentes (disponible sous forme de variable globale dans votre bibliothèque partagée). Ensuite, il vous suffira d’appeler une fois dlsym pour obtenir le nom de cette variable globale.

BTW, un constructeur (le constructor est un atsortingbut de fonction ), la fonction de votre plugin pourrait plutôt “enregistrer” certaines fonctions de ce plugin (dans une structure de données globale de votre programme principal; c’est ainsi que fonctionne la liaison dynamic Ocaml ); il est donc logique de ne jamais appeler dlsym et d’utiliser les fonctions de votre plugin.

Pour un plugin, ses fonctions de constructeur sont appelées au moment de dlopen (avant le retour de dlopen !) Et ses fonctions de destructeur sont appelées au moment de dlclose (avant le retour de dlclose ).

répéter les appels à dlsym

Il est de pratique courante d’utiliser dlsym plusieurs fois. Votre programme principal déclarera plusieurs variables (ou d’autres données, par exemple des champs dans une struct , des composants de tableau, etc.) et les remplira avec dlsym . Appeler dlsym plusieurs centaines de fois est vraiment rapide. Par exemple, vous pouvez déclarer des variables globales

 void*p_func_a; void*p_func_b; 

(vous les déclarerez souvent comme des pointeurs vers des fonctions de types appropriés, et peut-être différents; utilisez peut- être typedef pour déclarer des signatures )

et vous allez charger votre plugin avec

 void*plh = dlopen("/usr/lib/myapp/myplugin.so", RTLD_NOW); if (!plh) { fprintf(stderr, "dlopen failure %s\n", dlerror()); exit(EXIT_FAILURE); }; 

alors vous allez chercher des pointeurs de fonction avec

 p_func_a = dlsym(plh, "func_a"); if (!p_func_a) { fprintf(stderr, "dlsym func_a failure %s\n", dlerror()); exit(EXIT_FAILURE); }; p_func_b = dlsym(plh, "func_b"); if (!p_func_b) { fprintf(stderr, "dlsym func_b failure %s\n", dlerror()); exit(EXIT_FAILURE); }; 

(Bien sûr, vous pouvez utiliser des macros de préprocesseur pour raccourcir un tel code répétitif; les astuces X-macro sont pratiques.)

Ne soyez pas timide en appelant dlsym centaines de fois. Il est cependant important de définir et de documenter les conventions appropriées concernant votre plugin (par exemple, expliquez que chaque plugin doit définir func_a et func_b et quand sont-ils appelés par votre programme principal (en utilisant p_func_a etc.). Si vos conventions nécessitent des centaines de noms, c’est une mauvaise odeur.

agglomération de fonctions de plug-in dans une structure de données

Supposons donc que votre bibliothèque définit func_a , func_b , func_c1 , … func_c99 etc., etc., vous pourriez avoir un tableau global (POSIX permet de convertir des fonctions en void* mais la norme C11 ne le permet pas):

 const void* globalarray[] = { (void*)func_a, (void*)func_b, (void*)func_c1, /// etc (void*)func_c99, /// etc NULL /* final sentinel value */ }; 

et vous n’aurez alors besoin que d’ un seul symbole: globalarray ; Je ne sais pas si vous en avez besoin ou si vous en avez besoin. Bien sûr, vous pouvez utiliser des structures de données plus sophistiquées (par exemple, en imitant des tables vtables ou des tables d’opérations).


en utilisant une fonction constructeur dans votre plugin

Avec l’approche constructeur, et si votre programme principal fournit une fonction register_plugin_function qui fait les choses appropriées (par exemple, placez le pointeur dans une table de hachage globale, etc.), nous aurions dans le code du plugin une fonction déclarée comme

 static void my_plugin_starter(void) __atsortingbute__((constructor)); void my_plugin_starter(void) { register_plugin_function ("func", 0, (void*)func_a); register_plugin_function ("func", 1, (void*)func_b); /// etc... register_plugin_function ("func", -1, (void*)func_c1); /// etc... }; 

et avec un tel constructeur, func_a etc … pourrait être static ou avec une visibilité réduite. Nous n’avons alors plus besoin d’appel à dlsym depuis le programme principal (qui devrait fournir la fonction register_plugin_function ) lors du chargement du plugin.


références

Lisez plus attentivement le chargement dynamic, les plug-ins et les wikipages de l’ éditeur de liens . Lisez le livre de Levine’s Linkers and Loaders . Lisez elf (5) , proc (5) , ld-linux (8) , dlopen (3) , dlsym (3) , dladdr (3) . Jouez avec objdump (1) , nm (1) , readelf (1) .

Bien sûr, lisez le document Comment écrire des bibliothèques partagées de Drepper.

BTW, vous pouvez appeler dlopen puis dlsym un grand nombre de fois. Mon programme manydl.c génère du code C “aléatoire”, le comstack en tant que plug-in, puis le dlopen -ing et le dlsym -ing, et le répète. Cela démontre que (avec de la patience), vous pouvez avoir des millions de plug-ins dlopen dans le même processus, et vous pouvez appeler dlsym souvent.

Vous pouvez générer automatiquement des fonctions de trampoline pour tous les symboles de la bibliothèque dlopen . Les trampolines seraient considérés comme des fonctions normales dans une application, mais seraient redirigés en interne vers du code réel dans une bibliothèque. Voici un simple PoC de 5 minutes:

 $ cat lib.h // Dynamic library header #ifndef LIB_H #define LIB_H extern void foo(int); extern void bar(int); extern void baz(int); #endif $ cat lib.c // Dynamic library implementation #include  void foo(int x) { printf("Called library foo: %d\n", x); } void bar(int x) { printf("Called library baz: %d\n", x); } void baz(int x) { printf("Called library baz: %d\n", x); } $ cat main.c // Main application #include  #include  #include  // Should be autogenerated void *fptrs[100]; void init_trampoline_table(void *h) { fptrs[0] = dlsym(h, "foo"); fptrs[1] = dlsym(h, "bar"); fptrs[2] = dlsym(h, "baz"); } int main() { void *h = dlopen("./lib.so", RTLD_LAZY); init_trampoline_table(h); printf("Calling wrappers\n"); foo(123); bar(456); baz(789); printf("Returned from wrappers\n"); return 0; } $ cat trampolines.S // Trampoline code. // Should be autogenerated. Each wrapper gets its own index in table. // TODO: abort if table wasn't initialized. .text .globl foo foo: jmp *fptrs .globl bar bar: jmp *fptrs+8 .globl baz baz: jmp *fptrs+16 $ gcc -fPIC -shared -O2 lib.c -o lib.so $ gcc -I. -O2 main.c trampolines.S -ldl $ ./a.out Calling wrappers Called library foo: 123 Called library baz: 456 Called library baz: 789 Returned from wrappers 

Notez que le code de l’application dans main.c utilise uniquement des fonctions locales (qui encapsulent les fonctions de la bibliothèque) et ne doit pas du tout gâcher les pointeurs de fonction (à l’exception de l’initialisation de la table de redirection au démarrage qui devrait de toute façon être du code généré automatiquement).

EDIT: J’ai créé un outil autonome Implib.so pour automatiser la création de bibliothèques de stub comme dans l’exemple ci-dessus. Cela s’est avéré être plus ou moins équivalent aux bibliothèques d’importation Windows DLL bien connues.