我正在处理一个项目,需要随意地将基于Rust的插件(共享对象)加载/卸载到独立的dynamic库名称空间中。
我使用dlmopen(LM_ID_NEWLM, "rust-plugin.so", RTLD_LAZY)
为共享对象创build一个新的名称空间。 当共享对象不再需要时,我调用dlclose()
。
不幸的是,我发现,即使当我dlclose()
以便一次只有一个共享对象是有效的,在dlmopen()
14 Rust插件对象后,我得到的错误:
dlmopen(rust-plugin.so) failed: /lib/x86_64-linux-gnu/libc.so.6: cannot allocate memory in static TLS block
在失败之后继续尝试dlmopen()
导致分段错误,并且no more namespaces available for dlmopen()
。
我似乎已经将问题隔离到Rust共享对象的libpthread.so
依赖项。 其他共享对象依赖关系,如libgcc_s.so.1
(以及任何.so文件,我已经尝试过)可以通过无限期地被下面的代码打开和closures,而libpthread.so
错误在我打开和closures它14次之后。
#include <link.h> #include <stdio.h> #include <dlfcn.h> #include <cstdlib> void load(char const *file) { void *handle_ = dlmopen(LM_ID_NEWLM, file, RTLD_LAZY); if (!handle_) { printf("dlmopen(%s) failed: %s\n", file, dlerror()); exit(1); } if (dlclose(handle_) != 0) { exit(2); } } int main() { void *handle_; for (int i = 0; true; i++) { printf("%d\n", i); load("libpthread.so.0"); } }
有没有什么办法可以让libpthread正确清理,这样我可以避免这个问题?
libpthread.so.0
有NODELETE
标志:
readelf -d /lib/x86_64-linux-gnu/libpthread.so.0 | grep NODELETE 0x000000006ffffffb (FLAGS_1) Flags: NODELETE INITFIRST
这使得dlclose()
在它上面没有任何操作。 另请参阅此答案 。
鉴于dlclose()
是no-op,所有其他内容都是有意义的:GLIBC配置了16个加载器名称空间,其中一个是为主应用程序保留的。 一旦调用dlmopen
(不调用dlclose
)15次,就会耗尽它们,随后的尝试失败,并且no more namespaces available
。
使用NODELETE
标记libpthread
是有道理的:一旦它在图片中,就会从根本上改变GLIBC操作(例如malloc
开始获取锁, errno
切换到线程本地等等)。
有没有什么办法可以让libpthread正确清理,这样我可以避免这个问题?
我相信你唯一现实的选择是尽量避免从你的插件依赖libpthread
。
其他事情你可以做:
libpthread
的影响是什么; 也许它应该是无法载入的, -z,nodelete
链接器标志,并安排这个libpthread.so.0
作为插件依赖项加载(确保这个版本与系统保持同步一个,否则你会看到很难调试崩溃)。