web-dev-qa-db-fra.com

Compiler Python code en exécutable lié statiquement avec Cython

J'ai un pur script Python que je voudrais distribuer aux systèmes avec une configuration inconnue Python. Par conséquent, je voudrais compiler le Python code vers un exécutable autonome.

Je cours cython --embed ./foo.py sans problème pour donner foo.c. Ensuite, je cours

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

python3-config --cflags donne

-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

et python3-config --ldflags donne

-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 cette façon, j'obtiens un exécutable lié dynamiquement qui s'exécute sans problème. ldd a.out rendements

 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)

Maintenant, j'essaye d'ajouter l'option -static à gcc, mais cela se traduit par une erreur:

/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

J'ai vérifié que toutes les bibliothèques partagées fournies par ldd sont également installées en tant que bibliothèques statiques.

Alors, est-ce une incompatibilité avec les options fournies par python3-config?

12
phlegmax

Les problèmes rencontrés proviennent évidemment de l'éditeur de liens (gcc a démarré un éditeur de liens sous le capot, pour le voir - il suffit de démarrer gcc avec -v - en mode verbeux). Commençons donc par un bref rappel du fonctionnement du processus de liaison:

L'éditeur de liens conserve les noms de tous les symboles qu'il doit résoudre. Au début, ce n'est que le symbole main. Que se passe-t-il lorsque l'éditeur de liens inspecte une bibliothèque?

  1. S'il s'agit d'une bibliothèque statique, l'éditeur de liens examine chaque fichier objet de cette bibliothèque et si ces fichiers objet définissent certains symboles recherchés, le fichier objet entier est inclus (ce qui signifie que certains symboles sont résolus, mais que de nouveaux symboles non résolus peuvent être ajouté). L'éditeur de liens peut avoir besoin de passer plusieurs fois sur une bibliothèque statique.

  2. S'il s'agit d'une bibliothèque partagée, elle est considérée par l'éditeur de liens comme une bibliothèque composée d'un seul fichier objet énorme (après tout, nous devons charger cette bibliothèque au moment de l'exécution et ne pas avoir à passer plusieurs fois encore et encore pour élaguer les symboles inutilisés): S'il y a au moins un symbole nécessaire, toute la bibliothèque est "liée" (pas vraiment la liaison se produit au moment de l'exécution, c'est une sorte de dry-run), sinon - la bibliothèque entière est jeté et jamais regardé à nouveau.

Par exemple, si vous établissez un lien avec:

gcc -L/path -lpython3.x <other libs> foo.o 

vous obtiendrez un problème, que ce soit python3.x est une bibliothèque partagée ou statique: lorsque l'éditeur de liens le voit, il ne recherche que le symbole main, mais ce symbole n'est pas défini dans la bibliothèque python-lib, donc la bibliothèque python-lib est supprimée et jamais regardé à nouveau. Uniquement lorsque l'éditeur de liens voit le fichier objet foo.o, il se rend compte que tous les symboles Python sont nécessaires, mais maintenant il est déjà trop tard.

Il existe une règle simple pour gérer ce problème: placez d'abord les fichiers objets! Cela signifie:

gcc -L/path  foo.o -lpython3.x <other libs> 

L'éditeur de liens sait maintenant ce dont il a besoin depuis la bibliothèque python-lib, lorsqu'il le voit pour la première fois.

Il existe d'autres façons d'obtenir un résultat similaire.

A) Laissez l'éditeur de liens réitérer un groupe d'archives tant qu'au moins une nouvelle définition de symbole a été ajoutée par balayage:

gcc -L/path --Wl,-start-group -lpython3.x <other libs> foo.o -Wl,-end-group

Options de l'éditeur de liens -Wl,-start-group et -Wl,-end-group dit que l'éditeur de liens parcourt plus d'une fois ce groupe d'archives, de sorte que l'éditeur de liens a une deuxième chance (ou plus) d'inclure des symboles. Cette option peut entraîner un temps de liaison plus long.

B) Activer l'option --no-as-needed entraînera la liaison d'une bibliothèque partagée (et uniquement d'une bibliothèque partagée), que des symboles définis soient nécessaires ou non dans cette bibliothèque.

gcc -L/path -Wl,-no-as-needed -lpython3.x -Wl,-as-needed <other libs> foo.o

En fait, le comportement ld par défaut est --no-as-needed, mais le gcc-frontend appelle ld avec l'option --as-needed, afin que nous puissions restaurer le comportement en ajoutant -no-as-needed avant la bibliothèque python, puis la désactiver à nouveau.


Passons maintenant à votre problème de liaison statique. Je ne pense pas qu'il soit conseillé d'utiliser des versions statiques de toutes les bibliothèques standard (toutes ci-dessus glibc), ce que vous devriez probablement faire est de ne lier que la bibliothèque python de manière statique.

Les règles de la liaison sont simples: par défaut, l'éditeur de liens essaie d'ouvrir une version partagée de la bibliothèque d'abord et ensuite la version statique. C'est à dire. pour la bibliothèque libmylib et les chemins A et B, c'est-à-dire.

 -L/A -L/B lmylib

il essaie d'ouvrir les bibliothèques dans l'ordre suivant:

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

Ainsi, si le dossier A n'a qu'une version statique, cette version statique est utilisée (peu importe qu'il existe une version partagée dans le dossier B).

Parce qu'il est assez opaque de savoir quelle bibliothèque est vraiment utilisée - cela dépend de la configuration de votre système, généralement on activerait la journalisation de l'éditeur de liens via -Wl,-verbose pour dépanner.

En utilisant l'option -Bstatic on peut imposer l'utilisation de la version statique d'une bibliothèque:

gcc  foo.o -L/path -Wl,-Bstatic -lpython3.x -Wl,-Bdynamic <other libs>  -Wl,-verbose -o foo

Chose notable:

  1. foo.o est lié avant les bibliothèques.
  2. désactiver le mode statique, directement après la bibliothèque python, afin que d'autres bibliothèques soient liées dynamiquement.

Et maintenant:

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

Et oui, si vous établissez un lien contre des glibc statiques (je ne le recommande pas), vous devrez supprimer -Xlinker -export-dynamic depuis la ligne de commande.

L'exécutable compilé sans -Xlinker -export-dynamic ne pourra pas charger une partie de l'extension c qui dépend de cette propriété de l'exécutable dans lequel ils sont chargés avec ldopen.

8
ead