【问题标题】:Compile Python code to statically linked executable with Cython使用 Cython 将 Python 代码编译为静态链接的可执行文件
【发布时间】:2021-07-27 00:48:36
【问题描述】:

我有一个纯 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 提供的选项有些不兼容?

【问题讨论】:

  • 真的猜到-export-dynamic 看起来像个嫌疑人?
  • -export-dynamic 是关于“动态符号表”(根据 gcc 文档)。我不知道这是什么。无论如何,删除它会产生一个错误:/usr/bin/ld: unrecognized option '-Wl,-O1'。 gcc 将不再识别 -Wl 选项。但是,如果没有这些,我会得到File format not recognized
  • 我认为-Xlinker -export-dynamic isone 标志 - (XLinker 将标志转发给链接器)尝试整体删除?
  • 确实可以删除这两个标志。它提供了一个正常运行的可执行文件。但是,添加-static 会导致大量undefined reference to 'PyObject_Call' 类型的错误。
  • 你的链接器命令行是什么样的? foo.o 应该出现在静态链接库之前。

标签: python gcc linker cython


【解决方案1】:

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

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

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

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

例如,如果您链​​接到:

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调用ld的时候带有--as-needed选项,所以我们可以通过在python-library前面加上-no-as-needed来恢复这个行为,然后把它关掉再次。


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

链接的规则很简单:默认情况下,链接器会先尝试打开库的共享版本,然后再打开静态版本。 IE。对于库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 之后关闭静态模式,以便动态链接其他库。

现在:

 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 编译的可执行文件将无法加载依赖于使用ldopen 加载到的可执行文件的此属性的某些c 扩展。


隐式-pie 选项可能导致的问题。

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

重定位 R_X86_64_32S 对符号 'XXXXX' 不能使用时 制作一个 PIE 对象;用 -fPIC 重新编译

在这种情况下,-no-pie 选项应添加到&lt;cflags&gt;

【讨论】:

  • 非常感谢您详细而有趣的回答。我的命令行现在显示为gcc $(python3-config --cflags) -L/usr/lib/python3.5/config-3.5m-x86_64-linux-gnu -L/usr/lib ./foo.c -Wl,-Bstatic -lpython3.5m -Wl,-Bdynamic -lpthread -ldl -lutil -lm -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions -o foo -Wl,-verbose。但是,我仍然收到大量“未定义引用”错误,例如/usr/lib/python3.5/config-3.5m-x86_64-linux-gnu/libpython3.5m.a(zlibmodule.o): In function 'PyInit_zlib': (.text+0x1ad): undefined reference to 'zlibVersion'
  • @phlegmax 我不知道您的系统在查找所有依赖项时出了什么问题。但显然您需要将-lzlib 添加到库中。实际上,您的问题中给出了所有需要的库的列表(ldd a.out 的结果)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-30
  • 2017-03-29
  • 2022-01-17
  • 1970-01-01
相关资源
最近更新 更多