Ссылка расширения 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.c
gzopen()
,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_open
foo.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_open
gzopen
на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
. Это должно быть не слишком сложно для вас. Вы, поскольку вы, кажется, хорошо владеете С.