8

非常に古い Red Hat システムで Python C 拡張機能を作成しています。システムには zlib 1.2.3 があり、大きなファイルを正しくサポートしていません。残念ながら、システムの zlib を新しいバージョンにアップグレードすることはできません。一部のパッケージが内部の zlib 構造に突き刺さり、新しい zlib バージョンでは機能しなくなるためです。

gzopen()すべての zlib 呼び出し ( など) が、gzseek()残りの Python 実行可能ファイルやその他の拡張機能に影響を与えることなく、ユーザー ディレクトリにインストールするカスタム zlib に解決されるように、拡張機能を構築したいと考えています。

libz.aリンク中にgccコマンドラインに追加して静的にリンクしようとしましlibz.aたが、うまくいきませんでした(gzopen()たとえば、大きなファイルを作成することはまだできません)。私もgccに渡そうとし-z origin -Wl,-rpath=/path/to/zlib -lzましたが、それもうまくいきませんでした。

zlib の新しいバージョンはまだ名前が付けられzlib 1.xsonameいるため、同じであるため、シンボルのバージョン管理は機能しないと思います。私がやりたいことをする方法はありますか?

私は 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()

ファイル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現在のディレクトリを読み込もうとします。

non-staticallylinked を使用してこれを行うとgztest.so、2 GB を超えて読み取ろうとするまで期待どおりに動作します。

staticallylinked を使用してこれを行うと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 ()

問題の 1 つは、バックトレースの 2 行目がlibz.so.1!を参照していることです。するとldd gztest.so、他の行の中でも次のようになります。

    libz.so.1 => /lib/libz.so.1 (0xf6f87000)

なぜそれが起こっているのかはわかりません。

編集2

私は最終的に次のことをしました:

  • z_プレフィックス 付きでエクスポートされたすべてのシンボルを使用して、カスタム zlib をコンパイルしました。zlibconfigureスクリプトを使用すると、これが非常に簡単になります。実行するだけ./configure --zprefix ...です。
  • 私のCythonコードgzopen64()の代わりに呼び出されました。gzopen()これは、正しい「基になる」シンボルを使用していることを確認したかったためです。
  • z_off64_t明示的に使用されます。
  • zlib.aカスタムを Cython によって生成された共有ライブラリに静的にリンクします。'-Wl,--whole-archive /home/alok/zlib_lfs_z/lib/libz.a -Wl,--no-whole-archive'それを達成するためにgccとリンクしながら使用しました。他の方法があるかもしれませんし、これは必要ないかもしれませんが、正しいライブラリが確実に使用されるようにする最も簡単な方法のように思えました。

上記の変更により、大きなファイルが機能する一方で、残りの Python 拡張モジュール/プロセスは以前と同様に機能します。

4

2 に答える 2

2

これは別の質問の問題に似ているように見えますが、反対の動作が発生します。

の 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

...主にに追加し-fPIClibz.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");
}

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は期待どおりに機能し、印刷されました...

gzopen64
gz_open

...しかしfoo.so、Python での使用...

import ctypes

foo = ctypes.CDLL('./foo.so')
foo.main()

...何も出力しなかったので、Python を使用していると思いlibz.soます...

$ 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を直接開くことでした...

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()直接呼び出す必要がありました。

これは修正する 1 つの方法ですが、カスタム 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
 */

...gzopen64()定義しないと関数が公開されないという意味です_LARGEFILE64_SOURCE_LFS64_LARGEFILEあなたのシステムに当てはまるかどうかはわかりません。

于 2013-06-06T17:27:49.220 に答える
2

を使用することをお勧めしctypesます。C ライブラリを通常の共有ライブラリとして作成し、それを使用ctypesしてアクセスします。Python データ構造から C データ構造にデータを転送するには、もう少し Python コードを記述する必要があります。大きな利点は、システムの残りの部分からすべてを分離できることです。*.soロードするファイルを明示的に指定できます。Python C API は必要ありません。私はかなり良い経験をしていctypesます。あなたは C に習熟しているように見えるので、これはそれほど難しいことではありません。

于 2013-06-03T13:11:50.613 に答える