【问题标题】:Import c-modules from embedded Python interpreter (pybind11) in a shared object raises an undefined symbol exception从共享对象中的嵌入式 Python 解释器 (pybind11) 导入 c 模块引发未定义符号异常
【发布时间】:2022-01-20 05:01:17
【问题描述】:

更新 (1):在某些已编译的 stdlib 模块中可以看到同样的问题。这与numpy无关(我从标题中删除了numpy标签和numpy)

我正在编写一个包含嵌入式 python 解释器的共享对象(即软件插件)。共享对象启动解释器,解释器导入要执行的 python 模块。如果导入的模块包含 numpy,我会收到未定义的符号错误。实际的未定义符号错误在python版本或numpy版本的函数中发生变化,但始终是PyExc_*族的struct。

我已将问题简化为这个最小示例(它实际上包含两个文件):

// main.cc
#include "pybind11/embed.h"
namespace py = pybind11;

extern "C" {
int main() {
  py::scoped_interpreter guard{};
  auto py_module = py::module::import("numpy");
  auto version   = py_module.attr("__version__");
  py::print(version);
  return 0;
}
}

// load.cc
#include <dlfcn.h>

int main() {
  void * lib = dlopen("./libissue.so", RTLD_NOW);
  int(*fnc)(void) = (int(*)(void))dlsym(lib, "main");
  fnc();
  dlclose(lib);
  return 0;
}

我正在用这个 CMakeFile 编译:

cmake_minimum_required(VERSION 3.14)

include(FetchContent)
FetchContent_Declare(
  pybind11
  GIT_REPOSITORY https://github.com/pybind/pybind11
  GIT_TAG v2.8.1)
FetchContent_MakeAvailable(pybind11)

project(
  pybind_issue
  LANGUAGES C CXX
  VERSION 1.0.0)

add_library(issue SHARED main.cc)
set_target_properties(issue PROPERTIES 
  POSITION_INDEPENDENT_CODE ON 
  CXX_STANDARD 11)
target_link_libraries(issue PRIVATE pybind11::embed)
# also tested with
# target_link_libraries(main PRIVATE mylib pybind11::lto pybind11::embed pybind11::module)

add_executable(issue_main main.cc)
set_target_properties(issue_main PROPERTIES 
  POSITION_INDEPENDENT_CODE ON
  CXX_STANDARD 11)
target_link_libraries(issue_main PRIVATE pybind11::embed)

add_executable(loader load.cc)
target_link_libraries(loader PRIVATE ${CMAKE_DL_LIBS})

这个 CMakeFile 编译三个目标:

  • 加载解释器、导入 numpy 并打印其版本的可执行文件
  • 导出一个 C 函数的共享对象,该函数执行完全相同的操作
  • 一个简单的共享对象加载器,它尝试从共享对象运行导出的函数"main"

如果我运行 issue_main 可执行文件,我会在屏幕上正确显示 numpy 版本。如果我运行 loader 我会收到此错误:

terminate called after throwing an instance of 'pybind11::error_already_set'
  what():  ImportError: 


    https://numpy.org/devdocs/user/troubleshooting-importerror.html

  * The Python version is: Python3.8 from "/usr/bin/python3"
  * The NumPy version is: "1.20.3"

and make sure that they are the versions you expect.
Please carefully study the documentation linked above for further help.

Original error was: /usr/local/lib/python3.8/dist-packages/numpy/core/_multiarray_umath.cpython-38-x86_64-linux-gnu.so: undefined symbol: PyExc_RecursionError


At:
  /usr/local/lib/python3.8/dist-packages/numpy/core/__init__.py(51): <module>
  <frozen importlib._bootstrap>(219): _call_with_frames_removed
  <frozen importlib._bootstrap_external>(848): exec_module
  <frozen importlib._bootstrap>(686): _load_unlocked
  <frozen importlib._bootstrap>(975): _find_and_load_unlocked
  <frozen importlib._bootstrap>(991): _find_and_load
  <frozen importlib._bootstrap>(219): _call_with_frames_removed
  <frozen importlib._bootstrap>(1050): _handle_fromlist
  <frozen importlib._bootstrap>(219): _call_with_frames_removed
  <frozen importlib._bootstrap>(961): _find_and_load_unlocked

irb(main):003:1* module TestMain
=> #<FFI::Function address=0x00007f9d0ba43bb6>
irb(main):008:0> 
irb(main):009:0> TestMain.main
terminate called after throwing an instance of 'pybind11::error_already_set'
  what():  ImportError: 

IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE!

Importing the numpy C-extensions failed. This error can happen for
many reasons, often due to issues with your setup or how NumPy was
installed.

We have compiled some common reasons and troubleshooting tips at:

    https://numpy.org/devdocs/user/troubleshooting-importerror.html

Please note and check the following:

  * The Python version is: Python3.8 from "/usr/bin/python3"
  * The NumPy version is: "1.20.3"

and make sure that they are the versions you expect.
Please carefully study the documentation linked above for further help.

Original error was: /usr/local/lib/python3.8/dist-packages/numpy/core/_multiarray_umath.cpython-38-x86_64-linux-gnu.so: undefined symbol: PyExc_RecursionError


At:
  /usr/local/lib/python3.8/dist-packages/numpy/core/__init__.py(51): <module>
  <frozen importlib._bootstrap>(219): _call_with_frames_removed
  <frozen importlib._bootstrap_external>(848): exec_module
  <frozen importlib._bootstrap>(686): _load_unlocked
  <frozen importlib._bootstrap>(975): _find_and_load_unlocked
  <frozen importlib._bootstrap>(991): _find_and_load
  <frozen importlib._bootstrap>(219): _call_with_frames_removed
  <frozen importlib._bootstrap>(1050): _handle_fromlist
  /usr/local/lib/python3.8/dist-packages/numpy/__init__.py(145): <module>
  <frozen importlib._bootstrap>(219): _call_with_frames_removed
  <frozen importlib._bootstrap_external>(848): exec_module
  <frozen importlib._bootstrap>(686): _load_unlocked
  <frozen importlib._bootstrap>(975): _find_and_load_unlocked
  <frozen importlib._bootstrap>(991): _find_and_load
  <frozen importlib._bootstrap>(219): _call_with_frames_removed
  <frozen importlib._bootstrap>(961): _find_and_load_unlocked
  <frozen importlib._bootstrap>(991): _find_and_load

这个问题在 linux 上是特定的(未在 OSX 上测试过),而在 Windows 上一切都按预期进行(代码有一些变化,为了完整起见,在此报告):

// main.cc
#include "pybind11/embed.h"
namespace py = pybind11;

extern "C" {
__declspec(dllexport) int main() {
  py::scoped_interpreter guard{};
  auto py_module = py::module::import("numpy");
  auto version   = py_module.attr("__version__");
  py::print(version);
  return 0;
}
}
// load.cc
#include <windows.h>

int main() {
  HMODULE lib = LoadLibrary("./issue.dll");
  int(*fnc)(void) = (int(*)(void))GetProcAddress(lib, "main");
  fnc();
  FreeLibrary(lib);
  return 0;
}

我有什么遗漏的吗?

注意事项

  • 我的第一个问题是 pybind cmake 中的一个错误,这就是我发布this bug report 的原因
  • 我的问题似乎与this bug report 中描述的问题相似,但我不确定,甚至不确定它是否是一个错误
  • 问题类似于one described here,但我认为在最小示例中我不会多次加载解释器。我想我已经看到了一个与使用相同解决方案的相同问题相关的 SO question(不要多次加载解释器),但我现在找不到参考。
  • 我已经测试了几个 numpy 版本(从 1.19 到 1.22,从 Ubuntu 存储库安装,从 pip 安装,本地构建),但问题仍然存在。只有未定义的符号发生了变化(但始终是PyExc_
  • 在 Ubuntu 18.04 和 Ubuntu 20.04 中使用 python3.6 和 3.8 测试
  • 在 pybind 2.6、2.7、2.8.1 上测试
  • 我厌倦了链接到 python 静态库,但它没有使用 -fPIC 编译,因此编译失败...

更新注意事项 (1):这似乎与 numpy 无关。如果我导入 decimal(带有 c 模块组件的 stdlib 数字类),我会收到类似的错误:

#include "pybind11/embed.h"
namespace py = pybind11;

extern "C" {
int main() {
  py::scoped_interpreter guard{};
  auto py_module = py::module::import("decimal");
  auto version   = py_module.attr("__name__");
  py::print(version);
  return 0;
}
}

给我

terminate called after throwing an instance of 'pybind11::error_already_set'
  what():  ImportError: /usr/lib/python3.8/lib-dynload/_contextvars.cpython-38-x86_64-linux-gnu.so: undefined symbol: PyContextVar_Type

At:
  /usr/lib/python3.8/contextvars.py(1): <module>
  <frozen importlib._bootstrap>(219): _call_with_frames_removed
  <frozen importlib._bootstrap_external>(848): exec_module
  <frozen importlib._bootstrap>(686): _load_unlocked
  <frozen importlib._bootstrap>(975): _find_and_load_unlocked
  <frozen importlib._bootstrap>(991): _find_and_load
  /usr/lib/python3.8/_pydecimal.py(440): <module>
  <frozen importlib._bootstrap>(219): _call_with_frames_removed
  <frozen importlib._bootstrap_external>(848): exec_module
  <frozen importlib._bootstrap>(686): _load_unlocked
  <frozen importlib._bootstrap>(975): _find_and_load_unlocked
  <frozen importlib._bootstrap>(991): _find_and_load
  /usr/lib/python3.8/decimal.py(8): <module>
  <frozen importlib._bootstrap>(219): _call_with_frames_removed
  <frozen importlib._bootstrap_external>(848): exec_module
  <frozen importlib._bootstrap>(686): _load_unlocked
  <frozen importlib._bootstrap>(975): _find_and_load_unlocked
  <frozen importlib._bootstrap>(991): _find_and_load

[1]    3095287 abort (core dumped)  ./loader

【问题讨论】:

    标签: python c++ shared-libraries pybind11


    【解决方案1】:

    我找到了解决方案。知道它与 numpy 无关,这让我们将注意力转移到真正的问题上:符号丢失。接受this answer 的建议,尤其是这一点:

    解决问题。首先通过 dlopen 加载在步骤 1 中找到的库(在此处也使用 RTLD_GLOBAL)。

    我已将最小示例修改如下:

    // main.cc
    #include "pybind11/embed.h"
    #include <dlfcn.h>
    namespace py = pybind11;
    
    extern "C" {
    void * python;
    
    int create() {
      python = dlopen("/usr/lib/x86_64-linux-gnu/libpython3.8.so", RTLD_NOW | RTLD_GLOBAL);
      return 0;
    }
    
    int destroy() {
      dlclose(python);
      return 0;
    }
    
    int main() {
      py::scoped_interpreter guard{};
      auto py_module = py::module::import("numpy");
      auto version   = py_module.attr("__version__");
      py::print(version);
      return 0;
    }
    }
    
    // load.cc
    #include <dlfcn.h>
    
    int main() {
      void * lib = dlopen("./libissue.so", RTLD_NOW | RTLD_DEEPBIND);
      int(*fnc)(void) = (int(*)(void))dlsym(lib, "main");
      int(*create)(void) = (int(*)(void))dlsym(lib, "create");
      int(*destroy)(void) = (int(*)(void))dlsym(lib, "destroy");
      create();
      fnc();
      destroy();
      dlclose(lib);
      return 0;
    }
    

    (显然在 cmake 中我必须添加 ${CMAKE_DL_LIBS} 作为 issue 目标的目标链接库)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-23
      • 2020-12-27
      • 1970-01-01
      相关资源
      最近更新 更多