无法从共享库中打开共享库,只能从可执行文件中打开

Cannot dlopen a shared library from a shared library, only from executables

我有一个 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 加载库,但不能从其他共享库加载库。是什么原因造成的?我该如何解决?

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。或者可能是 vice-versa。我似乎还记得 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 ;
}

What happens is that libcore gets loaded properly, but then all other libraries fail with no no error message

这听起来有点不寻常。听起来 sub-projects 中的附加库不在链接器路径中。

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

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


but then all other libraries fail with no no error message. I cannot find out why it's not working.

正如@Paul 在评论中所述,检查 dlopen error is with dlerror 的方法。这是一种蹩脚的方法,因为你只能得到一个文本字符串而不是错误代码。

dlopen man page is at http://man7.org/linux/man-pages/man3/dlopen.3.html,上面写着:

RETURN VALUE

On success, dlopen() and dlmopen() return a non-NULL handle for the loaded library. On error (file could not be found, was not readable, had the wrong format, or caused errors during loading), these functions return NULL.

On success, dlclose() returns 0; on error, it returns a nonzero value.

Errors from these functions can be diagnosed using dlerror(3).

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

您可以通过以下方式验证:

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

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

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

LD_DEBUG=libs ./main-exe

会告诉您加载程序正在搜索哪些库,以及原因。

I cannot find out why it's not working

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

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

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

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

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

指定路径

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