在较低GLIBC版本的目标系统上从.so文件导入Python模块失败
我在Ubuntu 14.04上用GCC 4.9.0(从源代码编译的)创建了一个共享库(叫做‘libFoo.so’),并在它周围构建了一个Boost.Python的包装库(根据命名规则,叫做‘Foo.so’)。
为了方便分发,我把所有共享对象的依赖文件放在同一个目录里,这些文件是我用ldd命令提取出来的:
machine1:~/lib$ ls
Foo.so libc.so.6 libgcc_s.so.1 libFoo.so libpthread.so.0 librt.so.1 libutil.so.1
libboost_python.so.1.55.0 libdl.so.2 libm.so.6 libpython2.7.so.1.0 libstdc++.so.6 libz.so.1
machine1:~/lib$ ldd Foo.so
linux-vdso.so.1 => (0x00007fff319fe000)
libboost_python.so.1.55.0 (0x00007f9d568aa000)
libpython2.7.so.1.0 (0x00007f9d56342000)
libFoo.so (0x00007f9d55e92000)
libm.so.6 (0x00007f9d55b8c000)
libc.so.6 (0x00007f9d557c5000)
libutil.so.1 (0x00007f9d555c2000)
libpthread.so.0 (0x00007f9d553a4000)
libdl.so.2 (0x00007f9d5519f000)
librt.so.1 (0x00007f9d54f97000)
libstdc++.so.6 => /usr/local/lib64/libstdc++.so.6 (0x00007f9d54c8d000)
libgcc_s.so.1 => /usr/local/lib64/libgcc_s.so.1 (0x00007f9d54a76000)
libz.so.1 (0x00007f9d5485d000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9d56d82000)
在原来的系统上,我现在只需要写:
machine1:~/lib$ LD_LIBRARY_PATH=. PYTHONPATH=. python
Python 2.7.6 (default, Mar 22 2014, 22:59:56)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import Foo
>>>
但是,在另一个系统(Ubuntu 12.04 LTS)上,我在执行这个步骤时遇到了严重的问题。
machine2:~/lib$ LD_LIBRARY_PATH=. PYTHONPATH=. python
Inconsistency detected by ld.so: dl-close.c: 759: _dl_close: Assertion `map->l_init_called' failed!
好吧,连启动Python都有冲突,所以我们先把那些有问题的库移到别的地方:
machine2:~/lib$ mv libc.so.6 libdl.so.2 ..
machine2:~/lib$ LD_LIBRARY_PATH=. PYTHONPATH=. python
Python 2.7.3 (default, Feb 27 2014, 19:58:35)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import Foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.17' not found (required by ./libstdc++.so.6)
Error in sys.excepthook:
Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/apport_python_hook.py", line 66, in apport_excepthook
from apport.fileutils import likely_packaged, get_recent_crashes
File "/usr/lib/python2.7/dist-packages/apport/__init__.py", line 1, in <module>
from apport.report import Report
File "/usr/lib/python2.7/dist-packages/apport/report.py", line 20, in <module>
import apport.fileutils
File "/usr/lib/python2.7/dist-packages/apport/fileutils.py", line 22, in <module>
from apport.packaging_impl import impl as packaging
File "/usr/lib/python2.7/dist-packages/apport/packaging_impl.py", line 20, in <module>
import apt
File "/usr/lib/python2.7/dist-packages/apt/__init__.py", line 21, in <module>
import apt_pkg
ImportError: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.17' not found (required by ./libstdc++.so.6)
Original exception was:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.17' not found (required by ./libstdc++.so.6)
现在Python可以启动了,但显然,Foo.so依赖的GLIBC版本和目标系统上安装的版本不一样:
machine2:~/lib$ /lib/x86_64-linux-gnu/libc.so.6
GNU C Library (Ubuntu EGLIBC 2.15-0ubuntu10.5) stable release version 2.15, by Roland McGrath et al.
(...)
我该如何让这个工作正常,并在目标系统上成功导入Foo模块到Python里呢?假设我不能在目标系统上全局安装更新的GLIBC版本,也不能安装GCC 4.9并在本地编译C++库。
我确实可以控制构建过程,但为了讨论的方便,我想假设我从第三方那里收到了这些共享对象文件,不能对它们进行修改。
目标系统上的'python'二进制文件似乎依赖于较旧的Glibc版本。我原以为Glibc是向后兼容的,也就是说,只要有更新的版本(我提供的),一切应该都能正常工作。
我真的不相信自己是第一个遇到这种问题的人,所以我想应该有简单的解决办法,尽管我还没找到...
2 个回答
看起来解决办法就是把构建系统中的共享库加载器复制过来(确保当前目录下有所有需要的依赖),然后用下面的方式来调用Python:
machine2:~/lib$ ./ld-linux-x86-64.so.2 --library-path . `which python`
Python 2.7.3 (default, Feb 27 2014, 19:58:35)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import Foo
>>> dir(Foo)
[...]
为什么提供更新版本的Glibc就不能直接使用呢?
这个问题的解释可以在这里找到。
你想到的解决办法——用--library-path
来运行加载器——如果你没有在.
里完整安装glibc,还是会有问题。特别是,如果你尝试使用任何NSS函数(比如getgrpnam
、getpwnam
等),你的程序可能会崩溃,因为你没有复制libnss*.so
这些库。
“如果我使用了这些函数,使用ldd Foo.so就会列出libnss*.so,并且会被复制到.里,对吧?”
不,这个理解是错误的。这些库是动态加载的,并不是直接链接的。
有没有什么方法可以可靠地找出所有的依赖项呢?
对于某个特定的调用,你可以这样做:
LD_DEBUG=libs ./ld-linux-x86-64.so.2 --library-path . `which python` foo.py
这样你就能得到foo.py
所需要的库的列表。这个列表在你运行不同的脚本时可能会变化,所以这并不是一个通用的解决方案。
你提到的glibc有200多个库的说法,有什么依据吗?
当然有:
dpkg -L libc6 | grep '\.so' | wc -l
300
其中一些是符号链接,但不是全部。如果你想要非符号链接的数量:
for j in $(dpkg -L libc6 | grep '\.so' ); do [[ -L $j ]] || echo $j; done | wc -l
279