原贴: 点击打开链接
导引:
共享库加载失败问题排查。
共享库的多个名字的解释。
编译器生成共享库时不检查符号的依赖项,这往往导致编译出的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 =====<crypt是估计加的,未使用到
nemo@ubuntu:~/code/linux_unleashed/chap24$ ldd -r errtest
linux-gate.so.1 => (0x00638000)
liberr.so.1 => not found
libcrypt.so.1 => /lib/tls/i686/cmov/libcrypt.so.1 (0x00fc8000) =====<crypt被链进来了!!!
libc.so.6 => /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