解决动态库的符号冲突

网友投稿 501 2022-10-07


解决动态库的符号冲突

一次debug遇到的疑惑

某天发现一个程序有点问题。祭上 print 大法,在关键的 lib_func() 函数里添加 print 调试信息,重新编译运行。 期望 print 出的信息一点都没有,但是程序确确实实又执行过了 libfunc() ,因为除了添加的调试 print 没有执行,libfunc() 该有的功能都执行了。这真是奇怪了。 程序不会骗人。执行的 libfunc() 肯定不是我们修改后的那个 libfunc() ,一定是别的地方有原版的 lib_func() 被执行了。一番调查,果然如此。为了便于说明,把程序和现象简化说明如下: 程序包含如下代码文件——

main.c # 主程序 plugin.c # 插件程序 lib.c lib.h # 一个库

Makefile如下:

all:main plugin main: cc -o main main.c lib.c -ldl -rdynamic plugin: cc -shared -fPIC -o plugin.so plugin.c lib.c

编译后,生成可执行程序main, 和动态库文件 plugin.so。其中主程序运行的时候,会动态加载插件 plugin.so (调用了 lib.c 里的程序)并执行。 怀疑出问题的地方在 lib.c 里。修改后的 lib.c 内容如下,添加了 debug 字样。

void lib_func() { // fprintf(stderr, "%s()\n", __func__); fprintf(stderr, "debug:%s()\n", __func__); }

因为主程序没问题,就只重新编译了 plugin.so ,重新运行。 期望得到的 print 的结果是 debug:libfunc() ,实际得到却是 libfunc()。 看起来,一个程序里的确有两份 lib_func() 代码。从 Makefile 可以看出,一份在 main 程序里,一份在 plugin.so 里。实际运行的是 main 里的那份。 事情忽然就有意思了:如果一个程序里包含多个相同的函数,实际执行的是哪一个?

TIPS:可以简单地使用 linux 的命令 nm <程序文件> 查看程序里有哪些函数

动态库和符号表

尽管程序各不相同,但总有些功能很常见。每个程序都为他们写一遍代码很不划算,于是独立出来成了库,在多个程序之间共享。一个库也可以使用别的库。有两种共享的办法:静态的,动态的。 在编译时,把库的代码复制一份合并到可执行文件里的,是静态库。 在运行时,把库的代码加载一份到内存里的,是动态库。 动态库更节省资源,不用被复制很多次,更新也方便。 负责链接的东西,叫做链接器(linker),负责加载的叫做加载器(loader)。 然而计算机是根据地址来执行的,每条指令执行前都要先确定地址。动态库加载之前,谁都不知道它会被加载到哪里,也就不知道动态库里的指令的地址,只能通过符号(名称)来记录它提供给别人用的函数列表(导出表),以及它期望别人提供给他的函数列表(导入表)。库被加载后,就获得了地址。程序运行前,需要先解析符号表,确定每个符号的实际地址。 我们开头的例子,两个相同名字的 libfunc() ,一个在 main 程序里,一个在 plugin.so 里,main 先加载,plugin.so 使用的 libfunc() 就被解析到了 main 的 libfunc() 。执行了老的 libfunc() ,而不是我们修改过的带有 debug 版本。

TIPS:对程序链接和加载有兴趣的同学可以看看 Linkers and Loaders 这本书,非常详细。

和符号有关的编译器选项和环境变量选项

如果条件允许,尽量不要在同一个程序中出现两份代码,出现相同符号的情况,造成冲突。 如果出现了符号冲突一定要解决:如本例中,假设 main 不可变,已经包含了 lib 的代码。plugin.so 可通过 gcc 的 -Wl, -Bsymbolic 选项告诉加载器优先使用自己的符号,而不优先用全局的符号。该选项可以解决符号冲突。

TIPS:如果想观察加载器的工作,可以使用环境变量 LD_DEBUG=all ./main 来执行程序,会获得详细的解析过程。manpage 的 ld.so(8) 有更多详细的说明。

最后附上示例用的代码:

➜ sample cat lib.h #ifndef UNTITLED_LIB_H #define UNTITLED_LIB_H void lib_func(); #endif //UNTITLED_LIB_H ➜ sample cat lib.c #include #include "lib.h" void lib_func() { //fprintf(stderr, "%s()\n", __func__); fprintf(stderr, "debug:%s()\n", __func__); } ➜ sample cat main.c #include #include #include #include "lib.h" int main() { void *handle = dlopen("./plugin.so", RTLD_NOW); void (*plugin_func)() = (void (*)()) dlsym(handle, "plugin"); plugin_func(); return 0; } ➜ sample cat plugin.c #include "lib.h" void plugin() { lib_func(); } ➜ sample cat Makefile all:main plugin main: cc -o main main.c lib.c -ldl -rdynamic plugin:plugin.c lib.c #cc -shared -fPIC -o plugin.so plugin.c lib.c -Wl,-Bsymbolic cc -shared -fPIC -o plugin.so plugin.c lib.c clean: rm -f main plugin.so ➜ sample

(陈国 | 天存信息)


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Ubuntu16安装one_gadget(Ubuntu16安装qt)
下一篇:2020年中国互联网会议——CRM对企业的重要性(2020互联网大会主要参与企业)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~