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

Dependency Hell: linux .so plugin dynamic loading

本文关键字:插件 动态 加载 so linux 地狱 依赖      更新时间:2023-10-16

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

我的图书馆是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();

我在《共享库和动态加载内幕》中读到,"动态加载的模块与底层应用程序完全解耦",如果可以正常工作,我想重新配置加载过程来解决这个问题。

以下是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

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

此外,在不更改父应用程序或我的库的完整工具链的情况下,这可能吗?

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

这对于像libclibstdc++这样的基本、低级库是不起作用的。这些库管理大量的全局对象(内存管理、线程、环境、区域设置等),在同一地址空间中有几个不同版本的全局对象会带来灾难。此外,不同libstdc++版本的STL类不能保证在内存中具有相同的二进制布局。应用程序内部的std::string可能与库中的std::string不同。将一个from/to插件传递可能会导致难以调试的崩溃。(您收到的libstdc++的错误消息在一定程度上是为了防止这种情况的发生。)这导致了一个问题,即(即使您静态链接libstdc++)插件的函数将无法接受或返回STL类作为参数/返回值,而只能接受基本的C类型。

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

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

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

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

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

在这种情况下,rpath很可能会帮助您。例如,请参阅此答案。

据我所知,您希望构建一个可在不同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::string s,尝试所有)。

或者,可以针对libgcc_slibstdc++进行静态链接。这是我的建议。使用-static-libgcc-static-libstdc++选项可以很容易地做到这一点。但是,当然,也有一个陷阱。libv_repExtPluginSkeleton.so提供C++接口或从其他库调用C++函数不再安全。原因是libstdc++在不同版本之间并不特别兼容。

因此,总结之前提到的所有内容,这是我的建议:

  • 针对旧版本的libc动态链接,以确保您也将针对新版本进行链接。不过,不要和它一起发货
  • 针对CCD_ 47和CCD_
  • 提供一个C接口,因为您没有其他(可移植的)选项

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

是的,别忘了许可证问题。

您应该在文件系统的某个位置有一个配置文件,其中为您的应用程序定义了默认模块文件夹。Vim,并将此路径编辑为~/.llinuxbrew/lib.Re-run。如果仍然出现模块错误,请按照错误消息显示的方式重新安装正确版本的模块,并将模块复制到~/.llinuxbrew/lib路径。最后,总是检查权限&模块和/或模块路径上的所有权。也许由于简单的所有权/权限问题,您的应用程序无法获取这些模块。

这个答案将特定于我的用例,但许多命令和设置可以推广到其他应用程序和配置。我还在vrep论坛上更详细地讨论了我对这个特定问题的调试。

首先,我将vrep.sh更改为完全清除LD_LIBRARY_PATH,这样它就不会干扰加载内容。

unset LD_LIBRARY_PATH

感谢@fireant的指针,尽管不幸的是,仅靠这个链接并不能解决我的问题。

我解决这个问题的第一个提示来自rpath上的这篇stackoverflow帖子,下面是该帖子中的命令,让你看到当前的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,我认为这是因为在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到我希望在~/.linugbrew/lib中找到我的库的位置,然后我用以下命令更改了vrep可执行文件的rpath:

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)

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

事实上,对我来说,理想的解决方案应该是vrep加载系统库,插件加载linux brew库。我不确定这是否可能,如果可能的话,可能需要修改vrep源代码/构建。

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