Ç插件系统:失败的dlopen [英] C plugin system: dlopen fails

查看:238
本文介绍了Ç插件系统:失败的dlopen的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

作为一个延续这个帖子<一个href=\"http://stackoverflow.com/questions/36797376/c-pluginsystem-symbol-lookup-error?noredirect=1#comment61173064_36797376\">C pluginsystem:符号查找错误,我还是写我的插件系统和遇到新的错误

as a continuation to this post C pluginsystem: symbol lookup error, I am still writing my plugin system and encounter new bugs.

要回顾一下插件是什么,该方案包括一个外壳接口的网络应用,消息都有一个类型,因此可以使用,以在网络上创建应用程序。对于为例,一个可能的应用是聊天或transfert应用程序。

To recap what the plugins are, the program consists of a network application interfaced by a shell, messages has a type and therefore can be use to create applications on the network. For exemple, a possible application would be a chat or a transfert application.

所以外壳命令可以在网络中,当接收到消息,如果它对应于一个特定的应用程序,然后一个操作功能与消息内容作为参数执行上发送一个特定的应用程序的消息,它可以是应用程序。

So shell commands can send message of a particular application on the network, when a message is received, if it corresponds to a particular application then an action function is executed with the message content as argument, it could be the application.

一个插件是与注册它的命令和操作的初始化函数的共享库。一个命令可能仅仅是一个简单的命令,不与网络交互,这就是为什么我在那一刻实现。

A plugin is a shared library with an init function that register it's commands and actions. A command could just be a simple command that doesn't interact with the network, and that's why I achieved at the moment.

插件系统由模块:


  1. plugin_system.c

  2. list.c 的使用的第一个模块存储插件

  1. plugin_system.c
  2. list.c used by the first module to store plugins

网络部分在于:


  1. protocol.c 的协议的主要部分

  2. message.c 的主要部分消息治疗

  3. application.c 的用于编程应用的主要部分

  4. common.c中的有ccommon功能的文件

  5. network.c 的实用网络功能

  1. protocol.c main part of the protocol
  2. message.c main part for message treatment
  3. application.c main part used to program applications
  4. common.c file with ccommon functions
  5. network.c useful network functions

在protocole模块都是相互依存的,我为便利品分割文件。
所有模块编译-fPIC选项。

The modules in protocole are all interdependent, I have split files for conveniency. All modules are compiled with -fPIC option.

要编译称为插件的 plug.c 的至极不与网络交互,我使用:

To compile a plugin called plug.c wich doesn't interact with the network, I use:

gcc -Wall -O2 -std=gnu99  -D DEBUG -g -fPIC -c -o plug.o plug.c
gcc -Wall -O2 -std=gnu99  -D DEBUG -g -o plug.so plug.o plugin_system.o list.o -shared

和它完美的作品,图书馆装有的dlopen 没有问题,满载则dlsym 初始化函数和正确执行这样的插件被注册,然后我执行的命令,我可以看到它的工作。

And it works perfectly, the library is loaded with dlopen with no problem, the init function loaded with dlsym and executed correctly so the plugin is registered, I then executed the command and I can see that it work.

现在我wan't添加支持的插件网络通信,所以我已经修改了相同的测试插件,我用它只有一个命令打印一条消息。我不得不打电话 sendappmessage_all 通过网络发送消息给大家一个函数中定义的 message.c

Now I wan't to add supports for network communications for the plugins, so I have modified the same test plugin that I used which has just a command to print a message. I have had a call to sendappmessage_all a function that send a message to everyone over the network, defined in message.c.

我可以编译新的插件不添加网络模块对象,它编译,插件加载正确的,但是当它调用 sendappmessage_all 显然失败消息

I can compile the new plugin without adding the network module objects, it compile, the plugin loads correctly, but when it call sendappmessage_all obviously it fails with the message

 symbol lookup error: ./plugins/zyva.so: undefined symbol: sendappmessage_all

所以,使其工作,我想与网络模块插件所以这就是我与做

So to make it work, I should like the plugin with network modules so that's what I have done with

gcc -Wall -O2 -std=gnu99  -D DEBUG -g -o plug.so plug.o plugin_system.o list.o protocol.o message.o thread.o common.o application.o network.o -shared

它编译,但是当我尝试加载插件,的dlopen 收益 NULL
我也尝试添加只有一个模块,在最坏的情况只会导致未定义的符号的错误,但我的dlopen 还是回到 NULL

It compile but when I try to load the plugin, dlopen return NULL. I have also tried to add just one module, at worst it would only result in an undefined symbol error, but I dlopen still return NULL.

我知道这是一个很大的信息和对阿瑟赛德你可能wan't看到code,但我试图在最succint方式,我可能是因为是更清晰的方式更加复杂,比大帖子。

I know it's a lot of informations and on the otherside you probably wan't to see the code but I tried to be the clearer in the most succint way I could be because is way more complex and bigger than the post.

感谢您的理解。

推荐答案

的问题是,当你编译插件系统(即功能由插件调用),并将其链接到最终的可执行文件,链接器不导出符号在动态符号表所使用的插件。

The problem is that when you compile the plugin system (i.e. functions called by plugins), and link it to the final executable, the linker does not export the symbols used by the plugins in the dynamic symbol table.

有两种选择:


  1. 使用 -rdynamic 连接最终可执行文件时,将所有符号动态符号表。

  1. Use -rdynamic when linking the final executable, adding all symbols to the dynamic symbol table.

使用 -Wl,-dynamic列表,插件,system.list 连接最终可执行文件时,将在文件中列出的符号插件-system.list 来动态符号表。

Use -Wl,-dynamic-list,plugin-system.list when linking the final executable, adding symbols listed in file plugin-system.list to the dynamic symbol table.

这个文件的格式很简单:

The file format is simple:

 {
     sendappmessage_all;
     plugin_*;
 };

在换句话说,你可以列出每个符号名(函数或数据结构),或匹配所需的符号名的glob模式。记住每个符号后分号,右括号之后,或者你会,在动态列表的语法错误得到一个链接时错误。

In other words, you can list either each symbol name (function or data structure), or a glob pattern that matches the desired symbol names. Remember the semicolon after each symbol, and after the closing brace, or you'll get a "syntax error in dynamic list" error at link time.

请注意,只是标志着拿来主义功能通过<一个href=\"https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-g_t_0040$c$c_007bused_007d-function-attribute-3258\"相对=nofollow> __ __属性((使用)) 是不足以使连接器包括在动态符号表中(使用GCC 4.8.4和GNU LD 2.24,至少)。

Note that just marking a function "used" via __attribute__((used)) is not sufficient to make the linker include it in the dynamic symbol table (with GCC 4.8.4 and GNU ld 2.24, at least).

由于OP觉得我写上面不正确,这里是上面的完全可验证的证据。

Since the OP thinks what I wrote above is incorrect, here is a fully verifiable proof of the above.

首先,一个简单的的main.c 的加载插件命令行指定的文件,并执行他们的为const char * register_plugin(无效); 功能。因为函数的名字在所有的插件共享的,我们需要把它们连接在本地( RTLD_LOCAL )。

First, a simple main.c that loads plugin files named on the command line, and executes their const char *register_plugin(void); function. Because the function name is shared across all plugins, we need to link them locally (RTLD_LOCAL).

#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <stdio.h>

static const char *load_plugin(const char *pathname)
{
    const char    *errmsg;
    void          *handle; /* We deliberately leak the handle */
    const char * (*initfunc)(void);

    if (!pathname || !*pathname)
        return "No path specified";

    dlerror();
    handle = dlopen(pathname, RTLD_NOW | RTLD_LOCAL);
    errmsg = dlerror();
    if (errmsg)
        return errmsg;

    initfunc = dlsym(handle, "register_plugin");
    errmsg = dlerror();
    if (errmsg)
        return errmsg;

    return initfunc();
}

int main(int argc, char *argv[])
{
    const char *errmsg;
    int         arg;

    if (argc < 1 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s plugin [ plugin ... ]\n", argv[0]);
        fprintf(stderr, "\n");
        return EXIT_SUCCESS;
    }

    for (arg = 1; arg < argc; arg++) {
        errmsg = load_plugin(argv[arg]);
        if (errmsg) {
            fflush(stdout);
            fprintf(stderr, "%s: %s.\n", argv[arg], errmsg);
            return EXIT_FAILURE;
        }
    }

    fflush(stdout);
    fprintf(stderr, "All plugins loaded successfully.\n");
    return EXIT_SUCCESS;
}

该插件将不得不通过某些功能(和/或变量),在宣布获得 plugin_system.h

#ifndef   PLUGIN_SYSTEM_H
#define   PLUGIN_SYSTEM_H

extern void plugin_message(const char *);

#endif /* PLUGIN_SYSTEM_H */

他们正在实施的 plugin_system.c

#include <stdio.h>

void plugin_message(const char *msg)
{
    fputs(msg, stderr);
}

和在动态符号上市的 plugin_system.list

{
    plugin_message;
};

我们还需要一个插件, plugin_foo.c

We'll also need a plugin, plugin_foo.c:

#include <stdlib.h>
#include "plugin_system.h"

const char *register_plugin(void) __attribute__((used));
const char *register_plugin(void)
{
    plugin_message("Plugin 'foo' is here.\n");
    return NULL;
}

和刚取出大约有让每个同名插件登记功能有什么影响任何混乱,另外一个插件名为 plugin_bar.c

and just to remove any confusion about what effect there is having each plugin a registration function by the same name, another plugin named plugin_bar.c:

#include <stdlib.h>
#include "plugin_system.h"

const char *register_plugin(void) __attribute__((used));
const char *register_plugin(void)
{
    plugin_message("Plugin 'bar' is here.\n");
    return NULL;
}

为使这一切很容易的进行编译,我们需要一个的Makefile

To make all of this easy to compile, we'll need a Makefile:

CC              := gcc
CFLAGS          := -Wall -Wextra -O2
LDFLAGS         := -ldl -Wl,-dynamic-list,plugin_system.list
PLUGIN_CFLAGS   := $(CFLAGS)
PLUGIN_LDFLAGS  := -fPIC
PLUGINS         := plugin_foo.so plugin_bar.so
PROGS           := example

.phony: all clean progs plugins

all: clean progs plugins

clean:
    rm -f *.o $(PLUGINS) $(PROGS)

%.so: %.c
    $(CC) $(PLUGIN_CFLAGS) $^ $(PLUGIN_LDFLAGS) -shared -Wl,-soname,$@ -o $@

%.o: %.c
    $(CC) $(CFLAGS) -c $^

plugins: $(PLUGINS)

progs: $(PROGS)

example: main.o plugin_system.o
    $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@

需要注意的是Makefile文件需要由制表符,而不是空格intendation;这里列出文件总是将它们转换成空间。所以,如果你粘贴上面的一个文件,你需要修复的压痕,通过例如

Note that Makefiles require intendation by tabs, not spaces; listing the file here always converts them to spaces. So, if you paste the above to a file, you'll need to fix the indentation, via e.g.

sed -e 's|^  *|\t|' -i Makefile

这是安全的,运行超过一次;它可以做最坏的打算,就是弄乱你的人类可读的布局。

It is safe to run that more than once; the worst it can do, is mess up your "human-readable" layout.

编译上面使用例如

make

和运行它通过例如

./example ./plugin_bar.so ./plugin_foo.so

这将输出

Plugin 'bar' is here.
Plugin 'foo' is here.
All plugins loaded successfully.

标准错误。

我个人preFER经由结构登记自己的插件,具有版本号,以及至少一个函数指针(到初始化功能)。这让我在初始化之前加载所有的插件,例如与解决interplugin冲突或依赖关系。 (换言之,我使用的结构具有固定名称,而不是具有固定名称的函数,来识别插件。)

Personally, I prefer to register my plugins via a structure, with a version number, and at least one function pointer (to the initialization function). This lets me load all plugins before initializing them, and resolve e.g. interplugin conflicts or dependencies. (In other words, I use a structure with a fixed name, rather than a function with a fixed name, to identify plugins.)

现在,至于 __ __属性((使用))。如果修改 plugin_system.c

Now, as to __attribute__((used)). If you modify plugin_system.c into

#include <stdio.h>

void plugin_message(const char *msg) __attribute__((used));

void plugin_message(const char *msg)
{
    fputs(msg, stderr);
}

和修改Makefile有 LDFLAGS:= -ldl 只,示例程序和插件会编译得很好,但运行它会产生

and modify the Makefile to have LDFLAGS := -ldl only, the example program and plugins will compile just fine, but running it will yield

./plugin_bar.so: ./plugin_bar.so: undefined symbol: plugin_message.

在换句话说,如果出口到插件API中的一个单独的编译单元编译,您将需要为使用 -rdynamic - WL,-dynamic列表,插件,system.list 来确保功能都包含在最终的可执行文件的动态符号表;在使用属性不足够了。

In other words, if the API exported to plugins is compiled in a separate compilation unit, you will need to use either -rdynamic or -Wl,-dynamic-list,plugin-system.list to ensure the functions are included in the dynamic symbol table in the final executable; the used attribute does not suffice.

如果您希望所有和唯一的非 - 静态 plugin_system.o 函数和符号包含在动态符号表在最后的二进制文件,你可以如修改 Makefile结尾

If you want all and only non-static functions and symbols in plugin_system.o included in dynamic symbol table in the final binary, you can e.g. modify the end of the Makefile into

example: main.o plugin_system.o
    @rm -f plugin_system.list
    ./list_globals.sh plugin_system.o > plugin_system.list
    $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@

list_globals.sh

#!/bin/sh
[ $# -ge 1 ] || exit 0
export LANG=C LC_ALL=C
IFS=:
IFS="$(printf '\t ')"

printf '{\n'
readelf -s "$@" | while read Num Value Size Type Bind Vis Ndx Name Dummy ; do
    [ -n "$Name" ] || continue
    if [ "$Bind:$Type" = "GLOBAL:FUNC" ]; then
        printf '    %s;\n' "$Name"
    elif [ "$Bind:$Type:$Ndx" = "GLOBAL:OBJECT:COM" ]; then
        printf '    %s;\n' "$Name"
    fi
done
printf '};\n'

记住使脚本可执行文件, CHMODü+ X list_globals.sh

这篇关于Ç插件系统:失败的dlopen的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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