libpthread.so 在 `dlclose()` 之后继续使用 TLS space 和 DL 名称space

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

我正在做一个项目,需要任意 load/unload 基于 Rust 的插件(共享对象)到隔离的动态库命名空间中。

我使用 dlmopen(LM_ID_NEWLM, "rust-plugin.so", RTLD_LAZY) 为共享对象创建新的命名空间。当不再需要共享对象时,我调用 dlclose().

不幸的是,我发现即使我 dlclose() 一次只有一个共享对象有效,在 dlmopen()ing 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.0NODELETE 标志:

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

这使得 dlclose() 成为空操作。另见 answer.

鉴于 dlclose() 是空操作,其他一切都有意义:GLIBC 配置了 16 个加载程序命名空间,其中一个是为主应用程序保留的。一旦你调用 dlmopen(没有调用 dlclose)15 次,你就用尽了它们,随后的尝试失败并显示 no more namespaces available.

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

Is there some way I can have libpthread properly clean up so I can avoid this issue?

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

您可以做的其他事情:

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