【问题标题】:Dependency Hell: linux .so plugin dynamic loading依赖地狱:linux .so插件动态加载
【发布时间】:2016-01-30 03:58:59
【问题描述】:

我使用 linuxbrew 创建了一个使用独立构建树构建的共享库,由于依赖冲突,它无法加载到父应用程序中。我正在使用一个单独的应用程序,它在使用 Qt5 QLibrary 类启动后动态加载库。

我的图书馆是libv_repExtPluginSkeleton.so。它和父应用程序都依赖于 glibc 和 libstdc++。所有主应用程序的依赖项都在/usr/lib 中,而我所有库的依赖项都在~/.linuxbrew/lib 中。

当父应用程序去加载.so失败时,我用LD_DEBUG=all "$dirname/$appname"调试失败并在输出中发现以下错误报告:

  2610: file=/home/hbr/V-REP_PRO_EDU_V3_2_2_64_Linux/libv_repExtPluginSkeleton.so [0];  dynamically loaded by libQt5Core.so.5 [0]
  2610: file=/home/hbr/V-REP_PRO_EDU_V3_2_2_64_Linux/libv_repExtPluginSkeleton.so [0];  generating link map
  2610:   dynamic: 0x00007fd063cff570  base: 0x00007fd063ae7000   size: 0x000000000021a6a8
  2610:     entry: 0x00007fd063af1150  phdr: 0x00007fd063ae7040  phnum:                  5
  2610: 
  2610: checking for version `GCC_3.0' in file /lib/x86_64-linux-gnu/libgcc_s.so.1 [0] required by file /home/hbr/V-REP_PRO_EDU_V3_2_2_64_Linux/libv_repExtPluginSkeleton.so [0]
  2610: checking for version `GLIBC_2.14' in file /lib/x86_64-linux-gnu/libc.so.6 [0] required by file /home/hbr/V-REP_PRO_EDU_V3_2_2_64_Linux/libv_repExtPluginSkeleton.so [0]
  2610: checking for version `GLIBC_2.2.5' in file /lib/x86_64-linux-gnu/libc.so.6 [0] required by file /home/hbr/V-REP_PRO_EDU_V3_2_2_64_Linux/libv_repExtPluginSkeleton.so [0]
  2610: checking for version `CXXABI_1.3' in file /usr/lib/x86_64-linux-gnu/libstdc++.so.6 [0] required by file /home/hbr/V-REP_PRO_EDU_V3_2_2_64_Linux/libv_repExtPluginSkeleton.so [0]
  2610: checking for version `GLIBCXX_3.4.9' in file /usr/lib/x86_64-linux-gnu/libstdc++.so.6 [0] required by file /home/hbr/V-REP_PRO_EDU_V3_2_2_64_Linux/libv_repExtPluginSkeleton.so [0]
  2610: checking for version `GLIBCXX_3.4.21' in file /usr/lib/x86_64-linux-gnu/libstdc++.so.6 [0] required by file /home/hbr/V-REP_PRO_EDU_V3_2_2_64_Linux/libv_repExtPluginSkeleton.so [0]
  2610: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: error: version lookup error: version `GLIBCXX_3.4.21' not found (required by /home/hbr/V-REP_PRO_EDU_V3_2_2_64_Linux/libv_repExtPluginSkeleton.so) (fatal)
  2610: 
  2610: file=/home/hbr/V-REP_PRO_EDU_V3_2_2_64_Linux/libv_repExtPluginSkeleton.so [0];  destroying link map

如您所见,当我的库加载时,似乎已经加载的glibc版本在/usr/lib,但我的库需要加载~/.linuxbrew/lib中的版本。

QLibrary 用于加载插件如下:

plug=new CPlugin(filename,pluginName);
int loadRes=plug->load();

我在The Inside Story on Shared Libraries and Dynamic Loading 中读到“动态加载的模块与底层应用程序完全解耦”,如果可以正常工作,我想重新配置加载过程来解决问题。

这里是ldd找到的依赖列表,说明了不同位置的依赖重叠:

+ ldd /home/hbr/V-REP_PRO_EDU_V3_2_2_64_Linux/libv_repExtPluginSkeleton.so
    linux-vdso.so.1 (0x00007fffc17af000)
    libstdc++.so.6 => /home/hbr/.linuxbrew/lib/libstdc++.so.6 (0x00007ff5b9a32000)
    libm.so.6 => /home/hbr/.linuxbrew/lib/libm.so.6 (0x00007ff5b9742000)
    libgcc_s.so.1 => /home/hbr/.linuxbrew/lib/libgcc_s.so.1 (0x00007ff5b9531000)
    libc.so.6 => /home/hbr/.linuxbrew/lib/libc.so.6 (0x00007ff5b91b9000)
    /home/hbr/.linuxbrew/Cellar/glibc/2.19/lib64/ld-linux-x86-64.so.2 (0x00007ff5b9f81000)
+ ldd /home/hbr/V-REP_PRO_EDU_V3_2_2_64_Linux/vrep
    linux-vdso.so.1 =>  (0x00007ffc333f9000)
    liblua5.1.so (0x00007fc10e763000)
    libQt5Core.so.5 (0x00007fc10df28000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fc10dd0a000)
    libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fc10da06000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fc10d7f0000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc10d42b000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fc10d125000)
    libicui18n.so.54 (0x00007fc10ccb7000)
    libicuuc.so.54 (0x00007fc10c909000)
    libicudata.so.54 (0x00007fc10aedf000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc10acdb000)
    libgthread-2.0.so.0 => /usr/lib/x86_64-linux-gnu/libgthread-2.0.so.0 (0x00007fc10aad9000)
    librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007fc10a8d1000)
    libglib-2.0.so.0 => /lib/x86_64-linux-gnu/libglib-2.0.so.0 (0x00007fc10a5c9000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fc10e66d000)
    libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007fc10a38b000)
+ LD_DEBUG=all /home/hbr/V-REP_PRO_EDU_V3_2_2_64_Linux/vrep

我的启动脚本将LD_LIBRARY_PATH 中的第一个条目设置为~/.linuxbrew/lib,但应用程序仍然首先获取系统版本。

另外,对于我自己的插件库,RPATH 是正确的:

objdump -x libv_repExtPluginSkeleton.so |grep RPATH
RPATH                /home/hbr/.linuxbrew/lib

如何解决此版本冲突,以便我的库正确加载?

另外,这是否可以在不更改父应用程序或我的库的完整工具链的情况下实现?

【问题讨论】:

  • 我建议你使用系统库重建你的库。
  • @Anon Mail 我很感激你的建议,如果它工作得很好,我会这样做,但我不能保证给定的平台会支持我需要的依赖版本。
  • 你能构建你的库,让它静态链接它的依赖库吗?
  • 您是否尝试设置LIBRARY_PATH?有关说明,请参见此处:stackoverflow.com/a/13292386/4181011

标签: c++ linux dependencies shared-libraries dynamic-loading


【解决方案1】:

如何解决此版本冲突,以便我的库正确加载?

这不适用于libclibstdc++ 等基本的低级库。这些库管理全局对象的数量(内存管理、线程、环境、语言环境等),并且在同一地址空间中拥有它们的多个不同版本是灾难的根源。更重要的是,不同libstdc++版本的STL类不保证在内存中有相同的二进制布局。应用程序中的std::string 可能与库中的std::string 不同。从/向插件传递一个可能会导致难以调试的崩溃。 (您收到的libstdc++ 的错误消息部分是为了防范这种情况。)这会导致问题(即使您静态链接libstdc++)您的插件的功能将无法接受或返回 STL 类作为参数/返回值 - 仅限基本 C 类型。

此外,对于动态加载,另一个障碍是 Linux/UNIX 链接器使用平面线性库列表和其符号的平面线性列表。库由内部soname 标识,而不是文件名。链接器会将寻找的符号绑定到第一个符号,通过线性搜索通过库及其符号列表找到。稍后加载的库将从已为应用程序本身加载的库中获得大部分符号。您的插件引用的库甚至可能不会被查看。

总的来说,您的选择非常有限:

  • 静态链接。将插件与所需库的静态变体链接。但是,如果您需要覆盖 libclibstdc++,这将不起作用。

  • 服务器/客户端方法。插件库是一个瘦包装器,它只启动服务器进程,然后通过某种 RPC 机制将所有从应用程序到插件的调用重定向到该服务器应用程序。这是我知道的解决库版本冲突的唯一可靠方法。由于与库不同,应用程序可以更好地控制动态链接器(rpath,或者在最坏的情况下使用LD_PRELOAD)。

通常,正如其他人之前所说,您应该始终使用随操作系统安装的libclibstdc++,如果需要,为不同的操作系统变体发布不同的构建。

【讨论】:

  • libstdc++ 中的 STL 类上的错误 cmets 需要引用(这里旧版本已经有 libstdc++.so.6,所以它不是古老的)。
  • 幸运的是,我使用的 API 似乎坚持跨插件/应用程序边界的 C 接口,它也与 lua 高度集成,然后提供 c++ 类以使与这些 c api 的集成更容易。
  • @Marc Glisse "需要引用" 究竟是什么评论? “错误”是一种不好的识别。关于 C++ 类/模板的二进制兼容性问题,您可以查看 KDE 的技术库:techbase.kde.org/Policies/… GNU 的 STL 库以保持长期的二进制兼容性而闻名,但它也不是永远的:从 GCC 5.x 开始, STL 有新的 ABI。大多数其他编译器甚至不关心这种长期兼容性。
  • @AndrewHundt,然后 (1) 使用操作系统的 libc,(2) 尝试静态链接 libstdc++。尝试跨发行版进行测试:libc 的二进制接口非常稳定,除非您使用一些新功能,否则针对新的libc 编译的应用程序/库可能也适用于旧的libc。 (但关于静态 GNU libstdc++ 你必须在别处询问 - 我自己没有尝试过。我们为此目的使用了静态 STLport。)
【解决方案2】:

rpath 最有可能在这种情况下为您提供帮助。例如,请参阅this answer

【讨论】:

  • 我将LD_LIBRARY_PATH 中的第一个条目设置为~/.linuxbrew/lib,但它仍然首先获取系统版本。我可以更改已构建的应用程序/库的 rpath 吗?另外,对于我自己的插件库,RPATH 是正确的:objdump -x libv_repExtPluginSkeleton.so |grep RPATHRPATH /home/hbr/.linuxbrew/lib
  • 那么也许您可以编写一个小型测试程序,例如 test.c,它会产生相同的问题,但使用您可以更轻松地使用和版本的小型库。可能在处理该问题时,您会发现问题所在,或者这里的其他人可以处理相同的代码并提供帮助...
  • 这并没有直接解决我的问题,但它为我指明了寻找正确事物的道路,所以我至少给了你我的赏金。谢谢!
【解决方案3】:

据我了解,您希望构建一个可跨不同 Linux 发行版移植的共享库。这很棘手。特别是因为您正在处理 C++。

基本上,您需要处理 3 个库:libclibgcc_slibstdc++libm 并不是什么大问题,因为您始终可以静态链接它)。

第一个 (libc) 似乎是最大的问题,因为您既不能静态链接它,也不能提供自己的版本。但是,如果您使用简单的技巧,则不需要。 GNU C 库使用符号版本控制并且向后兼容。你可以依靠这个。只需使用一个相当旧的libc.so.6 版本(2.11 版本已经足够老,可以确保您几乎可以在当今使用的所有发行版上工作,但您可能需要更新的版本)并动态链接它。不过,不要将它与您的 libv_repExtPluginSkeleton.so 一起发送。您正在链接它以确保您的 libv_repExtPluginSkeleton.so 将动态链接到目标机器上的(可能较新的)系统提供的版本。您依赖的是向后兼容性。

现在,如果您正在构建一个可执行文件,您可以动态链接到您自己的 (rpathed) 版本的 libgcc_slibstdc++,然后您就可以开始了。但是,由于您正在构建一个共享库,因此您没有这样的奢侈。所以你有两个选择。

首先,您可以尝试使用与我建议的 libc 相同的技巧。动态链接到旧版本,看看它是否有效。理论上,这应该可行,因为libstdc++ 也使用 ELF 符号版本控制(并且有点保持向后兼容性)。但是我尝试过做类似的事情并面临几个特别讨厌的问题。所以要严格测试(尝试从您的libv_repExtPluginSkeleton.so 抛出异常,尝试传递短而空的std::strings,尝试一切)。

或者,您可以静态链接到libgcc_slibstdc++。这是我推荐的。这可以通过-static-libgcc-static-libstdc++ 选项轻松完成。但是,当然,有一个问题。 libv_repExtPluginSkeleton.so 提供 C++ 接口或从其他库调用 C++ 函数不再安全。原因是libstdc++在不同版本之间不是特别兼容。

所以,总结前面提到的所有内容,这是我的建议:

  • 动态链接到旧版本的libc,以确保您也链接到新版本。不过,不要随身携带。
  • 静态链接到 libgcc_slibstdc++ 以不依赖它们。
  • 提供 C 接口,因为您没有其他(便携式)选项。

当然,这违背了您不更改用于构建库的工具链的意愿。你失去了 C++ 接口。而且您还必须用libc 功能换取兼容性。但至少你获得兼容性。

是的,不要忘记许可问题。

【讨论】:

    【解决方案4】:

    您应该在文件系统的某个位置有一个配置文件,其中为您的应用程序定义了默认模块文件夹。 Vim 并将此路径编辑为 ~/.linuxbrew/lib。重新运行。如果您仍然遇到模块错误,请重新安装正确版本的模块,因为您的错误消息显示并将模块复制到 ~/.linuxbrew/lib 路径。最后,始终检查模块和/或模块路径的权限和所有权。由于简单的所有权/权限问题,您的应用程序可能无法获取这些模块。

    【讨论】:

    • 你能解释一下如何检查权限和所有权吗?还有什么会重新安装帮助?据我所知,所有依赖项都是全新构建并安装在它们所属的正确位置,并且操作系统版本也是正确的(对于操作系统)
    • 您的第一条错误消息显示在查找“GLIBCXX_3.4.21”版本时出现问题。你有这个安装吗?如果没有,您应该安装它。在存储模块的目录中运行命令 ls -la 以显示模块的所有者/组和权限。如果权限不包括您用于运行应用程序的用户的读取或执行权限,则模块可能无法正确加载。
    • 看起来您在上面找到了一个可行的答案。干得好!
    【解决方案5】:

    此答案将针对我的用例,但许多命令和设置可以推广到其他应用程序和配置。我还有一个more detailed discussion of my debugging of this specific problem on the vrep forum

    首先我更改了 vrep.sh 以完全清除 LD_LIBRARY_PATH,因此它不会干扰正在加载的内容。

    unset LD_LIBRARY_PATH
    

    感谢@fireant 的指点,但不幸的是,这个链接本身并不能解决我的问题。

    我解决该问题的第一个提示来自stackoverflow post on rpath,遵循该帖子中的命令,可让您查看当前的 rpath:

    objdump -x binary-or-library |grep RPATH
    
    # Maybe an even better way to do it is the following:
    readelf -d binary-or-library |head -20
    
    # This second command above also lists the direct dependencies on other libraries followed by rpath.
    
    # On ubuntu 15.04 someone had to use:
    objdump -x binary-or-library |grep RUNPATH
    

    vrep 的当前 3.2.2 版本有一个非常奇怪的 rpath,我相信这是因为在为 release 构建 vrep 时可能没有明确配置它:

    $ objdump -x vrep |grep RPATH
      RPATH                /home/marc/Qt5.2.0/5.2.0/gcc_64:/home/marc/Qt5.2.0/5.2.0/
    

    为了解决我的问题,我创建了一个从 $VREPDIR/lib 到我希望在 ~/.linuxbrew/lib 中找到我的库的符号链接,然后使用以下命令 I changed the rpath of the vrep executable

    VREPDIR=/path/to/vrep/executable
    cd $VREPDIR
    ln -s ~/.linuxbrew/lib $VREPDIR/lib
    patchelf --set-rpath '$ORIGIN/lib' vrep
    

    请注意,ld/rpath/patchelf 系统将 $ORIGIN 理解为 vrep 可执行文件所在的目录。运行上述代码后,我有一个新的 RUNPATH,如下所示(RPATH 与 runpath 可能是由于版本差异造成的?):

    $ objdump -x vrep |grep RUNPATH
      RUNPATH              $ORIGIN/lib
    

    这个解决方案并不理想,我可能需要修复它,因为它正在按如下方式安装 linuxbrew Qt:

    libQt5Core.so.5 => /usr/lib/x86_64-linux-gnu/libQt5Core.so.5 (0x00007fece8993000)
    

    这可能与 vrep 旁边的目录中提供的 Qt 共享库中 V-REP 使用的 API 冲突,尤其是在更新 linuxbrew 时。尽管如此,此解决方案仍然有效,并且 vrep 正在加载 3 个 .so 库中的 2 个而不会崩溃!一项改进是进行如下所述的一些小改动,以便仍然可以加载随 vrep 提供的库。

    实际上,对我来说理想的解决方案是 vrep 加载系统库,插件加载 .linuxbrew 库。我不确定这是否可行,如果可行,可能需要修改 vrep 源/构建。

    虽然应用程序与此解决方案一起工作,但存在一些潜在问题,我不确定它是否是理想的解决方案。尽管如此,我认为它可能对其他在使用的应用程序中遇到类似问题的人有用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-08
      • 1970-01-01
      • 2015-12-21
      • 1970-01-01
      相关资源
      最近更新 更多