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

时间:2015-10-29 20:24:50

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

我使用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

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

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

5 个答案:

答案 0 :(得分:3)

  

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

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

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

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

  • 静态链接。将插件与所需库的静态变体相链接。但是,如果您需要覆盖libclibstdc++,则无效。

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

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

答案 1 :(得分:1)

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

答案 2 :(得分:0)

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

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

第一个(libc)似乎是最大的问题,因为你既不能静态地链接它,也不能附带你自己的版本。但是,如果你提出一个简单的技巧,你就不需要了。 GNU C库使用符号版本并向后兼容。你可以依靠这个。只需使用相当旧版本的libc.so.6(版本2.11已经足够大,以确保您可以在几乎所有当前使用的发行版上工作,但您可能需要更新的版本)并动态链接它。不过,请不要随libv_repExtPluginSkeleton.so运送它。您正在对其进行链接,以确保您的libv_repExtPluginSkeleton.so将在目标计算机上动态链接(可能更新的)系统提供的版本。您依赖于向后兼容性。

现在,如果您正在构建可执行文件,则可以动态链接到您自己的rpathlibgcc_slibstdc++版本,您就可以开始了。但是,既然你正在建立一个共享图书馆,那么你就没那么奢侈了。所以你有两个选择。

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

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

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

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

当然,这违反了您不希望更改用于构建库的工具链的愿望。你失去了C ++界面。此外,您还必须交换libc功能以实现兼容性。但至少你获得兼容性。

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

这个答案将特定于我的用例,但许多命令和设置可以推广到其他应用程序和配置。我也有一个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,我相信这是因为在构建vrep for release时它可能没有明确配置:

$ 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 vs 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源/构建。

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