在较低GLIBC版本的目标系统上从.so文件导入Python模块失败

1 投票
2 回答
3051 浏览
提问于 2025-04-18 09:09

我在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 个回答

0

看起来解决办法就是把构建系统中的共享库加载器复制过来(确保当前目录下有所有需要的依赖),然后用下面的方式来调用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)
[...]
2

为什么提供更新版本的Glibc就不能直接使用呢?

这个问题的解释可以在这里找到。

你想到的解决办法——用--library-path来运行加载器——如果你没有在.里完整安装glibc,还是会有问题。特别是,如果你尝试使用任何NSS函数(比如getgrpnamgetpwnam等),你的程序可能会崩溃,因为你没有复制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

撰写回答