【问题标题】:unresolved symbol with only dlmopen and not dlopen只有 dlmopen 而不是 dlopen 的未解析符号
【发布时间】:2016-02-03 12:38:26
【问题描述】:

我正在使用一个有很多全局变量的共享库, 几乎用在 所有导出的函数,因此库函数不是线程安全的。 我的应用程序创建了多个线程,每个线程动态地打开它 库并避免在并行调用之间使用任何同步 对出口 函数,我在磁盘上多次复制了具有不同名称的库 每个线程都打开自己的副本。为避免这种情况,现在我希望改用 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));

dlerror 是:

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 并且多个文本部分的问题将得到解决。

【问题讨论】:

    标签: multithreading dll linker dynamic-linking dlopen


    【解决方案1】:

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

    您可能会注意到,诸如地址清理器之类的东西会在 .so 库中放置一些 UNDEFINED 符号并在入口 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
    

    【讨论】:

      【解决方案2】:

      有没有办法告诉链接器创建一个新的命名空间 NEWLM,通过复制现有的基本命名空间,而不是使用新创建的命名空间的 dlmopen + lmid 打开 libfoo,所有其他必需的符号都已经存在?

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

      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);
        

      【讨论】:

        【解决方案3】:

        我该如何解决这个问题

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

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

        dlmopen 失败的事实告诉你它不是。您应该重新链接 libfoo.so.0libprotobuf.so(以及它需要的任何其他库)。

        使用ldd -r libfoo.so.0 验证其中的所有符号都已解析。在链接libfoo.so.0 时使用-Wl,--no-undefined 也是一个好主意。

        更新:

        我的主应用程序导出了一些 libfoo 将使用的符号。由于在新命名空间中打开 libfoo,主应用程序的符号对 libfoo 不可见,因此无法解析它们。

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

        void *h = dlmopen(...);
        void (*init)(void *, void *) = dlsym(h, 'init');
        (*init)(&main_fn1, &main_fn2);
        

        有没有办法告诉链接器创建一个新的命名空间 NEWLM,通过复制现有的基本命名空间,而不是使用新创建的命名空间的 dlmopen + lmid 打开 libfoo,所有其他必需的符号都已经存在?

        我不相信。这是一个有趣的想法。随时在glibc bugzilla 中提出功能请求。

        使用 dlmopen 似乎是合理的(尽管最大限制为 16)

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

        更新 2:

        我可以自己映射 libfoo 的不同部分并提供指向映射到 libc 的部分的指针

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

        【讨论】:

        • 感谢您的回复。可以将 libfoo 与 libproto 和所有其他必需的库重新链接。但我有一个更大的问题。我的主应用程序导出了一些 libfoo 将使用的符号。由于在新命名空间中打开 libfoo,主应用程序的符号对 libfoo 不可见,因此无法解析它们。有没有办法告诉链接器创建一个新的命名空间 NEWLM,通过复制现有的基本命名空间,而不是使用新创建的命名空间的 dlmopen + lmid 打开 libfoo,所有其他必需的符号都已经存在?
        • 这个活动的关键是要避免多次复制同一个库导致的文本部分的多个副本,因为每个副本实例会有不同的inode号。将此与 inode 相同时的情况进行比较。在这种情况下,文本部分将在每个 dlopen 实例之间共享,而每个实例都有自己的私有数据副本。不幸的是,dlopen 不允许多次打开,因为 libc 代码会检查 inode 是否已在当前命名空间中打开。使用 dlmopen 似乎是合理的(尽管最大限制为 16),但空的命名空间给我带来了问题。
        • 感谢您的回答。我同意采用 dlmopen 方式也不是一个完整的解决方案,但更好一些。我也同意,更改 libfoo 将是最合适的路径。不过,我有一个(希望是最后一个)问题。这只是一个海军问题,请原谅我对链接/加载没有足够的知识。我可以自己映射 libfoo 的不同部分并提供指向映射到 libc 的部分的指针吗?意味着打开文件并从libc中映射出来并让它完成符号解析的工作?这样我根本不需要调用 dlopen 并且多个文本部分的问题将得到解决
        猜你喜欢
        • 2022-10-13
        • 1970-01-01
        • 1970-01-01
        • 2014-10-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-10
        相关资源
        最近更新 更多