库期望平面命名空间中的符号,尽管使用两级命名空间编译

Library expects symbol in flat namespace although compiled with two-level namespace

本文关键字:命名空间 编译 两级 平面 期望 符号      更新时间:2023-10-16

我用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;
}

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

如果我们以冗长的方式运行,我们可以看到 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数据符号。也许有人可以指出一个明确的参考。