How to use dlsym reliably when you have duplicate characters?

Good evening, I'm currently working on a plug-in system in C ++ / Linux based on the Plux.net model.

To keep it simple, I usually declare a character (allows its pluginInformation) with extern C (to untie), and my plugin manager looks for that character in a predefined import (.so).

The fact is that the main application declares the same symbol, and not just that, but any of its dependencies can have a symbol. (as in this plugininformation modules can publish traffic jams and / or slots).

So, when my PluginManager starts up, first try to find the character in the main program (passing NULL to dlopen ), then it tries to find the character in any of its dependencies (using dl_iterate_phdr ). And the last one: dlopen is a set of import settings (it will read the .so path that the user has configured, dlopen and finally dlsym the pluginInformation symbol).

Then, the pluginInformation assembly found in all modules is used to create extension 3.

If I declare a symbol in the main program and load the import using dlopen , it works (while I skip the RTLD_DEEPBIND flag when expanding the import).

But for the application dependencies, I have no way to pass the flag (I can, but it does nothing), since these .sos were loaded when the application started.

Now when I try to use any of the characters obtained from the dependencies (those that load at startup), I get a segmentation error. I assume the problem is that I have multiple characters with the same name in the character table, it is strange that it seems to correctly identify that there are multiple characters, and even gives me the correct .so path where the character is declared, but as soon as I access the symbol, a segmentation error occurs. If I declare only a character in the main program or in one of the dependencies, everything works correctly.

How can I control duplicate characters between the main program and import strat up with dlsym ?.

I thought about saving the mechanism, and then just try to find my character breaking through the character table, but I'm not sure if this is even possible (listing all the characters in the module programmatically).

PD: Sorry, I did not send the code, but I am not at home right now, I hope that the description of what I'm trying to do is clear enough if I can not publish the code tomorrow.

+6
source share
1 answer

Here is an alternative approach.

The application itself exports one or more functions for registering plug-in elements. For instance:

int register_plugin_item(const char *const text, const char *const icon, void (*enter)(void *), void (*click)(void *), void (*leave)(void *), void *data); 

For a registered item, there are two slots ( text and icon ), three functional slots ( enter , click and leave ) and an opaque link provided to functions as a parameter when called.

(Note that you need to use the -rdynamic compiler -rdynamic when compiling the main application (an object file that implements the above function) to ensure that the linker adds the register_plugin_item character to the dynamic symbol table.)

Each plugin calls the register_plugin_item() function for each of the elements it wants in the constructor function (which automatically starts when the library loads). It is perhaps often useful for a function to first check the environment in which it works in order to determine which functions to register or which optimized options for functions to use for each element of the plugin.

Here is a trivial plugin example. Note that all symbols are static , so the plugin does not pollute the dynamic symbol table or cause symbol conflicts.

 #include <stdlib.h> #include <stdio.h> extern int register_plugin_item(const char *const, const char *const, void (*enter)(void *), void (*click)(void *), void (*leave)(void *), void *); static void enter(void *msg) { fprintf(stderr, "Plugin: Enter '%s'\n", (char *)msg); } static void leave(void *msg) { fprintf(stderr, "Plugin: Leave '%s'\n", (char *)msg); } static void click(void *msg) { fprintf(stderr, "Plugin: Click '%s'\n", (char *)msg); } static void init(void) __attribute__((constructor)); static void init(void) { register_plugin_item("one", "icon-one.gif", enter, leave, click, "1"); register_plugin_item("two", "icon-two.gif", enter, leave, click, "2"); } 

The above plugin exports two elements. For testing, create at least a couple of variants of the above; You will see that there are no symbol conflicts, even if the plugins use the same (static) variables and function names.

Here is an example application that downloads the specified plugins and validates each registered item:

 #include <stdlib.h> #include <dlfcn.h> #include <string.h> #include <errno.h> #include <stdio.h> struct item { struct item *next; const char *text; const char *icon; void *data; void (*enter)(void *); void (*leave)(void *); void (*click)(void *); }; static struct item *list = NULL; int register_plugin_item(const char *const text, const char *const icon, void (*enter)(void *), void (*click)(void *), void (*leave)(void *), void *data) { struct item *curr; curr = malloc(sizeof *curr); if (!curr) return ENOMEM; curr->text = text; curr->icon = icon; curr->data = data; curr->enter = enter; curr->leave = leave; curr->click = click; /* Prepend to list */ curr->next = list; list = curr; return 0; } int main(int argc, char *argv[]) { int arg; void *handle; struct item *curr; if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { fprintf(stderr, "\n"); fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]); fprintf(stderr, " %s PLUGIN.so ... \n", argv[0]); fprintf(stderr, "\n"); fprintf(stderr, "Please supply full plugin paths, unless\n"); fprintf(stderr, "the plugins reside in a standard library directory,\n"); fprintf(stderr, "or in a directory listed in LD_LIBRARY_PATH.\n"); fprintf(stderr, "\n"); return 1; } for (arg = 1; arg < argc; arg++) { handle = dlopen(argv[arg], RTLD_NOW); if (handle != NULL) fprintf(stderr, "%s: Loaded.\n", argv[arg]); else fprintf(stderr, "%s.\n", dlerror()); /* Note: We deliberately "leak" the handle, * so that the plugin is not unloaded. */ } for (curr = list; curr != NULL; curr = curr->next) { if (curr->text) printf("Item '%s':\n", curr->text); else printf("Unnamed item:\n"); if (curr->icon) printf("\tIcon is '%s'\n", curr->icon); else printf("\tNo icon\n"); if (curr->data) printf("\tCustom data at %p\n", curr->data); else printf("\tNo custom data\n"); if (curr->enter) printf("\tEnter handler at %p\n", curr->enter); else printf("\tNo enter handler\n"); if (curr->click) printf("\tClick handler at %p\n", curr->click); else printf("\tNo click handler\n"); if (curr->leave) printf("\tLeave handler at %p\n", curr->leave); else printf("\tNo leave handler\n"); if (curr->enter || curr->click || curr->leave) { printf("\tTest calls:\n"); if (curr->enter) curr->enter(curr->data); if (curr->click) curr->click(curr->data); if (curr->leave) curr->leave(curr->data); printf("\tTest calls done.\n"); } } return 0; } 

If the application is app.c , and you have plugins plugin-foo.c and plugin-bar.c , you can compile them, for example,

 gcc -W -Wall -rdynamic app.c -ldl -o app gcc -W -Wall -fpic -c plugin-foo.c gcc -shared -Wl,-soname,plugin-foo.so plugin-foo.o -o plugin-foo.so gcc -W -Wall -fpic -c plugin-bar.c gcc -shared -Wl,-soname,plugin-bar.so plugin-bar.o -o plugin-bar.so 

and execute for example

 ./app --help ./app ./plugin-foo.so ./app ./plugin-foo.so ./plugin-bar.so 

Please note that if the same plugin is defined more than once, the constructor is executed only once for this library. There will be no duplicate registrations.


The interface between the plugins and the application is completely up to you. There is only one function in this example. A real application is likely to have more. An application can also export other functions, for example, for a plugin for requesting application configuration.

Designing a good interface is a completely different topic and certainly deserves at least as much thought as you put into the implementation.

The Plux.NET plugin platform allows plugins to also export their own slots. This alternative approach allows this in many ways. One of them is to export the plugin registration function, that is, to register plugins instead of individual elements, which takes a pointer to a function:

 int register_plugin(const char *const name, int (*extend)(const char *const, ...)); 

If the plugin provides slots, it provides its registration function as a pointer to the extend function. The application also exports a function, for example

 int plugin_extend(const char *const name, ...); 

which plugins can use to call other plugin registration functions. (The implementation of plugin_extend() in the main application includes finding the appropriate extend function already registered, and then calling it / them.)

An implementation that allows plugins to export slots makes the implementation quite complicated. In particular, when and in what order should the slots exported by the plugins be available? Is there a specific order in which plugins should be loaded to make sure that all possible slots are exported? What happens if there is a circular addiction? Should plugins indicate which other plugins they use before registration starts?

If each plugin is a separate object that does not export a single slot, it only connects to the main application slots, you avoid most of the implementation complexity.

The order in which registered items are registered is a part that you probably need to think about. The above sample program uses a linked list in which the items end in reverse order than the registration order, and the registration order is the same as the order in which the plugin file names are first specified on the command line. If you have a plugin directory that is automatically scanned (using, for example, the opendir() / readdir() / dlopen() / closedir() ), then the registration order of the plugin is semi-random (depending on the file system, usually changing only when plugins are added or removed).

Corrections? Any questions? Comments?

+5
source

Source: https://habr.com/ru/post/953810/


All Articles