栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

C插件系统:dlopen失败

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C插件系统:dlopen失败

问题在于,当您编译插件系统(即,由插件调用的函数)并将其链接到最终可执行文件时,链接器不会在动态符号表中导出插件使用的符号。

有两种选择:

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

  2. -Wl,-dynamic-list,plugin-system.list
    在链接最终的可执行文件时使用,将文件中列出的符号添加
    plugin-system.list
    到动态符号表中。

文件格式很简单:

     {     sendappmessage_all;     plugin_*; };

换句话说,您可以列出每个符号名称(函数或数据结构),也可以列出与所需符号名称匹配的全局模式。请记住,每个符号后和右括号后都使用分号,否则在链接时会收到“动态列表中的语法错误”错误。

请注意,仅标记功能为“使用”的通道

__attribute__((used))
不足以使链接器将其包括在动态符号表中(至少在GCC 4.8.4和GNU ld 2.24中)。


由于OP认为我在上面写的是不正确的,所以这里是上述的完全可验证的证明。

首先,一个简单的 main.c 加载在命令行上命名的插件文件,并执行其

const char*register_plugin(void);
功能。由于函数名称在所有插件之间共享,因此我们需要在本地将它们链接(
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;     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_Hextern void plugin_message(const char *);#endif 

它们在 plugin_system.c 中实现:

#include <stdio.h>void plugin_message(const char *msg){    fputs(msg, stderr);}

并在 plugin_system.list中 列为动态符号:

{    plugin_message;};

我们还需要一个插件 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)的 功能产生什么影响的困惑:

#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

CC   := gccCFLAGS          := -Wall -Wextra -O2LDFLAGS         := -ldl -Wl,-dynamic-list,plugin_system.listPLUGIN_CFLAGS   := $(CFLAGS)PLUGIN_LDFLAGS  := -fPICPLUGINS         := plugin_foo.so plugin_bar.soPROGS:= example.phony: all clean progs pluginsall: clean progs pluginsclean:    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文件需要使用制表符(而不是空格)来指定;在此处列出文件始终会将它们转换为空格。因此,如果将以上内容粘贴到文件中,则需要通过以下方式修复缩进:

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

运行一次以上是安全的;最糟糕的是,搞砸了您的“人类可读”布局。

使用例如编译以上

make

并通过例如运行它

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

将输出

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

到标准错误。

就个人而言,我更喜欢通过具有版本号和至少一个函数指针(指向初始化函数)的结构注册我的插件。这样,我可以在初始化之前加载所有插件,并解决例如插件之间的冲突或依赖关系。(换句话说,我使用具有固定名称的结构而不是具有固定名称的函数来标识插件。)

现在,至

__attribute__((used))
。如果修改
plugin_system.c

#include <stdio.h>void plugin_message(const char *msg) __attribute__((used));void plugin_message(const char *msg){    fputs(msg, stderr);}

并将Makefile修改为

LDFLAGS := -ldl
仅包含,示例程序和插件将编译得很好,但是运行它将产生

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

换句话说,如果导出到插件的API是在单独的编译单元中编译的,则您将需要使用

-rdynamic
-Wl,-dynamic-list,plugin-system.list
确保功能已包含在最终可执行文件的动态符号表中;该
used
属性不足。


如果要在最终二进制文件的动态符号表

static
plugin_system.o
包括所有非函数和符号,则可以例如将的结尾修改
Makefile

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 0export LANG=C LC_ALL=CIFS=: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"    fidoneprintf '};n'

请记住,使脚本可执行

chmod u+x list_globals.sh



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/408916.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号