重复符号后如何可靠地使用dlsym? [英] How to use dlsym reliably when you have duplicated symbols?

查看:124
本文介绍了重复符号后如何可靠地使用dlsym?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

晚上好,我目前正在基于 Plux.net 模型.

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

为简单起见,我基本上声明了一个带有extern C的符号(将其命名为pluginInformation)(以取消损坏),并且我的插件管理器在预配置的导入文件(.so)中查找该符号.

To keep it simple, I basicly declare a symbol (lets call it pluginInformation) with extern C (to unmangle) and my plugin manager look for that symbol in the pre configured imports (.so).

问题是主应用程序声明了相同的符号,不仅如此,而且它具有的任何依赖项也可能具有该符号. (由于使用了该pluginInformation,模块可以发布插头和/或插槽).

The thing is that the main application declares the same symbol, not only that but any dependency it have may have the symbol aswell. (since in this pluginInformation, modules can publish plugs and/or slots).

因此,当我的PluginManager启动时,它首先尝试在主程序中查找符号(将NULL传递给 dlopen ),然后尝试查找其任何依赖项中的符号(使用 dl_iterate_phdr ).最后,它会 dlopen 一组配置导入(它将读取的路径.以便用户配置 dlopen 并最终

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

然后使用在所有模块中找到的pluginInformation集合来构建扩展名三.

The collection of pluginInformation found in all the modules is used then to build the extension three.

如果我在主程序中声明该符号并使用 dlopen 加载导入,它有效(只要在倾斜导入时通过了RTLD_DEEPBIND标志即可.)

If I declare the symbol in the main program and load the imports using dlopen, it works (as long as I pass the flag RTLD_DEEPBIND when dlopening the imports).

但是对于应用程序依赖项,我没有选择传递标志的选项(我可以,但是它没有做任何事情),因为此.sos是在应用程序启动时加载的.

But for the application dependencies I dont have the option of passing the flag (I can but it doesnt do anything) since this .sos were loaded at the start up of the application.

现在,当我尝试使用从依赖项中获得的任何符号(启动时加载的符号)时,都会遇到分段错误.我认为问题是符号表中有多个具有相同名称的符号,奇怪的是,它似乎可以正确识别出多个符号,甚至还提供了.so的正确路径.声明,但是一旦我访问该符号,就会发生分段错误.如果我只在主程序或其中一个依赖项中声明符号,则一切正常.

Now when I try to use any of the symbols I got from the dependencies (the ones loaded at start up) I get a segmentation fault. I assume the problem is that I have several symbols with the same name in the symbol table, the weird thing is that it seems to correctly identify that there are several symbols and it even gives me the correct path of the .so where the symbol is declared, but as soon as I access the symbol a segmentation fault occurs. If I only declare the symbol in the main program or in one of the dependencies everything works correctly.

如何使用dlsym管理主程序和分层导入之间的重复符号?

How can I manage duplicate symbols between the main program and the strat up imports with dlsym?.

我一直在想保持整齐,然后尝试找到我的符号放入符号表,但是我不确定这是否有可能(以编程方式列出模块中的所有符号).

I have been thinking in keep the mangling and then just try to find my symbol pasring the symbol table, but im not sure this is even possible (listing all the symbols in a module programmatically).

PD:对不起,我没有发布任何代码,但是我现在不在家里,我希望我试图做的事情的描述很清楚,如果没有的话我明天可以发布一些代码.

PD: Sorry I didnt post any code, but im not at home right now, I hope the description of what im trying to do is clear enough, if not I can post some code tomorrow.

推荐答案

这是另一种方法.

应用程序本身导出一个或多个插件项注册功能.例如:

The application itself exports one or more plugin item registration functions. For example:

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

每个注册项目有两个字符串槽(texticon),三个功能槽(enterclickleave),以及为这些功能提供的不透明引用作为调用时的参数.

Per registered item, there are two string slots (text and icon), three function slots (enter, click, and leave), and an opaque reference that is given to the functions as a parameter when called.

(请注意,在编译主应用程序(实现上述功能的目标文件)时,需要使用-rdynamic编译器选项,以确保链接器将register_plugin_item符号添加到动态符号表中. )

(Note that you'll need to use the -rdynamic compiler option when compiling the main application (the object file implementing the above function), to make sure the linker adds the register_plugin_item symbol to the dynamic symbol table.)

每个插件在构造函数(在库加载时自动运行)中为其所需的每个项目调用register_plugin_item()函数.对于该功能,可能首先要检查其运行的环境,以确定要注册的功能,或者为每个插件项目使用哪些优化的功能变体,这都是可能的,而且通常是有用的.

Each plugin calls the register_plugin_item() function for each of the items it wants, in a constructor function (that is automatically run at library load time). It is possible, and often useful, for the function to first examine the environment it runs in to determine which features to register, or which optimized variants of functions to use for each plugin item.

这是一个简单的示例插件.请注意所有符号都是static,这样插件就不会污染动态符号表,也不会引起任何符号冲突.

Here is a trivial example plugin. Note how all the symbols are static, so that the plugin does not pollute the dynamic symbol table, or cause any 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 items. 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 same (static) variables and function names.

这是一个示例应用程序,它加载指定的插件并测试每个注册的项目:

Here is an example application that loads the specified plugins, and tests 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;
}

如果应用程序是app.c,并且您有插件plugin-foo.cplugin-bar.c,则可以使用例如

If the application is app.c, and you have plugins plugin-foo.c and plugin-bar.c, you can compile them using e.g.

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

并使用例如

./app --help

./app ./plugin-foo.so

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

请注意,如果多次定义同一个插件,则构造函数仅对该库执行一次.没有重复的注册.

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

插件和应用程序之间的接口完全由您决定.在此示例中,只有一个功能.一个真正的应用程序可能会有更多.该应用程序还可以导出其他功能,例如用于插件以查询应用程序配置.

The interface between the plugins and the application is completely up to you. In this example, there is only one function. A real application would probably have more. The application can also export other functions, for example for the plugin to query application configuration.

设计一个好的界面是一个完全不同的主题,并且绝对应至少与实现时一样多.

Designing a good interface is a whole different topic, and definitely deserves at least as much thought as you put in the implementation.

Plux.NET插件平台也允许插件导出自己的插槽.这种替代方法可以通过多种方式实现这一点.其中之一是导出插件注册功能-即用于注册 plugins 而不是单个项目-该功能需要一个函数指针:

The Plux.NET plugin platform allows plugins to export their own slots, too. This alternate approach allows that in many ways. One of them is to export a plugin registration function -- that is, for registering plugins instead of individual items -- that takes a function pointer:

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

如果插件提供了插槽,则它将提供自己的注册功能作为extend函数指针.该应用程序还导出功能,例如

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

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

插件可以用来调用其他插件的注册功能的

. (在主应用程序中plugin_extend()的实现涉及搜索已经注册的合适的extend函数,然后对其进行调用.)

that the plugins can use to call other plugins' registration functions. (The implementation of plugin_extend() in the main application involves searching for a suitable extend function already registered, then calling it/them.)

在实施方面,允许插件导出插槽会使实施复杂得多.特别是,插件导出的插槽何时以及以什么顺序可用?是否有特定的插件必须加载顺序,以确保导出所有可能的插槽?如果存在循环依赖关系会怎样?插件应该在注册开始之前指定他们依赖的其他插件吗?

Implementation-wise, allowing plugins to export slots complicates implementation quite a bit. In particular, when and in which order should the slots exported by the plugins become available? Is there a specific order in which plugins must be loaded, to make sure all possible slots are exported? What happens if there is a circular dependency? Should plugins specify which other plugins they rely on before the registrations commence?

如果每个插件是一个单独的实体,不导出自己的任何插槽,而仅插入主应用程序插槽,则可以避免实现过程中的大部分复杂性.

If each plugin is a separate entity that does not export any slots of its own, only plugs into main application slots, you avoid most of the complexity in the implementation.

但是,检查注册项目的顺序可能是您可能需要考虑的细节.上面的示例程序使用一个链表,该链表中的项与注册顺序相比以相反的顺序结束,并且注册顺序与在命令行上首次指定插件文件名的顺序相同.如果您具有自动扫描的插件目录(例如,使用opendir()/readdir()/dlopen()/closedir()循环),则插件注册顺序为半随机(取决于文件系统;通常仅更改)添加或删除插件时).

The order in which registered items are examined is a detail you probably need to think about, though. The above example program uses a linked list, in which the items end up in reverse order compared to the registration order, and 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, which is automatically scanned (using e.g. opendir()/readdir()/dlopen()/closedir() loop), then the plugin registration order is semi-random (depending on the filesystem; usually changing only when plugins are added or removed).

更正?问题?有评论吗?

Corrections? Questions? Comments?

这篇关于重复符号后如何可靠地使用dlsym?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆