Ссылка расширения Python C с пользовательской общей библиотекой
Я пишу расширение Python C на очень старой системе Red Hat. Система имеет 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, которая построена на заказ.
Edit:
Я создал минимальный пример. Я использую 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('%lldn', <long long>ifp.tell())
printf('%lldn', <long long>ifp.tell())
ifp.close()
Файл 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])
Пользовательские 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
Чтобы скомпилировать модуль с помощью этого 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
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
Мы можем проверить размер файлов gztest.so:
$ stat --format='%s %n' original/gztest.so custom/gztest.so
62398 original/gztest.so
627744 custom/gztest.so
Таким образом, статически связанный файл намного больше, как и ожидалось.
Теперь я могу сделать:
>>> import gztest
>>> gztest.read_test()
И он попытается прочитать foo.gz в текущем каталоге.
Когда я делаю это, используя нестатически связанные gztest.so, он работает так, как ожидалось, пока не попытается прочитать более 2 ГБ.
Когда я делаю это, используя статически связанные gztest.so, он сбрасывает ядро:
$ python -c 'import gztest; gztest.read_test()'
error (2): No such file or directory (0: )
0
Segmentation fault (core dumped)
Ошибка о 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 ()
Одна из проблем, по-видимому, заключается в том, что вторая строка в обратном следе относится к libz.so.1! Если я делаю ldd gztest.so, я получаю, среди прочих строк:
libz.so.1 => /lib/libz.so.1 (0xf6f87000)
Я не уверен, почему это происходит. хотя.
править 2:
В итоге я сделал следующее:
- скомпилировал мой пользовательский zlib со всеми символами, экспортированными с префиксом
z_.zlib'Sconfigureскрипт делает это очень легко: просто запустите./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/процессы работают, как и раньше.
2 ответа:
Похоже, что это похоже на проблему в другом вопросе, за исключением того, что я получаю противоположное поведение.
Я загрузил тарбол
zlib-1.2.8, запустил./configure, затем изменил следующие переменныеMakefile...[41]}...в основном, чтобы добавитьCFLAGS=-O3 -fPIC -D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64 SFLAGS=-O3 -fPIC -D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64-fPICвlibz.a, чтобы я мог ссылаться на него в общей библиотеке.Затем я добавил некоторые операторы
printf()в функцииgzlib.cgzopen(),gzopen64(), иgz_open()так что я легко мог сказать, были ли они вызваны.После строя
libz.aиlibz.so, я создал действительно простойfoo.c...[41]}...и скомпилировал как автономный двоичный файл#include "zlib-1.2.8/zlib.h" void main() { gzFile foo = gzopen("foo.gz", "rb"); }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Запуск
fooработал, как и ожидалось, и печатался...[41]}...но используяgzopen64 gz_openfoo.soв Python С...[41]}...ничего не печатал, так что я думаю, что он использует Pythonimport ctypes foo = ctypes.CDLL('./foo.so') foo.main()libz.so...[41]}...хотя$ ldd `which python` ... libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f5af2c68000) ...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)Единственный способ, которым я мог это сделать. работа заключалась в том, чтобы открыть обычай
libz.soнепосредственно С...[41]}...который распечатали...import ctypes libz = ctypes.CDLL('zlib-1.2.8/libz.so.1.2.8') libz.gzopen64('foo.gz', 'rb')Обратите внимание, что перевод сgzopen64 gz_opengzopenнаgzopen64выполняется препроцессором, поэтому мне пришлось вызватьgzopen64()напрямую.Так что это один из способов исправить это, но лучшим способом, вероятно, было бы перекомпилировать пользовательский Python 2.6, чтобы либо связать его со статическим
zlib-1.2.8/libz.a, либо полностью отключитьzlibmodule.c, тогда у вас будет больше гибкости в связывании опции.
Обновить
Относительно
_LARGEFILE_SOURCEпротив_LARGEFILE64_SOURCE: я только указал на это из-за этого комментария вzlib.h...[41]}...подразумевается, что функция/* 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 */gzopen64()не будет открыта, если вы не определите_LARGEFILE64_SOURCE. Я не уверен, относится ли_LFS64_LARGEFILEк вашей системе или нет.
Я бы рекомендовал использовать
ctypes. Напишите свою библиотеку C как обычную общую библиотеку и используйтеctypesдля доступа к ней. Вам нужно будет написать немного больше кода Python, чтобы перенести данные из структур данных Python в C-структуры. Большое преимущество заключается в том, что вы можете изолировать все от остальной системы. Вы можете явно указать файл*.so, который вы хотите загрузить. Python C API не требуется. У меня есть довольно хороший опыт работы сctypes. Это должно быть не слишком сложно для вас. Вы, поскольку вы, кажется, хорошо владеете С.