尽管使用两级命名空间

时间:2018-03-20 20:32:30

标签: python c++ macos linker symbols

我使用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是否使用Two-Level命名空间选项进行编译。根据我的理解,符号解析需要库名称+符号名称本身。为什么它在平面命名空间中期望_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;
}

1 个答案:

答案 0 :(得分:2)

这个问题和你的相关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);

鉴于第一个成功,我们知道通常动态加载器正在针对调用库的命名空间解析未定义的符号。事实上,正如你在其他问题的评论中所指出的那样,当有两个版本的python库时,这甚至可以工作,即python库完成的dlopen()解析它们各自的命名空间。到目前为止,这听起来完全是你想要的。但是,为什么_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数据符号。也许有人可以指出一个明确的参考。