我有一个纯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提供的选项有些不兼容?
答案 0 :(得分:7)
经验丰富的问题显然来自链接器(gcc在引擎盖下启动了一个链接器,看看它 - 只需用-v
启动gcc - 在详细模式下)。因此,让我们先简要提醒链接过程如何工作:
链接器保留所需解析的所有符号的名称。在开始时,它只是符号main
。当链接器检查库时会发生什么?
如果它是一个静态库,链接器将查看此库中的每个目标文件,如果此目标文件定义了一些查找符号,则包含整个目标文件(这意味着某些符号会被解析,但是可以添加一些新的未解决的符号)。链接器可能需要在静态库上多次传递。
如果它是一个共享库,它会被链接器视为一个由一个巨大的目标文件组成的库(毕竟,我们必须在运行时加载这个库而不必反复传递多次以修剪未使用的符号):如果至少有一个需要的符号,则整个库是“链接的”(实际上并不是在运行时链接发生,这是一种干运行),如果不是 - 整个图书馆都被丢弃了,再也没有看过了。
例如,如果您链接:
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
和路径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
值得注意的事情:
foo.o
在图书馆之前链接。现在:
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
的可执行文件的这个属性。