共享库加载失败问题排查。gcc编译器生成共享库时不检查符号的依赖项。gcc编译器生成可执行程序时,会多链接一些无用的额库

您所在的位置:网站首页 加载动态库失败什么意思 共享库加载失败问题排查。gcc编译器生成共享库时不检查符号的依赖项。gcc编译器生成可执行程序时,会多链接一些无用的额库

共享库加载失败问题排查。gcc编译器生成共享库时不检查符号的依赖项。gcc编译器生成可执行程序时,会多链接一些无用的额库

2024-07-09 08:46| 来源: 网络整理| 查看: 265

原贴: 点击打开链接

导引: 共享库加载失败问题排查。 共享库的多个名字的解释。 编译器生成共享库时不检查符号的依赖项,这往往导致编译出的so在动态加载时因为出现未定义符号而加载失败。

编译器生成可执行程序时,会多链接一些无用的额库,导致程序在没有部署相应库的机器上运行不起来。

 

加载通过dlopen加载so失败,为何?怎么解决?

(本节也介绍了gcc的链接生成共享库的特点:即使没有指定符号依赖的共享库,也不会报错!)

方法1:

   1)ldd -r xxx.so 

   2)此时应该会提示:        a)某些xxx.so依赖的so没有找到,(这个情况解决较简单)           解决办法:               ldd显示依赖的so都是动态库的soname,soname是指向动态库realname的一个软链,

             在硬盘里搜索realname的动态库的所在位置,然后手动建一个soname软链指向realname,放在/usr/lib目录下。

       b)或者以及存在某些未定义的符号undefined symbol(这个情况解决较复杂)。           解决办法:

 

      为什么会存在未定义的符号?

 在编译链接生成xxx.so时,根本就没有链接未定义符号所在的so。所以,编译其也不知到符号所在的so的soname是什么。

      此时就需要检查你的makefile文件。

 ps:生成so时,即使不链接其依赖的库,也会编译过去不报错!!!

 ps:生成so时,即使链接其依赖的库,但如果链接错了文件,也会编译过去不报错!!!suse系统gcc4.1版本实验。

 我遇到的情况是:链接的linkername对应的文件是个0字节文件,居然也没报错。结果导致了很多符号未定义

 比如 -lmysqlclient, 结果发现 mysqlclient.so是个0字节的文件,不是软链,居然也编译过去了。

通过修正makefile中实际引用到的 mysqlclient.so软链,使其指向正确的库,libmysqlclient.so -> libmysqlclient.so.15.0.0* 重新编译搞定。

 

编译器生成共享库时特点

不检查符号的依赖项,,即使不链接其依赖的库,也会编译过去不报错,这往往导致编译出的so在动态加载时因为出现未定义符号而加载失败。

编译器生成可执行程序时特点

会严格检查符号是否都有定义,但是,对于未使用到的共享库,如果你链接了,即使任何符号都未使用到,也会被链进去!!

gcc errtest.c -o errtest -L. -lerr -lcrypt =====  (0x00638000)

        liberr.so.1 => not found

        libcrypt.so.1 => /lib/tls/i686/cmov/libcrypt.so.1 (0x00fc8000) ===== /lib/tls/i686/cmov/libc.so.6 (0x00110000)

        /lib/ld-linux.so.2 (0x00985000)

 

实际开发中经常遇到这样的情况,我的CGI没有使用mysql共享库,但是由于我的makefile里面链接的mysql的库,导致我的cgi运行时也必须加载mysql库,

如果把cgi放在一个没有部署mysql共享库的机器上,就会运行不起来!!!

 

linux动态库的三个名字 soname realname linkername详解

为什么一个动态库会有如此多的名字,而且soname和linkername都是软链,实际的文件是realname

对于从windows下转行过来的同学会对此很不解,本人就非常的疑惑,今日得以领悟,记录下自己的认识。如有错误请批评指正。

下面从动态库的版本控制的角度讲起。

 

a)windows下的dll的版本控制:

在windows下,一个dll都具有一个文件名,比如xxx.dll, 同时该dll也具有一些属性:文件版本等。可以通过选中dll-属性-详细信息查看dll的版本版权等很多信息。

当dll的功能更新时,只要接口没变,那么只需更新dll的版本号即可,可以发布一个和之前dll同名的xxx.dll, 直接覆盖掉原来的dll就可实现新功能的升级。

exe可执行文件里面记录的依赖的dll仅记录了dll的文件名,所以dll升级后,exe也就自动链接升级后的dll了。

 

b)linux下的so的版本控制:

linux下的so文件不具有像windows下的额外属性。为了标识一个so的版本,gcc链接生成so目标时一般都采用libxxx.so.1.0.0的方式。

这样把so的版本信息记录在文件名里面。这个带版本信息的文件名就是realname。

 

那么exe可执行文件如何记录自己依赖的so呢,如果记录的是so的realname,那么so有新版本升级时,新的so必定具有不同的realname,exe就无法自动使用更新的so了。

好在linux下有软链这样的机制可以解决该问题。exe中不必记录依赖的so的realname,exe中记下一个指向实际realname的软链即可,这个软链就是soname啦。soname一般采用这样的名字 libxxx.so.1(保留大版本号1)

当有一个功能更新的so发布时,只需修改soname软链,指向升级后的realname文件即可。

比如我发布了xxx动态库的升级版 libxxx.so.1.0.1, 使软链libxxx.so.1指向libxxx.so.1.0.1即可。

soname是我们在编译其他程序时,往其他程序的二进制映像里面写入的共享库的名字。

 

那么什么是linkname呢?顾名思义,就是编译代码时的链接阶段使用的,比如我有一个程序需要链接libxxx.so.1.0.0库,

makefile需这样写 -lxxx.so.1.0.0,这r样写实在很长也很丑,而且如果后续libxxx有更新时,必须修改makefile文件才能链接到新的库上。

所以出现了一个新的链接到realname的软链,这个软链就叫 linkername。通常lingkername是不带任何版本信息的,取名如下 libxxx.so

这样makefile就变成了 -lxxx.so

 

这其中有一个问题,动态库的使用者(比如exe)是通过linkername链接的,而lingkername指向的是so的realname,我们前文说为了解决升级so重新编译的问题,exe文件里面记录的是其依赖的so的soname名字,而不是真实的realname,

那么exe是如何知道realname对应的soname呢?

答案在realname文件里面,在编译链接生成realname时, 同事也指定了其对应的soname,这个soname存储在realname的文件里面。

eg 如下编译命令:

gcc -g -Wall -shared -Wl,-soname,liberr.so.1 -o liberr.so.1.0.0 liberr.o -lc  // liberr.so.1是soname,将记录在生成的liberr.so.1.0.0文件里面

可以通过如下工具查看一个realname的so的对应的soname:

readelf -d ./liberr.so.1.0.0 

Dynamic section at offset 0xf10 contains 22 entries:

  Tag        Type                         Name/Value

 0x00000001 (NEEDED)                     Shared library: [libc.so.6]

 0x0000000e (SONAME)                     Library soname: [liberr.so.1] ===============< 其soname

 

 

参考资料:

 

Linux 动态库剖析

http://www.ibm.com/developerworks/cn/linux/l-dynamic-libraries/

文中给出了so动态加载的示例代码 dlopen dlsym等 以及一些工具的使用比如ldd等。

 

也谈共享库

http://bigwhite.blogbus.com/logs/88871474.html

文中介绍了

1)soname,realname,linkername,很好很详细

2)介绍了exe加载so时的搜索so的路径顺序:

   a)编译exe程序是指定的-rpath

   b)环境变量LD_LIBRARY_PATH

   c)ldconfig配置的缓存中的路径

   d)系统默认路径/lib和/usr/lib。

 

 

 

nm 命令详解

T 才是库中导出的符号

U 指的是在上边的T对应的函数中使用了,却未定义的函数或符号

所以, 用nm查看.a或.so文件导出符号的时候,只需查看具有T标识的行即可:

如下命令

 nm a.out|grep -w -i t|grep your_fun_name



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3