使用嵌入式Python解释器时出现未定义符号错误

4

原本我在一个使用pybind11嵌入Anaconda Python解释器的大项目中遇到了这个错误。我通过一个简单的最小示例可以重现出这个错误。

当我运行我的可执行文件(嵌入Python)时,我会得到这个错误:

Traceback (most recent call last):
  File "<string>", line 3, in <module>
  File "/app/Python-3.8.2-build/lib/python3.8/struct.py", line 13, in <module>
    from _struct import *
ImportError: /app/Python-3.8.2-build/lib/python3.8/lib-dynload/_struct.cpython-38-x86_64-linux-gnu.so: undefined symbol: PyByteArray_Type

首先,我从源代码构建了Python-3.8.2。然后我从以下C代码编译了一个可执行文件:

#include <Python.h>

int main(int argc, char *argv[])
{
    Py_Initialize();
    PyRun_SimpleString("import struct");
    if (Py_FinalizeEx() < 0) {
        exit(120);
    }
    return 0;
}

使用以下命令:

gcc -o execpy execpy.c \
-I/app/Python-3.8.2-build/include/python3.8 \
-Wno-unused-result -Wsign-compare  -DNDEBUG -g -fwrapv -O3 \
-L/app/Python-3.8.2-build/lib  -lcrypt -lpthread -ldl  -lutil -lm \
/app/Python-3.8.2/libpython3.8.a

然后只需执行./execpy即可得到上面的错误...有什么想法吗?

编辑: 在这个例子中,我想像Python解释器一样静态链接libpython,不依赖于任何libpython.so

编辑: _struct.*.so似乎没有与链接的libpython相关的依赖项(这对我的标准anaconda Python解释器也是如此):

$ ldd /app/Python-3.8.2-build/lib/python3.8/lib-dynload/_struct.cpython-38-x86_64-linux-gnu.so
    linux-vdso.so.1 =>  (0x00007fff32bf0000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f71a5634000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f71a5266000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f71a5a5c000)

我还在另一台机器上检查了系统Python解释器的_struct.*.so文件,它也有这个文件:

    linux-vdso.so.1 =>  (0x00007ffe2b3d9000)
    libpython3.6m.so.1.0 => /lib64/libpython3.6m.so.1.0 (0x00007febe24fd000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007febe22e1000)
    libc.so.6 => /lib64/libc.so.6 (0x00007febe1f13000)
    libdl.so.2 => /lib64/libdl.so.2 (0x00007febe1d0f000)
    libutil.so.1 => /lib64/libutil.so.1 (0x00007febe1b0c000)
    libm.so.6 => /lib64/libm.so.6 (0x00007febe180a000)
    /lib64/ld-linux-x86-64.so.2 (0x00007febe2c30000)

ldd /app/Python-3.8.2-build/lib/python3.8/lib-dynload/_struct.cpython-38-x86_64-linux-gnu.so 会输出什么?它是否加载了不同的 libpython3.8? - Botje
我在上面的问题中添加了ldd输出。 - Philipp Fischer
你能在python可执行文件上尝试ldd吗? - CristiFati
2个回答

4

静态链接libpython

简短回答:将-rdynamic标志添加到编译选项中,这对我而言是有效的。

-rdynamic标志的文档如下:

-rdynamic
    Pass the flag -export-dynamic to the ELF linker, on targets that support it. This 
    instructs the linker to add all symbols, not only used ones, to the dynamic symbol 
    table. This option is needed for some uses of dlopen or to allow obtaining 
    backtraces from within a program.

动态链接libpython

我还发现:如果您想动态地嵌入Python 3.8解释器 (libpython3.8.so),自Python 3.8起有一些更改

在Unix上,除Android和Cygwin外,C扩展不再链接到libpython。当嵌入Python时,必须使用RTLD_GLOBAL而不是RTLD_LOCAL加载libpython。以前,使用RTLD_LOCAL已经无法加载未链接到libpython的C扩展,例如由模块/设置的共享部分构建的标准库的C扩展。(由Victor Stinner在bpo-21536中提供)。

请注意(请参见此处):

要将Python嵌入应用程序,必须向python3-config --libs --embed传递新的--embed选项,以获取-lpython3.8(将应用程序链接到libpython)。为了同时支持3.8和旧版本,请首先尝试python3-config --libs --embed,如果上一个命令失败,则回退到python3-config --libs (不带--embed)。

向应用程序嵌入Python,请添加一个pkg-config python-3.8-embed模块:pkg-config python-3.8-embed --libs 包括-lpython3.8。为了同时支持3.8和旧版本,请首先尝试pkg-config python-X.Y-embed --libs,如果上一个命令失败,则回退到pkg-config python-X.Y --libs(不带--embed)(将X.Y替换为Python版本)。

因此,像这样动态编译和链接对我也起作用:

gcc -o execpy execpy.c -I/app/Python-3.8.2-build/include/python3.8 \
    -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 \
    -lcrypt -lpthread -ldl  -lutil -lm -lpython3.8\
    -L/app/Python-3.8.2-build/lib/ -Wl,-rpath,/app/Python-3.8.2-build/lib/

0

我认为这是因为execpy.c中没有引用PyByteArray_Type,所以链接器使用其默认的--gc-sections逻辑来删除未使用的符号。 尝试添加以下选项之一:

-Wl,--no-gc-sections

启用未使用输入节的垃圾回收。在不支持此选项的目标上被忽略。可以通过在命令行上指定--no-gc-sections来恢复默认行为(不执行此垃圾回收)。请注意,虽然COFF和PE格式目标的垃圾回收得到了支持,但实现目前被认为是实验性的。

-Wl,--gc-keep-exported

当启用--gc-sections时,此选项防止垃圾回收未使用的输入节,其中包含具有默认或受保护可见性的全局符号。此选项旨在用于可执行文件,在其中未引用的节将被垃圾回收,而不考虑所包含符号的外部可见性。请注意,由于已经是默认行为,因此此选项对于链接共享对象没有影响。此选项仅支持ELF格式目标。


我尝试了,但是它没有起作用。ld 报告说 "gc-keep-exported" 是未知的,而 "no-gc-sections" 没有任何效果。 - Philipp Fischer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接