[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Should .a library contains non-reallocatable code?



On Thu, Feb 19, 2015 at 05:19:30PM -0600, Jeff Epler wrote:
>  * foomodule is a Python wrapper for libfoo, so it must be shipped
>    as a .so, but if it links libfoo.a, and libfoo.a is not -fPIC,
>    it is not possible to build foomodule at all
> 
>    (The same goes for wrapping the library for most other interpreted
>    languages)

So here is a concrete example of this.  I chose libtomcrypt at
semi-random, because in libtomcrypt-dev 1.17-6 there is both a shared
library and a static library; the latter has non-PIC code.

Begin with a Python module wrapping a single aspect of libtomcrypt:
generating a random byte string using the Fortuna algorithm:

    #include <Python.h>
    #include <tomcrypt.h>

    static PyObject *tom_random(PyObject *noself, PyObject *args) {
        Py_ssize_t sz;

        if(!PyArg_ParseTuple(args, "n:tom.random", &sz)) return NULL;
        PyObject *bytearray = PyByteArray_FromStringAndSize("", 0);
        if(!sz || !bytearray) { return bytearray; }

        prng_state prng;
        int err;
        if((err = rng_make_prng(128, find_prng("fortuna"), &prng, NULL)) != CRYPT_OK)
            return PyErr_Format(PyExc_RuntimeError,
                    "rng_make_rng() returned %d", err);

        if(PyByteArray_Resize(bytearray, sz) < 0) {
            Py_DECREF(bytearray);
            bytearray = NULL;
            goto done;
        }

        if((err = fortuna_read(PyByteArray_AsString(bytearray), sz, &prng))
                != sz) {
            Py_DECREF(bytearray);
            bytearray = NULL;
            return PyErr_Format(PyExc_RuntimeError,
                    "fortuna_read() only read %zd bytes", (size_t)sz);
        }

    done:
        fortuna_done(&prng);
        return bytearray;
    }

    static PyMethodDef meth[] = {
        {"random", (PyCFunction)tom_random, METH_VARARGS,
            "random(n): get N random bytes from a fortuna generator"},
        {}
    };

    void inittom() {
        int err;
        if((err = register_prng(&fortuna_desc)) == -1) {
            PyErr_Format(PyExc_RuntimeError,
                    "register_prng() returned %d", err);
            return;
        }

        PyObject *m = PyImport_AddModule("tom");
        Py_InitModule("tom", meth);
    }

Stir in an 'version' file that exports just the required symbol, inittom:

    {
    global: inittom;
    local: *;
    };

create a Python 2.7 extension module as follows:
    $ gcc -I /usr/include/python2.7 -fPIC -c -o tommodule.o tommodule.c
    $ gcc -shared -o tommodule.so tommodule.o -ltomcrypt -lpython2.7 \
        -Wl,--version-script=version -Wl,--no-allow-shlib-undefined

this .so file has just one exported symbol, thanks to the export file:
    $ nm -D --defined-only tommodule.so 
        0000000000000d0e T inittom
and happily, it works:
    $ python -c 'import tom; print bytes(tom.random(16)).encode("hex")'
    412d02b965f8c1f34c6bbaf2d1beb001

However, if libtomcrypt.a is used instead, the linker errors:
    $ gcc -shared -o tommodule.so tommodule.o \
        -Wl,-Bstatic -ltomcrypt -Wl,-Bdynamic -lpython2.7 \
        -Wl,--version-script=version -Wl,--no-allow-shlib-undefined
    /usr/bin/ld.bfd.real: /usr/lib/x86_64-linux-gnu/libtomcrypt.a(crypt_find_prng.o):
        relocation R_X86_64_32 against `prng_descriptor' can not be used when making a shared object; recompile with -fPIC
    /usr/lib/x86_64-linux-gnu/libtomcrypt.a: error adding symbols: Bad value
    collect2: error: ld returned 1 exit status

If libtomcrypt.a *had* been built with -fPIC, then this would have
worked.  Using the version file prevents *ANY* identifier from
libtomcrypt being a part of the public API of the Python module.  From
the point of view of the user of the Python tom module, it's a 
moot point whether libtomcrypt's C API is stable.

In fact, I went ahead and rebuilt it this way, with just a patch to
debian/rules:
    -export CFLAGS += -DGMP_DESC -DLTM_DESC -DUSE_LTM
    +export CFLAGS += -DGMP_DESC -DLTM_DESC -DUSE_LTM -fPIC
with this version of the package installed, the linker line which
referred to the .a file worked to produce a Python shared library.

All tests done on a Jessie x86-64 system.  I manually trancsribed some
of the commands above, so there's every chance I made an error in
transcrption.

Jeff


Reply to: