编译Python代码以使用Cython静态链接可执行文件

时间:2018-02-14 20:55:17

标签: python gcc linker cython

我有一个纯Python脚本,我想将其分发给具有未知Python配置的系统。因此,我想将Python代码编译为独立的可执行文件。

我运行cython --embed ./foo.py时没有问题foo.c。然后,我跑

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

python3-config --cflags给出

-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

python3-config --ldflags给出了

-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

这样我获得了一个没有问题的动态链接的可执行文件。 ldd a.out产生

 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)

现在,我尝试将选项-static添加到gcc,但这会导致错误:

/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

我检查了ldd给出的所有共享库也作为静态库安装。

那么,这是否与python3-config提供的选项有些不兼容?

1 个答案:

答案 0 :(得分:7)

经验丰富的问题​​显然来自链接器(gcc在引擎盖下启动了一个链接器,看看它 - 只需用-v启动gcc - 在详细模式下)。因此,让我们先简要提醒链接过程如何工作:

链接器保留所需解析的所有符号的名称。在开始时,它只是符号main。当链接器检查库时会发生什么?

  1. 如果它是一个静态库,链接器将查看此库中的每个目标文件,如果此目标文件定义了一些查找符号,则包含整个目标文件(这意味着某些符号会被解析,但是可以添加一些新的未解决的符号)。链接器可能需要在静态库上多次传递。

  2. 如果它是一个共享库,它会被链接器视为一个由一个巨大的目标文件组成的库(毕竟,我们必须在运行时加载这个库而不必反复传递多次以修剪未使用的符号):如果至少有一个需要的符号,则整个库是“链接的”(实际上并不是在运行时链接发生,这是一种干运行),如果不是 - 整个图书馆都被丢弃了,再也没有看过了。

  3. 例如,如果您链​​接:

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

    无论python3.x是共享库还是静态库,都会遇到问题:当链接器看到它时,它只查找符号main,但此符号未在python-lib,所以python-lib被丢弃了,再也没有看过了。只有当链接器看到对象文件foo.o时,才意识到需要整个Python-Symbols,但现在已经太晚了。

    有一个简单的规则来处理这个问题:首先放置目标文件!这意味着:

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

    现在,链接器在第一次看到python-lib时就知道它需要什么。

    还有其他方法可以达到类似的效果。

    A)只要每次扫描添加至少一个新符号定义,让链接器重复一组归档:

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

    链接器选项-Wl,-start-group-Wl,-end-group表示链接器在这组存档上不止一次迭代,因此链接器有第二次机会(或更多)包含符号。此选项可能会导致更长的链接时间。

    B)打开选项--no-as-needed将导致链接的共享库(并且只有共享库),无论是否在此库中都需要定义符号。

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

    实际上,默认的ld-behavior是--no-as-needed,但是gcc-frontend使用选项--as-needed调用ld,所以我们可以通过在python之前添加-no-as-needed来恢复行为 - 库,然后再将其关闭。

    现在你的静态链接问题。我不认为使用所有标准库的静态版本(以上都是glibc)是不可取的,你应该做的就是静态链接python-library。

    链接的规则很简单:默认情况下,链接器首先尝试打开库的共享版本而不是静态版本。即对于库libmylib和路径AB,即

     -L/A -L/B lmylib
    

    它尝试按以下顺序打开库:

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

    因此,如果文件夹A只有静态版本,则使用此静态版本(无论文件夹B中是否存在共享版本)。

    因为真正使用哪个库是非常不透明的 - 它取决于系统的设置,通常会通过-Wl,-verbose打开链接器的日志来进行故障排除。

    通过使用选项-Bstatic,可以强制使用库的静态版本:

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

    值得注意的事情:

    1. foo.o在图书馆之前链接。
    2. 直接在python-library之后关闭静态模式,以便动态链接其他库。
    3. 现在:

       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!
      

      是的,如果你链接到静态glibc(我不推荐),你需要从命令行中删除-Xlinker -export-dynamic

      在没有-Xlinker -export-dynamic的情况下编译的可执行文件将无法加载一些c-extension,这些扩展依赖于它们加载ldopen的可执行文件的这个属性。