我正在开发一个项目,该项目需要将基于Rust的插件(共享对象)任意加载/卸载到隔离的动态库 namespace 中。

我使用dlmopen(LM_ID_NEWLM, "rust-plugin.so", RTLD_LAZY)为共享对象创建新的 namespace 。当不再需要共享对象时,我称为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文件),可以通过以下代码无限期地打开和关闭,而libpthread.so在我打开和关闭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()成为无操作对象。另请参见answer

假定dlclose()是no-op,那么其他一切都说得通:GLIBC总共配置了16个加载器 namespace ,其中一个保留给主应用程序。一旦您调用dlmopen(不调用dlclose)15次,就将它们全部用尽,随后的尝试会失败,并使用no more namespaces available失败。

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



我认为,唯一可行的选择是避免依赖插件中的libpthread

您可以做的其他事情:
  • 打开了针对GLIBC的错误(但您可能需要等待很长时间才能得到修复)。我不确定从非默认加载器作用域卸载libpthread的含义是什么;也许应该是可卸载的,
  • 构建您自己的libpthread.so,与系统版本相同,但没有-z,nodelete链接器标志,并安排将此libpthread.so.0作为插件依赖项加​​载(确保此版本与系统版本保持同步,否则您将看到非常难以调试的崩溃)。
  • 关于c++ - `dlclose()`之后,libpthread.so继续使用TLS空间和DL namespace ,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44446683/

    10-11 16:31