【问题标题】:Library expects symbol in flat namespace although compiled with two-level namespace尽管使用两级命名空间编译,但库需要平面命名空间中的符号
【发布时间】:2018-08-29 19:43:44
【问题描述】:

我使用dlopenRTLD_LOCAL 动态加载Python,以避免与巧合包含一些同名符号的另一个库发生冲突。使用 Xcode 在 macOS 上执行我上面的 MVCE 失败,因为它需要全局命名空间中的 _PyBuffer_Type

Traceback (most recent call last):
  File "...lib/python2.7/ctypes/__init__.py", line 10, in <module>
    from _ctypes import Union, Structure, Array
ImportError: dlopen(...lib/python2.7/lib-dynload/_ctypes.so, 2):
    Symbol not found: _PyBuffer_Type
  Referenced from: ...lib/python2.7/lib-dynload/_ctypes.so
  Expected in: flat namespace
 in ...lib/python2.7/lib-dynload/_ctypes.so
Program ended with exit code: 255

但是为什么呢? RTLD_LOCAL 会覆盖两级命名空间吗?

我使用otool -hV 检查 _ctypes.so 是否使用两级命名空间选项进行编译。据我了解,符号解析需要库名称+符号名称本身。为什么它在平面命名空间中期望_PyBuffer_Type 和/或为什么找不到它?向右滚动查看TWOLEVEL

> otool -hV /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload/_ctypes.so
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64  X86_64        ALL  0x00      BUNDLE    14       1536   NOUNDEFS DYLDLINK TWOLEVEL

知道这里发生了什么吗?

MVCE

可以复制到一个新的Xcode项目中,只需编译执行即可。

#include </System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7/Python.h>
#include <dlfcn.h>

int main(int argc, const char * argv[])
{
    auto* dl = dlopen("/System/Library/Frameworks/Python.framework/Versions/2.7/Python", RTLD_LOCAL | RTLD_NOW);
    if (dl == nullptr)
        return 0;

    // Load is just a macro to hide dlsym(..)
    #define Load(name)  ((decltype(::name)*)dlsym(dl, # name))

    Load(Py_SetPythonHome)("/System/Library/Frameworks/Python.framework/Versions/2.7");
    Load(Py_Initialize)();

    auto* readline = Load(PyImport_ImportModule)("ctypes");
    if (readline == nullptr)
    {
        Load(PyErr_Print)();
        dlclose(dl);
        return -1;
    }

    Py_DECREF(readline);
    Load(Py_Finalize)();
    return 0;
}

【问题讨论】:

    标签: python c++ macos linker symbols


    【解决方案1】:

    这个问题和您相关的RTLD_GLOBAL question 都涉及动态加载程序解析它加载的共享库中未定义符号的语义。我希望找到一个明确的文档参考来解释你所看到的,但我一直没能做到。尽管如此,我可以做一个观察来解释正在发生的事情。

    如果我们以冗长的方式运行,我们可以看到 python 库在失败之前尝试加载两个共享库:

    bash-3.2$ PYTHONVERBOSE=1 ./main 2>&1 | grep -i dlopen
    dlopen(".../python2.7/lib-dynload/_locale.so", 2);
    dlopen(".../python2.7/lib-dynload/_ctypes.so", 2);
    

    鉴于第一个成功,我们知道动态加载器通常会针对调用库的命名空间解析未定义的符号。事实上,正如您在另一个问题的 cmets 中所指出的那样,这甚至在有两个版本的 python 库时也有效,即 python 库完成的dlopen()s 解决了它们各自的命名空间。到目前为止,这听起来正是您想要的。但是,为什么_ctypes.so 加载失败?

    我们知道_PyModule_GetDict 是导致_locale.so 无法在您的其他问题中加载的符号;它显然在这里有效。我们也知道符号_PyBuffer_Type 在这里失败了。这两个符号有什么区别?在 python 库中查找它们:

    bash-3.2$ nm libpython2.7.dylib | grep _PyModule_GetDict
    00000000000502c0 T _PyModule_GetDict
    bash-3.2$ nm libpython2.7.dylib | grep _PyBuffer_Type
    0000000000154f90 D _PyBuffer_Type
    

    _PyModule_GetDictText(代码)符号,而 _PyBuffer_TypeData 符号。

    因此,基于此经验数据,我怀疑动态加载器将针对调用库的RTLD_LOCAL 代码符号解析未定义符号,而不是RTLD_LOCAL 数据符号。也许有人可以指出一个明确的参考。

    【讨论】:

    • 感谢您的大力帮助!!我也认为 RTLD_LOCAL 在这里失败,因为文档说Symbols defined in this library are not made available to resolve references in subsequently loaded libraries 所以Py_Initialize 成功,但我看到_local.so 也失败了,但也没有必要启动 Py。我认为总而言之,您的其他评论是正确的,至少 macOS 不支持多次加载同一个库。 (P.S. 万一其他人读到这个,在 Win 上这工作得很好,因为 c 模块明确地将 pythonXX.dll 作为内部依赖)
    • 再次感谢您的贡献!我也为这个问题添加了赏金,我可以在 24 小时后将其附加到您的答案中
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多