【问题标题】:Cannot dlopen a shared library from a shared library, only from executables无法从共享库中 dlopen 共享库,只能从可执行文件中
【发布时间】:2019-11-16 09:27:58
【问题描述】:

我有一个 C++ CMake 项目,它有多个我打包到共享库中的子项目。然后,作为可执行文件的项目本身与所有这些共享库链接。这是一个正在从 Windows 移植到 Ubuntu 的项目。我所做的是让可执行文件 EXE 使用一个子项目 Core 来打开所有其他库。问题是这在 Linux 上不起作用。

这是EXE:

int main(int argc, char *argv[])
{
    core::plugin::PluginManager& wPluginManager = core::plugin::PluginManagerSingleton::Instance();
    wPluginManager.loadPlugin("libcore.so");
    wPluginManager.loadPlugin("libcontroller.so")
    wPluginManager.loadPlugin("libos.so")
    wPluginManager.loadPlugin("libnetwork.so")
    wPluginManager.loadPlugin("liblogger.so")
}

这是core::plugin::PluginManager::loadPlugin()

bool PluginManager::loadPlugin(const boost::filesystem::path &iPlugin) {
    void* plugin_file = dlopen(plugin_file_name, RTLD_LAZY);
    std::cout << (plugin_file ? " success" : "failed") << std::endl;
    return true;
}

发生的情况是 libcore 被正确加载,但是所有其他库都失败了,没有错误消息。我不知道为什么它不起作用。然而,当我做同样的事情,而不是让 Core 加载库时,我只是在 main 中执行它并且它可以工作。

基本上,我可以从exe 加载库,但不能从其他共享库加载。什么给出了,我该如何解决这个问题?

【问题讨论】:

  • “失败,没有错误信息”。那你怎么知道他们失败了?
  • @n.m.因为在调试之后,dlopen 只是简单地返回 null。
  • dlerror()dlopen 失败后返回什么?
  • @PaulSanders 这很奇怪,但是 dlerror() 冻结了我的程序。如果我将它添加为 dlopen() 之后的下一条语句,则将冻结在该行上。即使 dlopen 成功也是如此。
  • 嗯,奇怪。 dlopen 失败后,errno 中的内容是什么?事先将其归零以避免误导性结果。

标签: c++ linux shared-libraries dlopen


【解决方案1】:
bool PluginManager::loadPlugin(const boost::filesystem::path &iPlugin) {
    void* plugin_file = dlopen(plugin_file_name, RTLD_LAZY);
    std::cout << (plugin_file ? " success" : "failed") << std::endl;
    return true;
}

dlopen 使用的标志取决于发行版。我认为 Debian 和衍生品使用 RTLD_GLOBAL | RTLD_LAZY,而 Red Hat 和衍生品使用 RTLD_GLOBAL。或者反之亦然。而且我似乎记得 Android 也使用过RTLD_LOCAL

您应该尝试两者来简化在不同平台上的加载:

bool PluginManager::loadPlugin(const boost::filesystem::path &iPlugin) {
    void* plugin_file = dlopen(plugin_file_name, RTLD_GLOBAL);
    if (!plugin_file) {
        plugin_file = dlopen(plugin_file_name, RTLD_GLOBAL | RTLD_LAZY);
    }
    const bool success = plugin_file != NULL;
    std::cout << (success ? "success" : "failed") << std::endl;
    return success ;
}

发生的情况是 libcore 被正确加载,但是所有其他库都失败了,没有错误消息

这听起来有点不寻常。听起来子项目中的其他库不在链接器路径中。

您应该确保附加库位于链接器路径中。将它们放在文件系统中的 libcore.so 旁边,因为加载 libcore.so 似乎可以按预期工作。

如果它们已经在libcore.so 旁边,那么您需要提供更多信息,例如来自loadPlugin 的失败、使用的RUNPATH(如果存在)和ldd 的输出。


但是所有其他库都失败了,没有错误消息。我不知道为什么它不起作用。

正如@Paul 在 cmets 中所述,检查dlopen 错误的方法是使用dlerror。这是一种很糟糕的方法,因为你只能得到一个文本字符串而不是一个错误代码。

dlopen 手册页位于 http://man7.org/linux/man-pages/man3/dlopen.3.html,上面写着:

返回值

成功时,dlopen() 和 dlmopen() 返回一个非 NULL 句柄 加载的库。出错时(找不到文件,不可读, 格式错误,或在加载过程中导致错误),这些 函数返回 NULL。

成功时,dlclose() 返回 0;出错时,它返回一个非零值。

可以使用 dlerror(3) 诊断来自这些函数的错误。

【讨论】:

  • "用于 dlopen 的标志取决于发行版。" ——这完全是虚假的建议。无论发行版如何,GLIBC 的工作方式都相同。
  • @EmployedRussian - 绝对不是。你应该卷起袖子在 Fedora 和 Ubuntu 上写一些代码。你会明白我的意思。
  • 证明一下?例如。制作正确链接的a.outlibfoo.so,其中a.out 未能在一个发行版上dlopen libfoo.so,然后将精确 相同的二进制文件复制到dlopen 成功的另一个发行版.我相信这是不可能的,但如果你能做到这一点,请提出一个包含所有细节的问题,我很乐意进行调查。
  • @EmployedRussian - 我第一次遇到这个问题是在 2008 年。我在注释 here 中详细说明了它。 2012 年与我共事的一位同事也遇到了同样的问题。我向他展示了我的笔记,后备人员为他清除了它。我和他并肩工作,看着他添加代码。我现在正试图在 Android 上找到我的笔记,但我似乎无法找到它们。我知道它很管用。
【解决方案2】:

主可执行文件中的dlopen 成功和libcore.so 中完全相同的dlopen 失败的最可能原因是主可执行文件具有正确的RUNPATH 以查找所有库,但@987654326 @ 没有。

您可以通过以下方式验证这一点:

readelf -d main-exe | grep R.*PATH
readelf -d libcore.so | grep R.PATH

如果(我怀疑)main-exe 有RUNPATH,而libcore.so 没有,正确的解决方法是将-rpath=.... 添加到libcore.so 的链接行。

您还可以通过使用LD_DEBUG 环境变量来深入了解动态加载器操作:

LD_DEBUG=libs ./main-exe

会告诉你加载器在哪些目录中搜索哪些库,以及为什么。

我不知道为什么它不起作用

是的,你可以。你还没有花足够的精力去尝试。

您的第一步应该是在dlopen 失败时打印dlerror() 的值。下一步是使用LD_DEBUG。如果这一切都失败了,您实际上可以调试运行时加载程序本身——它是开源的。

【讨论】:

  • "主可执行文件具有正确的 RUNPATH 以查找所有库,但 libcore.so 没有。"这没有多大意义。动态加载器不知道对 dlopen 的调用来自哪个地址。
  • 对于RUNPATH,您需要-Wl,--enable-new-dtags。否则你会得到一个RPATH
  • @jww 最新发布的默认值是 new-dtags 和 RUNPATH。如果您通过--disable-new-dtags 启用RPATH,那么问题就会消失,因为RPATH 适用于全局(并且与RUNPATH 不同,完全)。
  • @EmployedRussian 谢谢你,你是对的。 RUNPATH 适用于 exe,但不适用于 so 文件。我正在使用 Eclipse,所以我将工作目录更改为我的 exe 和 so 文件所在的目录。我还尝试为 ldopen 设置绝对路径,这也有效,因为我实际上告诉它在哪里看。
【解决方案3】:

我设法找到了解决此问题的方法。我不太了解内部工作原理,也不太了解我的解决方案,但它确实有效。如果比我对共享库的非常有限的经验有更好的理解的人可以用真实的解释评论我的答案,我相信它可以帮助这个问题的未来观众。

我目前正在做的是dlopen("libcore.so")。我只是将其更改为绝对路径dlopen("/home/user/project/libcore.so"),它现在可以工作了。我还没有尝试过使用相对路径,但似乎我们应该始终使用相对或绝对路径,而不仅仅是带有dlopen 的文件名。

【讨论】:

    【解决方案4】:

    如果绝对路径有帮助,可能问题是共享库的本地依赖关系。换句话说,也许 libcontroller.so 依赖于 libos.so 或您的其他库,但找不到它。 Linux loader 是指所有的共享库都放在/lib、/usr/lib 等目录下,查找动态库需要指定路径,环境变量为LD_LIBRARY_PATH。

    尝试以这种方式运行您的应用: LD_LIBRARY_PATH=/path/to/your/executable/and/modules ./yourapp

    【讨论】:

    • 看起来雇佣俄罗斯人是对的。 RUNPATH 对于 exe 是正确的,但对于共享库则不是。由于 RUNPATH 错误,设置绝对路径或相对路径使项目能够真正知道去哪里寻找。我的项目在eclipse上,所以我所做的就是将工作目录更改为我所有的exe和so文件所在的位置,问题就解决了。
    猜你喜欢
    • 1970-01-01
    • 2015-09-05
    • 2012-08-08
    • 2013-07-04
    • 1970-01-01
    • 2014-01-14
    • 1970-01-01
    • 2019-03-05
    • 2013-11-27
    相关资源
    最近更新 更多