Alo*_*hal 7 python zlib shared-libraries cython
我正在一个非常古老的Red Hat系统上编写Python C扩展.系统具有zlib 1.2.3,它不能正确支持大文件.不幸的是,我不能只是将系统zlib升级到更新的版本,因为一些软件包会进入内部zlib结构并且会破坏新的zlib版本.
我想建立自己的扩展,使所有的zlib的调用(gzopen(),gzseek()等)解析为我安装在我的用户目录中的自定义zlib的,不影响Python可执行程序和其他扩展的其余部分.
我已尝试libz.a通过libz.a在链接期间添加到gcc命令行来静态链接,但它不起作用(仍然无法使用gzopen()例如创建大文件).我也尝试过传递-z origin -Wl,-rpath=/path/to/zlib -lz给gcc,但那也行不通.
由于zlib的新版本仍然被命名zlib 1.x,所以soname它是相同的,所以我认为符号版本控制不起作用.有办法做我想做的事吗?
我在32位Linux系统上.Python版本是2.6,它是定制的.
编辑:
我创建了一个最小的例子.我正在使用Cython(版本0.19.1).
档案gztest.pyx:
from libc.stdio cimport printf, fprintf, stderr
from libc.string cimport strerror
from libc.errno cimport errno
from libc.stdint cimport int64_t
cdef extern from "zlib.h":
ctypedef void *gzFile
ctypedef int64_t z_off_t
int gzclose(gzFile fp)
gzFile gzopen(char *path, char *mode)
int gzread(gzFile fp, void *buf, unsigned int n)
char *gzerror(gzFile fp, int *errnum)
cdef void print_error(void *gzfp):
cdef int errnum = 0
cdef const char *s = gzerror(gzfp, &errnum)
fprintf(stderr, "error (%d): %s (%d: %s)\n", errno, strerror(errno), errnum, s)
cdef class GzFile:
cdef gzFile fp
cdef char *path
def __init__(self, path, mode='rb'):
self.path = path
self.fp = gzopen(path, mode)
if self.fp == NULL:
raise IOError('%s: %s' % (path, strerror(errno)))
cdef int read(self, void *buf, unsigned int n):
cdef int r = gzread(self.fp, buf, n)
if r <= 0:
print_error(self.fp)
return r
cdef int close(self):
cdef int r = gzclose(self.fp)
return 0
def read_test():
cdef GzFile ifp = GzFile('foo.gz')
cdef char buf[8192]
cdef int i, j
cdef int n
errno = 0
for 0 <= i < 0x200:
for 0 <= j < 0x210:
n = ifp.read(buf, sizeof(buf))
if n <= 0:
break
if n <= 0:
break
printf('%lld\n', <long long>ifp.tell())
printf('%lld\n', <long long>ifp.tell())
ifp.close()
Run Code Online (Sandbox Code Playgroud)
档案setup.py:
import sys
import os
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
if __name__ == '__main__':
if 'CUSTOM_GZ' in os.environ:
d = {
'include_dirs': ['/home/alok/zlib_lfs/include'],
'extra_objects': ['/home/alok/zlib_lfs/lib/libz.a'],
'extra_compile_args': ['-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -g3 -ggdb']
}
else:
d = {'libraries': ['z']}
ext = Extension('gztest', sources=['gztest.pyx'], **d)
setup(name='gztest', cmdclass={'build_ext': build_ext}, ext_modules=[ext])
Run Code Online (Sandbox Code Playgroud)
我的自定义zlib是/home/alok/zlib_lfs(zlib版本1.2.8):
$ ls ~/zlib_lfs/lib/
libz.a libz.so libz.so.1 libz.so.1.2.8 pkgconfig
Run Code Online (Sandbox Code Playgroud)
要使用以下代码编译模块libz.a:
$ CUSTOM_GZ=1 python setup.py build_ext --inplace
running build_ext
cythoning gztest.pyx to gztest.c
building 'gztest' extension
gcc -fno-strict-aliasing -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/home/alok/zlib_lfs/include -I/opt/include/python2.6 -c gztest.c -o build/temp.linux-x86_64-2.6/gztest.o -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -g3 -ggdb
gcc -shared build/temp.linux-x86_64-2.6/gztest.o /home/alok/zlib_lfs/lib/libz.a -L/opt/lib -lpython2.6 -o /home/alok/gztest.so
Run Code Online (Sandbox Code Playgroud)
gcc正在传递我想要的所有标志(添加完整路径libz.a,大文件标志等).
要在没有自定义zlib的情况下构建扩展,我可以在没有CUSTOM_GZ定义的情况下编译:
$ python setup.py build_ext --inplace
running build_ext
cythoning gztest.pyx to gztest.c
building 'gztest' extension
gcc -fno-strict-aliasing -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -fPIC -I/opt/include/python2.6 -c gztest.c -o build/temp.linux-x86_64-2.6/gztest.o
gcc -shared build/temp.linux-x86_64-2.6/gztest.o -L/opt/lib -lz -lpython2.6 -o /home/alok/gztest.so
Run Code Online (Sandbox Code Playgroud)
我们可以检查gztest.so文件的大小:
$ stat --format='%s %n' original/gztest.so custom/gztest.so
62398 original/gztest.so
627744 custom/gztest.so
Run Code Online (Sandbox Code Playgroud)
因此,静态链接文件比预期的要大得多.
我现在可以这样做:
>>> import gztest
>>> gztest.read_test()
Run Code Online (Sandbox Code Playgroud)
它会尝试读foo.gz入当前目录.
当我使用非静态链接时gztest.so,它按预期工作,直到它尝试读取超过2 GB.
当我使用静态链接执行此操作时gztest.so,它会转储核心:
$ python -c 'import gztest; gztest.read_test()'
error (2): No such file or directory (0: )
0
Segmentation fault (core dumped)
Run Code Online (Sandbox Code Playgroud)
该错误No such file or directory具有误导性 - 该文件存在并且gzopen()实际上已成功返回. gzread()虽然失败了.
这是gdb回溯:
(gdb) bt
#0 0xf730eae4 in free () from /lib/libc.so.6
#1 0xf70725e2 in ?? () from /lib/libz.so.1
#2 0xf6ce9c70 in __pyx_f_6gztest_6GzFile_close (__pyx_v_self=0xf6f75278) at gztest.c:1140
#3 0xf6cea289 in __pyx_pf_6gztest_2read_test (__pyx_self=<optimized out>) at gztest.c:1526
#4 __pyx_pw_6gztest_3read_test (__pyx_self=0x0, unused=0x0) at gztest.c:1379
#5 0xf769910d in call_function (oparg=<optimized out>, pp_stack=<optimized out>) at Python/ceval.c:3690
#6 PyEval_EvalFrameEx (f=0x8115c64, throwflag=0) at Python/ceval.c:2389
#7 0xf769a3b4 in PyEval_EvalCodeEx (co=0xf6faada0, globals=0xf6ff81c4, locals=0xf6ff81c4, args=0x0, argcount=0, kws=0x0, kwcount=0, defs=0x0, defcount=0, closure=0x0) at Python/ceval.c:2968
#8 0xf769a433 in PyEval_EvalCode (co=0xf6faada0, globals=0xf6ff81c4, locals=0xf6ff81c4) at Python/ceval.c:522
#9 0xf76bbe1a in run_mod (arena=<optimized out>, flags=<optimized out>, locals=<optimized out>, globals=<optimized out>, filename=<optimized out>, mod=<optimized out>) at Python/pythonrun.c:1335
#10 PyRun_StringFlags (str=0x80a24c0 "import gztest; gztest.read_test()\n", start=257, globals=0xf6ff81c4, locals=0xf6ff81c4, flags=0xffbf2888) at Python/pythonrun.c:1298
#11 0xf76bd003 in PyRun_SimpleStringFlags (command=0x80a24c0 "import gztest; gztest.read_test()\n", flags=0xffbf2888) at Python/pythonrun.c:957
#12 0xf76ca1b9 in Py_Main (argc=1, argv=0xffbf2954) at Modules/main.c:548
#13 0x080485b2 in main ()
Run Code Online (Sandbox Code Playgroud)
其中一个问题似乎是回溯中的第二行是指libz.so.1!如果我这样做ldd gztest.so,我会得到以下其他方面:
libz.so.1 => /lib/libz.so.1 (0xf6f87000)
Run Code Online (Sandbox Code Playgroud)
我不知道为什么会发生这种情况.
编辑2:
我最后做了以下事情:
z_前缀导出的所有符号编译了我的自定义zlib . zlib的configure脚本使这很简单:只需运行./configure --zprefix ....gzopen64()而不是gzopen()在我的Cython代码中调用.这是因为我想确保使用正确的"底层"符号.z_off64_t明确使用.zlib.a到Cython生成的共享库中.我'-Wl,--whole-archive /home/alok/zlib_lfs_z/lib/libz.a -Wl,--no-whole-archive'在使用gcc链接时使用了它.可能还有其他方法可能不需要,但这似乎是确保使用正确库的最简单方法.通过上述更改,大型文件可以正常工作,而其余的Python扩展模块/进程也可以像以前一样工作.
看起来这与另一个问题中的问题类似,只是我得到了相反的行为。
我下载了一个 tarball zlib-1.2.8,运行./configure,然后更改了以下Makefile变量......
CFLAGS=-O3 -fPIC -D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64
SFLAGS=-O3 -fPIC -D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64
Run Code Online (Sandbox Code Playgroud)
...主要是添加到-fPIC,libz.a以便我可以在共享库中链接到它。
然后我在函数、 、中添加了一些printf()语句,这样我就可以轻松判断这些语句是否被调用。gzlib.cgzopen()gzopen64()gz_open()
在构建libz.a和之后libz.so,我创建了一个非常简单的foo.c......
#include "zlib-1.2.8/zlib.h"
void main()
{
gzFile foo = gzopen("foo.gz", "rb");
}
Run Code Online (Sandbox Code Playgroud)
...并编译了一个foo独立的二进制文件和一个foo.so共享库...
gcc -fPIC -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -o foo.o -c foo.c
gcc -o foo foo.o zlib-1.2.8/libz.a
gcc -shared -o foo.so foo.o zlib-1.2.8/libz.a
Run Code Online (Sandbox Code Playgroud)
运行foo按预期工作,并打印...
gzopen64
gz_open
Run Code Online (Sandbox Code Playgroud)
...但是在Python中使用foo.so...
import ctypes
foo = ctypes.CDLL('./foo.so')
foo.main()
Run Code Online (Sandbox Code Playgroud)
...没有打印任何东西,所以我猜它正在使用Python的libz.so...
$ ldd `which python`
...
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f5af2c68000)
...
Run Code Online (Sandbox Code Playgroud)
……虽然foo.so没用过……
$ ldd foo.so
linux-vdso.so.1 => (0x00007fff93600000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc8bfa98000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc8c0078000)
Run Code Online (Sandbox Code Playgroud)
我可以让它工作的唯一方法是libz.so直接打开自定义...
import ctypes
libz = ctypes.CDLL('zlib-1.2.8/libz.so.1.2.8')
libz.gzopen64('foo.gz', 'rb')
Run Code Online (Sandbox Code Playgroud)
...打印出来...
gzopen64
gz_open
Run Code Online (Sandbox Code Playgroud)
gzopen请注意,从到 的转换gzopen64是由预处理器完成的,因此我必须gzopen64()直接调用。
所以这是修复它的一种方法,但更好的方法可能是重新编译自定义 Python 2.6 以链接到 staticzlib-1.2.8/libz.a或zlibmodule.c完全禁用,然后您的链接选项将具有更大的灵活性。
更新
关于_LARGEFILE_SOURCE与_LARGEFILE64_SOURCE:我只是因为这个评论而指出zlib.h......
/* provide 64-bit offset functions if _LARGEFILE64_SOURCE defined, and/or
* change the regular functions to 64 bits if _FILE_OFFSET_BITS is 64 (if
* both are true, the application gets the *64 functions, and the regular
* functions are changed to 64 bits) -- in case these are set on systems
* without large file support, _LFS64_LARGEFILE must also be true
*/
Run Code Online (Sandbox Code Playgroud)
...这意味着gzopen64()如果您不定义该函数将不会被公开_LARGEFILE64_SOURCE。我不确定是否_LFS64_LARGEFILE适用于您的系统。