依赖地狱:linux .so 插件动态加载

Dependency Hell: linux .so plugin dynamic loading

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

我的图书馆是 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 中读到 "dynamically loaded modules are completely decoupled from the underlying application",如果它可以正常工作,我想重新配置加载过程来解决问题。

这是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

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

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

很可能 rpath 可以在这种情况下帮助您。有关示例,请参阅 this answer

How can I resolve this version conflict so that my library loads correctly?

这不适用于 libclibstdc++ 等基本的低级库。这些库管理着大量的全局对象(内存管理、线程、环境、语言环境等),并且在同一个地址 space 中拥有它们的多个不同版本是灾难的根源。更何况,不同libstdc++版本的STL类不保证在内存中有相同的二进制布局。应用程序中的 std::string 可能与库中的 std::string 不同。传递一个 from/to 插件可能会导致难以调试的崩溃。 (你得到的关于 libstdc++ 的错误消息部分是为了防止这种情况。)这导致了一个问题,即(即使你 link 静态地 libstdc++)你的功能插件将无法接受或 return STL 类 作为 arguments/return 值 - 只有基本的 C 类型。

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

总体而言,您的选择非常有限:

  • 静态 linking。 Link 具有所需库的静态变体的插件。但是,如果您需要覆盖 libclibstdc++.

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

一般来说,正如其他人之前所说,您应该始终使用与 OS 一起安装的 libclibstdc++,针对不同的 OS 变体发布不同的版本如果需要的话。

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

基本上,您需要处理 3 个库:libclibgcc_slibstdc++libm 不是什么大问题,因为您可以总是静态地 link 反对它)。

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

现在,如果您正在构建一个可执行文件,您可以动态地 link 针对您自己的(rpathed)版本的 libgcc_slibstdc++,并且您准备好了。但是,由于您正在构建一个共享库,因此您没有这样的奢侈。所以你有两个选择。

首先,您可以尝试使用我建议的 libc 相同的技巧。针对旧版本动态 link 并查看它是否有效。从理论上讲,这应该可行,因为 libstdc++ 也使用 ELF 符号版本控制(并且在某种程度上保持向后兼容性)。但是我尝试过做类似的事情并遇到了几个特别讨厌的问题。所以严格测试(尝试从你的 libv_repExtPluginSkeleton.so 中抛出异常,尝试传递短而空的 std::strings,尝试 everything)。

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

所以,综上所述,这是我的建议:

  • 动态地 link 对抗较旧版本的 libc 以确保你也能 link 对抗较新的版本。不过,不要随附它。
  • 静态 link 反对 libgcc_slibstdc++ 不依赖于它们。
  • 提供 C 接口,因为您没有其他(便携式)选项。

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

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

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

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

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

unset LD_LIBRARY_PATH

感谢@fireant 的指点,但不幸的是 link 并没有解决我的问题。

我对解决问题的第一个提示来自这个 Whosebug post on rpath 遵循那个 post 的命令,让你看到当前的 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

当前 3.2.2 版本的 vrep 有一个非常奇怪的 rpath,我认为这是因为在为发布构建 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 创建了一个 symlink 到我希望在 ~/.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 vs 运行path 可能是由于版本差异?):

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

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

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

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

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

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