尽管使用两级命名空间编译,但库仍需要平面命名空间中的符号

2023-12-28

我动态加载Pythondlopen and RTLD_LOCAL以避免与另一个库发生冲突,该库碰巧包含一些同名的符号。执行我的MVCE上面在 macOS 上使用 Xcode 失败了,因为它期望_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覆盖二级命名空间?

I used 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;
}

这个问题和你的相关RTLD_GLOBAL 问题 https://stackoverflow.com/questions/49394526/rtld-global-and-two-level-namespaces-on-macos/49480920#49480920两者都涉及动态加载器解析其加载的共享库中未定义符号的语义。我希望找到一个明确的文档参考来解释您所看到的内容,但我无法做到这一点。尽管如此,我可以做出一个可以解释正在发生的事情的观察。

如果我们详细地运行,我们可以看到 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 库(即dlopen()python 库完成的解析针对其各自的名称空间。到目前为止,这听起来正是您想要的。但是,为什么是_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_GetDict is a Text(代码)符号,而_PyBuffer_Type is a Data symbol.

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

尽管使用两级命名空间编译,但库仍需要平面命名空间中的符号 的相关文章

随机推荐