静态链接 libstdc++:有什么陷阱吗?
- 2024-10-24 08:51:00
- admin 原创
- 66
问题描述:
我需要将在 Ubuntu 12.10 上使用 GCC 4.7 的 libstdc++ 构建的 C++ 应用程序部署到运行 Ubuntu 10.04 的系统上,该系统附带了相当旧版本的 libstdc++。
目前,我正在使用 进行编译-static-libstdc++ -static-libgcc
,正如这篇博客文章所建议的那样:静态链接 libstdc++。作者警告在静态编译 libstdc++ 时不要使用任何动态加载的 C++ 代码,这是我尚未检查的内容。不过,到目前为止一切似乎都很顺利:我可以在 Ubuntu 10.04 上使用 C++11 功能,这正是我想要的。
我注意到这篇文章是 2005 年的,从那时起可能已经发生了很多变化。它的建议仍然适用吗?有什么潜在问题我应该注意吗?
解决方案 1:
那篇博客文章相当不准确。
据我所知,GCC 的每个主要版本都引入了 C++ ABI 变化(即具有不同第一个或第二个版本号组件的版本)。
不对。自 GCC 3.4 以来引入的唯一 C++ ABI 变化是向后兼容的,这意味着 C++ ABI 已经稳定了近九年。
更糟糕的是,大多数主流 Linux 发行版都使用 GCC 快照和/或修补其 GCC 版本,因此几乎不可能在分发二进制文件时确切知道您可能正在处理的 GCC 版本。
发行版的 GCC 修补版本之间的差异很小,并且 ABI 没有变化,例如 Fedora 的 4.6.3 20120306(Red Hat 4.6.3-2)与上游 FSF 4.6.x 版本是 ABI 兼容的,并且几乎可以肯定与任何其他发行版的 4.6.x 兼容。
在 GNU/Linux 上,GCC 的运行时库使用 ELF 符号版本控制,因此很容易检查对象和库所需的符号版本,如果您有一个libstdc++.so
提供这些符号的版本,它就会起作用,即使它与您的发行版的另一个版本略有不同,修补版本也没有关系。
但如果要使其工作,则不能动态链接任何 C++ 代码(或任何使用 C++ 运行时支持的代码)。
这也不是事实。
也就是说,静态链接libstdc++.a
是您的一个选择。
如果您动态加载库(使用dlopen
),则可能无法正常工作的原因是,当您(静态)链接它时,您的应用程序可能不需要它所依赖的 libstdc++ 符号,因此这些符号将不会出现在您的可执行文件中。可以通过将共享库动态链接到libstdc++.so
(如果它依赖于它,这无论如何都是正确的做法)来解决此问题。ELF 符号插入意味着可执行文件中存在的符号将被共享库使用,但可执行文件中不存在的其他符号将在libstdc++.so
它链接到的任何地方找到。如果您的应用程序不使用,dlopen
您无需关心这一点。
另一个选项(也是我更喜欢的选项)是将较新的版本libstdc++.so
与您的应用程序一起部署,并确保在默认系统之前找到它libstdc++.so
,这可以通过强制动态链接器在正确的位置查找来完成,可以在$LD_LIBRARY_PATH
运行时使用环境变量,也可以RPATH
在链接时在可执行文件中设置。我更喜欢使用,RPATH
因为它不依赖于正确设置的环境来使应用程序工作。如果您将应用程序链接到'-Wl,-rpath,$ORIGIN'
(请注意单引号以防止 shell 尝试扩展$ORIGIN
),则可执行文件将具有一个RPATH
,$ORIGIN
它告诉动态链接器在与可执行文件本身相同的目录中查找共享库。如果将较新的版本放在与libstdc++.so
可执行文件相同的目录中,它将在运行时被发现,问题解决了。(另一个选择是将可执行文件放在中,/some/path/bin/
将较新的 libstdc++.so 放在中/some/path/lib/
并链接到'-Wl,-rpath,$ORIGIN/../lib'
或任何其他相对于可执行文件的固定位置,并设置相对于的 RPATH $ORIGIN
)
解决方案 2:
除了 Jonathan Wakely 的出色回答之外,还有一个问题,为什么 dlopen() 是有问题的:
由于 GCC 5 中新增了异常处理池(请参阅PR 64535和PR 65434),如果您 dlopen 和 dlclose 静态链接到 libstdc++ 的库,每次都会发生内存泄漏(池对象)。因此,如果您有机会使用 dlopen,静态链接 libstdc++ 似乎是一个非常糟糕的主意。请注意,这是一个真正的泄漏,而不是PR 65434中提到的良性泄漏。
解决方案 3:
Jonathan Wakely 关于 RPATH 的回答补充:
只有当所讨论的 RPATH 是正在运行的应用程序的 RPATH 时,RPATH 才会起作用。如果您有一个库,它通过自己的 RPATH 动态链接到任何库,则该库的 RPATH 将被加载它的应用程序的 RPATH 覆盖。当您无法保证应用程序的 RPATH 与库的 RPATH 相同时,就会出现问题,例如,如果您希望依赖项位于特定目录中,但该目录不是应用程序的 RPATH 的一部分。
例如,假设您有一个应用程序 App.exe,它对 GCC 4.9 的 libstdc++.so.x 具有动态链接依赖关系。App.exe 通过 RPATH 解析了此依赖关系,即
App.exe (RPATH=.:./gcc4_9/libstdc++.so.x)
现在假设有另一个库 Dependency.so,它对 GCC 5.5 的 libstdc++.so.y 有动态链接依赖。这里的依赖关系通过库的 RPATH 来解析,即
Dependency.so (RPATH=.:./gcc5_5/libstdc++.so.y)
当 App.exe 加载 Dependency.so 时,它既不附加也不添加库的 RPATH。它根本不会参考它。唯一要考虑的 RPATH 是正在运行的应用程序的 RPATH,在本例中是 App.exe 的 RPATH。这意味着,如果库依赖于 gcc5_5/libstdc++.so.y 中但不在 gcc4_9/libstdc++.so.x 中的符号,则库将无法加载。
这只是一个警告,因为我以前也遇到过这些问题。RPATH 是一个非常有用的工具,但它的实现仍然存在一些问题。
解决方案 4:
您可能还需要确保不依赖动态 glibc。运行ldd
生成的可执行文件并注意任何动态依赖项(libc/libm/libpthread 通常是可疑的)。
额外的练习是使用此方法构建一堆复杂的 C++11 示例,并在真正的 10.04 系统上实际尝试生成的二进制文件。在大多数情况下,除非您对动态加载做了一些奇怪的事情,否则您会立即知道程序是正常运行还是崩溃。
解决方案 5:
我想对 Jonathan Wakely 的回答进行以下补充。
在 Linux 上玩的时候-static-libstdc++
,我遇到了 的问题dlclose()
。假设我们有一个应用程序“A”静态链接到libstdc++
,并且它在运行时动态加载链接到libstdc++
插件“P”。这很好。但是当“A”卸载“P”时,会发生分段错误。我的假设是卸载后libstdc++.so
,“A”不再可以使用与 相关的符号libstdc++
。请注意,如果“A”和“P”都静态链接到libstdc++
,或者“A”动态链接而“P”静态链接,则不会发生问题。
摘要:如果您的应用程序加载/卸载可能动态链接到的插件libstdc++
,则应用程序也必须动态链接到它。这只是我的观察,我希望得到您的评论。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件