libpthread.so 在 'dlclose()' 之后继续使用 TLS 空间和 DL 命名空间

libpthread.so continues to use TLS space and DL namespaces after `dlclose()`

本文关键字:TLS 空间 命名空间 DL so dlclose 之后 libpthread 继续      更新时间:2023-10-16

我正在处理一个需要将基于 Rust 的插件(共享对象)任意加载/卸载到隔离的动态库命名空间中的项目。

我使用dlmopen(LM_ID_NEWLM, "rust-plugin.so", RTLD_LAZY)为共享对象创建一个新的命名空间。当不再需要共享对象时,我调用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 文件,就此而言)可以通过以下代码无限期地打开和关闭,而在我打开和关闭它 14 次后libpthread.so错误。

#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: %sn", file, dlerror());
exit(1);
}
if (dlclose(handle_) != 0) {
exit(2);
}
}
int main() {
void *handle_;
for (int i = 0; true; i++) {
printf("%dn", i);
load("libpthread.so.0");
}
}

有什么方法可以让libpthread正确清理,这样我就可以避免这个问题?

libpthread.so.0NODELETE标志:

readelf -d /lib/x86_64-linux-gnu/libpthread.so.0 | grep NODELETE
0x000000006ffffffb (FLAGS_1)            Flags: NODELETE INITFIRST

这使得dlclose()成为无操作。另请参阅此答案。

鉴于dlclose()是无操作的,其他一切都有意义:GLIBC 总共配置了 16 个加载程序命名空间,其中一个保留给主应用程序。一旦您调用dlmopen(不调用dlclose)15 次,您将耗尽所有尝试,后续尝试将失败并no more namespaces available

NODELETE标记libpthread是有意义的:一旦它出现在图片中,它就会从根本上改变 GLIBC 操作(例如malloc开始获取锁,errno切换到线程本地等)。

有什么方法可以让libpthread正确清理,这样我就可以避免这个问题?

我相信对您来说唯一现实的选择是尽量避免依赖插件中的libpthread

您可以执行的其他操作:

  1. 打开针对 GLIBC 的错误(但您可能会等待很长时间才能修复它)。我不确定从非默认加载程序范围卸载libpthread的含义是什么;也许它应该是可卸载的,
  2. 构建你自己的 libpthread.so,与系统相同,但没有-z,nodelete链接器标志,并安排将此libpthread.so.0作为插件依赖项加载(确保此版本与系统版本保持同步,否则您将看到很难调试崩溃)。