代码之家  ›  专栏  ›  技术社区  ›  phlegmax

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

  •  12
  • phlegmax  · 技术社区  · 7 年前

    我有一个纯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 回复  |  直到 7 年前
        1
  •  10
  •   ead    4 年前

    经验丰富的问题显然来自于链接器(gcc在引擎盖下启动了一个链接器,看吧-只需使用 -v -在详细模式下)。因此,让我们从一个简短的提示开始,提示链接过程是如何工作的:

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

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

    2. 如果它是一个共享库,则链接器会将其视为一个由单个大型对象文件组成的库(毕竟,我们必须在运行时加载此库,而不必反复多次删除未使用的符号):如果至少有一个所需的符号,则整个库都是“链接的”(实际上链接不是在运行时发生的,这是一种干运行),如果没有-整个库将被丢弃,不再查看。

    例如,如果您链接到:

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

    不管你是否 python3.x 是共享库还是静态库:当链接器看到它时,它只查找符号 主要的 ,但该符号未在python库中定义,因此它将被丢弃,不再查看python库。仅当链接器看到对象文件时 foo.o ,它意识到需要整个Python符号,但现在已经太迟了。

    有一个简单的规则来处理这个问题:将对象文件放在第一位!这意味着:

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

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

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

    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行为是 --根据需要无 ,但gcc前端调用ld with option --as-needed ,因此我们可以通过添加 -no-as-needed 然后再次将其关闭。


    现在来看看静态链接的问题。我认为不建议使用所有标准库的静态版本(都在glibc之上),您可能应该做的是只静态链接python库。

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

     -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. 富。o 在库之前链接。
    2. 直接在python库之后关闭静态模式,以便动态链接其他库。

    现在:

     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-导出动态 将无法加载某些依赖于其加载到的可执行文件的此属性的c-extension ldopen .


    由于隐含 -pie 选项

    Recent versions of gcc 使用生成 pie -option 按默认值。通常/有时,较旧的python版本使用较旧的gcc版本构建,因此 python-config --cflags 会错过现在需要的 -no-pie ,因为当时不需要它。在这种情况下,链接器将生成如下错误消息:

    在以下情况下,无法针对符号“XXXXX”重新定位R\u X86\u 64\u 32S 制作饼图对象;使用-fPIC重新编译

    在这种情况下, -没有派 选项应添加到 <cflags> .