Ç插件系统:失败的dlopen [英] C plugin system: dlopen fails
问题描述
作为一个延续这个帖子<一个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.
插件系统由模块:
- 的 plugin_system.c 的
- 的 list.c 的使用的第一个模块存储插件
- plugin_system.c
- list.c used by the first module to store plugins
网络部分在于:
- 的 protocol.c 的协议的主要部分
- 的 message.c 的主要部分消息治疗
- 的 application.c 的用于编程应用的主要部分
- 的 common.c中的有ccommon功能的文件
- 的 network.c 的实用网络功能
- protocol.c main part of the protocol
- message.c main part for message treatment
- application.c main part used to program applications
- common.c file with ccommon functions
- 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.
有两种选择:
-
使用
-rdynamic
连接最终可执行文件时,将所有符号动态符号表。
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屋!