只有 dlmopen 而不是 dlopen 的未解析符号

unresolved symbol with only dlmopen and not dlopen

我正在使用一个有很多全局变量的共享库, 几乎用在 所有导出的函数,因此库函数不是线程安全的。 我的应用程序创建多个线程,每个线程动态打开它 库并避免在并行调用之间使用任何同步 出口的 函数,我在磁盘上用不同的名称多次复制了库 每个线程打开自己的副本。为了避免这种情况,现在我想改用 dlmopen,但我遇到了一个问题。

当我在我的应用程序中使用 dlopen 打开库时,应用程序运行正常

libHandle = dlopen(ip->pathname, (RTLD_LAZY |RTLD_LOCAL|RTLD_DEEPBIND|RTLD_NODELETE));

当我在应用程序中改用 dlmopen 时,出现错误:

ip->libHandle = dlmopen(LM_ID_NEWLM, ip->pathname,
                (RTLD_LAZY |RTLD_LOCAL|RTLD_DEEPBIND|RTLD_NODELETE));

错误是:

error(libfoo.so.0: undefined symbol: _ZTIN6google8protobuf11MessageLiteE)

执行 nm 确实显示符号未定义 U _ZTIN6google8protobuf11MessageLiteE

问题 1:我想知道如何解决这个问题以便我可以使用 dlmopen。

原因是因为当一个人使用LM_ID_NEWLM时,在libc中创建了一个没有任何符号的新的空命名空间。所以库应该是独立的或与任何依赖项重新链接。

问题 2:我的主应用程序导出了一些 libfoo 将使用的符号。由于在新命名空间中打开 libfoo,主应用程序的符号对 libfoo 不可见,因此无法解析它们。 有什么方法可以告诉链接器创建一个新的命名空间 NEWLM,方法是复制现有的基本命名空间,而不是使用新创建的命名空间的 dlmopen + lmid 打开 libfoo,所有其他必需的符号已经存在?

问题 3:我可以自己映射 libfoo 的不同部分,并提供一个指向映射部分的指针到 libc。意味着完成打开文件并将其从 libc 中映射出来并让它完成符号解析的工作?这样我根本不需要调用 dlopen 并且多文本部分问题将得到解决。

how can I resolve this issue

当您使用dlopen时,新加载的库可以使用所有已经加载的库来解析它的符号。我猜 libprotobuf.so 是此类已加载的库之一。

当您使用 dlmopen(LM_ID_NEWLM, ...) 时,您新加载的库 必须 是完全独立的。

dlmopen 失败的事实告诉您事实并非如此。您应该将 libfoo.so.0 重新链接到 libprotobuf.so(以及它需要的任何其他库)。

使用ldd -r libfoo.so.0验证其中的所有符号都已解析。链接 libfoo.so.0.

时使用 -Wl,--no-undefined 也是一个好主意

更新:

My main application exports some symbols which libfoo will use. Due to opening libfoo in new namespace, the symbols of main application are not visible to libfoo and hence it is not able to resolve them.

这是预期的行为。如果此类符号的数量相当少,您可以使用 libfoo:

显式注册它们
void *h = dlmopen(...);
void (*init)(void *, void *) = dlsym(h, 'init');
(*init)(&main_fn1, &main_fn2);

Is there any way to tell linker to create a new namespace NEWLM, by making replica of existing base namespace and than use dlmopen + lmid of newly created namespace to open libfoo with all other required symbols being already present?

我不这么认为。这是一个有趣的想法。欢迎在 glibc bugzilla.

中提出功能请求

With dlmopen it seems plausible (although max limit of 16)

在我看来,虽然 libfoo 的 16 个实例比一个要好,但您在这条道路上仍然受到严重限制,重写 libfoo 以不使用全局变量会好得多首先。

更新二:

Can I myself mmap the different section of libfoo and provide a pointer to the mmaped sections to libc

如果实现了 GLIBC bug 11767,你可以。但事实并非如此。

Is there any way to tell linker to create a new namespace NEWLM, by making replica of existing base namespace and than use dlmopen + lmid of newly created namespace to open libfoo with all other required symbols being already present?

这是我解决类似问题的方法:

  1. 将 protobuf 动态加载到新的命名空间中:

    void* pb_handle = dlmopen(LM_ID_NEWLM, "libprotobuf.so", RTLD_LAZY);
    
  2. 获取命名空间id:

    Lmid_t lmid;
    dlinfo(dl_handle, RTLD_DI_LMID, &lmid);
    
  3. 在新的 protobuf 命名空间中打开 foo:

    void* foo_handle = dlmopen(lmid, “libfoo.so.0”, RTLD_LAZY);
    

dlmopen() 之后执行 dlerror() 以检查此 dll 是否已加载。 (检查 dlmopen() 的 return 值是否为 nullptr?)

您可能会注意到,地址清理器之类的东西会在 .so 库中放置一些未定义的符号,并在入口 ELF 文件(int main() 放置的)

中实现

要检查这一点,使用:

readelf -s exe_main |grep __asan_init
readelf -s libfoo.so |grep __asan_init

在我的系统中,我可以看到以下内容:

[vrqq@rhel]$ readelf -s ./out/libfoo.so |grep __asan_init
    26: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND __asan_init
   226: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND __asan_init
[vrqq@rhel]$ readelf -s ./out/exe_main |grep __asan_init
  1279: 00000000002fe430    96 FUNC    GLOBAL DEFAULT   16 __asan_init
  5017: 00000000002fe430     0 NOTYPE  LOCAL  DEFAULT   16 .annobin___asan_init.star
  5018: 00000000002fe490     0 NOTYPE  LOCAL  DEFAULT   16 .annobin___asan_init.end
  9044: 00000000002fe430    96 FUNC    GLOBAL DEFAULT   16 __asan_init