Comstack el código de Python para un ejecutable enlazado estáticamente con Cython

Tengo un script de Python puro que me gustaría distribuir a sistemas con una configuración de Python desconocida. Por lo tanto, me gustaría comstackr el código de Python en un ejecutable independiente.

cython --embed ./foo.py sin problemas dando foo.c Entonces corro

 gcc $(python3-config --cflags) $(python3-config --ldflags) ./foo.c 

donde python3-config --cflags da

 -I/usr/include/python3.5m -I/usr/include/python3.5m -Wno-unused-result -Wsign-compare -g -fdebug-prefix-map=/build/python3.5-MLq5fN/python3.5-3.5.3=. -fstack-protector-strong -Wformat -Werror=format-security -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes 

y python3-config --ldflags da

 -L/usr/lib/python3.5/config-3.5m-x86_64-linux-gnu -L/usr/lib -lpython3.5m -lpthread -ldl -lutil -lm -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions 

De esta manera obtengo un ejecutable enlazado dinámicamente que se ejecuta sin problemas. ldd a.out rendimientos

      linux-vdso.so.1 (0x00007ffcd57fd000) libpython3.5m.so.1.0 => /usr/lib/x86_64-linux-gnu/libpython3.5m.so.1.0 (0x00007fda76823000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fda76603000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fda763fb000) libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1 (0x00007fda761f3000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fda75eeb000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fda75b4b000) libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1 (0x00007fda7591b000) libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fda756fb000) /lib64/ld-linux-x86-64.so.2 (0x00007fda77103000) 

    Ahora, trato de agregar la opción -static a gcc, pero esto produce un error:

     /usr/bin/ld: dynamic STT_GNU_IFUNC symbol `strcmp' with pointer equality in `/usr/lib/gcc/x86_64-linux-gnu/6/../../../x86_64-linux-gnu/libc.a(strcmp.o)' can not be used when making an executable; recompile with -fPIE and relink with -pie collect2: error: ld returned 1 exit status 

    Comprobé que todas las bibliotecas compartidas dadas por ldd también están instaladas como bibliotecas estáticas.

    Entonces, ¿esto es algo de incompatibilidad con las opciones dadas por python3-config?

    Los problemas experimentados son obviamente del vinculador (gcc comenzó un vinculador debajo del capó, para verlo, simplemente inicie gcc con -v – en modo detallado). Así que comencemos con un breve recordatorio de cómo funciona el proceso de vinculación:

    El enlazador mantiene los nombres de todos los símbolos que necesita resolver. Al principio es solo el símbolo main . ¿Qué pasa, cuando el enlazador inspecciona una biblioteca?

    1. Si se trata de una biblioteca estática, el vinculador examina todos los archivos de objetos de esta biblioteca, y si estos archivos de objetos definen algunos símbolos buscados, se incluye todo el archivo de objetos (lo que significa que algunos símbolos se resuelven, pero algunos otros nuevos símbolos no resueltos pueden ser agregado). Es posible que el vinculador deba pasar varias veces a través de una biblioteca estática.

    2. Si es una biblioteca compartida, el enlazador la ve como una biblioteca que consta de un único archivo de objeto enorme (después de todo, tenemos que cargar esta biblioteca en el tiempo de ejecución y no tenemos que pasar varias veces una y otra vez para podar los símbolos no utilizados): si hay al menos un símbolo necesario, toda la biblioteca está “vinculada” (en realidad no es el enlace que se produce en el tiempo de ejecución, esto es un tipo de ejecución seca), si no, toda la biblioteca Se descarta y nunca se mira de nuevo.

    Por ejemplo si vinculas con:

     gcc -L/path -lpython3.x  foo.o 

    obtendrá un problema, sin importar si python3.x es una python3.x compartida o estática: cuando el vinculador lo ve, solo busca el símbolo main , pero este símbolo no está definido en python-lib, por lo que es python -lib se descarta y nunca se mira de nuevo. Solo cuando el enlazador ve el archivo de foo.o , se da cuenta de que se necesitan todos los símbolos de Python, pero ahora ya es demasiado tarde.

    Hay una regla simple para manejar este problema: ¡ponga primero los archivos objeto! Eso significa:

     gcc -L/path foo.o -lpython3.x  

    Ahora el enlazador sabe lo que necesita de python-lib, cuando lo ve por primera vez.

    Hay otras formas de lograr un resultado similar.

    A) Deje que el enlazador reitere un grupo de archivos siempre que se agregue al menos una nueva definición de símbolo por barrido:

     gcc -L/path --Wl,-start-group -lpython3.x  foo.o -Wl,-end-group 

    Opciones de -Wl,-start-group y -Wl,-end-group dice que el vinculador itere más de una vez en este grupo de archivos, por lo que el vinculador tiene una segunda oportunidad (o más) para incluir símbolos. Esta opción puede llevar a un mayor tiempo de vinculación.

    B) Al activar la opción --no-as-needed hará que se vincule una biblioteca compartida (y solo una biblioteca compartida), sin importar si en esta biblioteca se necesitan o no los símbolos definidos.

     gcc -L/path -Wl,-no-as-needed -lpython3.x -Wl,-as-needed  foo.o 

    En realidad, el comportamiento predeterminado de ld es --no-as-needed , pero el gcc-frontend llama a ld con la opción – --as-needed , por lo que podemos restaurar el comportamiento agregando -no-as-needed antes de python- biblioteca y luego apagarlo de nuevo.


    Ahora a su problema de vinculación estática. No creo que sea recomendable utilizar versiones estáticas de todas las bibliotecas estándar (todas las anteriores glibc), lo que probablemente deberías hacer es vincular solo la biblioteca de python de forma estática.

    Las reglas del enlace son simples: por defecto, el enlazador intenta abrir primero una versión compartida de la biblioteca y luego la versión estática. Es decir, para la biblioteca libmylib y las rutas A y B , es decir

      -L/A -L/B lmylib 

    intenta abrir bibliotecas en el siguiente orden:

     A/libmylib.so A/libmylib.a B/libmylib.so B/libmylib.a 

    Por lo tanto, si la carpeta A tiene solo una versión estática, entonces se usa esta versión estática (no importa si hay una versión compartida en la carpeta B ).

    Debido a que es bastante opaco, la biblioteca se usa realmente; depende de la configuración de su sistema, por lo general, se puede activar el registro del enlazador a través de -Wl,-verbose para -Wl,-verbose problemas.

    Al usar la opción -Bstatic uno puede imponer el uso de la versión estática de una biblioteca:

     gcc foo.o -L/path -Wl,-Bstatic -lpython3.x -Wl,-Bdynamic  -Wl,-verbose -o foo 

    Lo notable:

    1. foo.o está enlazado antes que las bibliotecas.
    2. desactiva el modo estático, justo después de la biblioteca python, para que otras bibliotecas se vinculen dinámicamente.

    Y ahora:

      gcc  L/paths foo.c -Wl,-Bstatic -lpython3.X -Wl,-Bdynamic  -o foo -Wl,-verbose ... attempt to open path/libpython3.6m.a succeeded ... ldd foo shows no dependency on python-lib ./foo It works! 

    Y sí, si enlaza con glibc estático (no lo recomiendo), deberá eliminar -Xlinker -export-dynamic de la línea de comandos.

    El ejecutable comstackdo sin -Xlinker -export-dynamic no podrá cargar parte de la extensión-c que depende de esta propiedad del ejecutable para el que están cargados con ldopen .