静态链接libstdc ++:有陷阱吗?


90

我需要将基于GCC 4.7的libstdc ++的,在Ubuntu 12.10上构建的C ++应用程序部署到运行Ubuntu 10.04的系统,该系统随附相当老的libstdc ++版本。

目前,我正在-static-libstdc++ -static-libgcc按照此博客文章的建议进行编译:静态链接libstdc ++。作者警告不要在静态编译libstdc ++时使用任何动态加载的C ++代码,这是我尚未检查的事情。尽管如此,到目前为止一切似乎都进展顺利:我可以在Ubuntu 10.04上使用C ++ 11功能。

我注意到这篇文章是从2005年开始的,此后也许已经发生了很大的变化。它的建议仍然有效吗?有没有我应该注意的潜伏问题?


不,静态链接到libstdc ++并不意味着这样做。如果确实暗示该-static-libstdc++选择没有意义,那么您只需使用-static
Jonathan Wakely 2012年

@JonathanWakely -statickernel too old在某些ubuntu 1404系统中将出错。glibc.so就像kernel32.dll在窗口中一样,它是操作系统接口的一部分,我们不应将其嵌入二进制文件中。您可以objdump -T [binary path]用来查看它是否动态加载libstdc++.so。对于golang编程器,您可以#cgo linux LDFLAGS: -static-libstdc++ -static-libgcc在导入“ C”之前添加
铜牌人

@bronzeman,但我们谈论的-static-libstdc++并不是-static这样,因此libc.so不会被静态链接。
Jonathan Wakely '18

1
链接到博客的@NickHutchinson消失了。该SO问题是此处相关术语的热门搜索命中。您能否从问题中的该博客文章中复制关键信息,或者如果您知道将其移至何处,则可以提供一个新链接?
Brian Cain

Answers:


134

该博客文章非常不准确。

据我所知,GCC的每个主要版本(即那些具有不同的第一或第二版本编号组件的版本)都引入了C ++ ABI更改。

不对。自GCC 3.4以来唯一引入的C ++ ABI更改是向后兼容的,这意味着C ++ ABI稳定了将近9年。

更糟糕的是,大多数主要的Linux发行版都使用GCC快照和/或修补其GCC版本,从而几乎不可能确切知道分发二进制文件时可能要处理的GCC版本。

发行版的GCC补丁版本之间的差异很小,并且不会更改ABI,例如Fedora的4.6.3 20120306(Red Hat 4.6.3-2)与上游FSF 4.6.x版本以及几乎所有4.6都兼容ABI。 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'(请注意使用单引号防止外壳扩展$ORIGIN),那么可执行文件将带有RPATH$ORIGIN其告诉动态链接程序在与可执行文件本身相同的目录中查找共享库。如果你把新的libstdc++.so在与可执行文件相同的目录中,它将在运行时找到,问题已解决。(另一种选择是将可执行文件放入其中/some/path/bin/,将较新的libstdc ++。so放入其中,/some/path/lib/并链接'-Wl,-rpath,$ORIGIN/../lib'到可执行文件或相对于可执行文件的任何其他固定位置,并将RPATH设置为$ORIGIN


8
这种解释,特别是有关RPATH的解释,是光荣的。
nilweed 2014年

3
在Linux上将libstdc ++与您的应用程序一起交付是不好的建议。Google用“ steam libstdc ++”观看了这带来的所有戏剧。简而言之,如果您的exe加载了要再次dlopen libstdc ++(例如radeon驱动程序)的外部库(例如opengl),则这些库将使用您的libstdc ++,因为它们已经被加载,而不是它们自己的,这正是他们所需要的期望。所以,您回到正题。

7
@ cap,OP专门询问有关部署到系统libstdc ++较旧的发行版的问题。Steam的问题是他们捆绑了libstdc ++。so,它比系统捆绑包还旧(大概在捆绑捆绑包时它是较新的,但是发行版转移到了较新的发行版上)。可以通过使RPATH指向包含libstdc++.so.6符号链接的目录来解决,该目录在安装时设置为指向捆绑的lib或指向较新的系统的lib。Red Hat DTS使​​用了更复杂的混合链接模型,但是很难自己完成。
Jonathan Wakely

5
嘿,我很抱歉,如果我不希望我的用于向后兼容二进制文件的模型包括“信任其他人以保持libstdc ++ ABI兼容”或“在运行时有条件地链接libstdc ++” ...在那里,我该怎么办,我的意思是不尊重。而且,如果您还记得memcpy@GLIBC_2.14的剧情,您就不能真的因为这引起信任问题而对我表示

6
我必须使用'-Wl,-rpath,$ ORIGIN'(注意rpath前面的'-')。我无法编辑答案,因为编辑必须至少包含6个字符...
user368507

11

Jonathan Wakely出色答案的一个补充,为什么dlopen()有问题:

由于GCC 5中有新的异常处理池(请参阅PR 64535PR 65434),如果dlopen和dlclose静态链接到libstdc ++的库,则每次都会发生(池对象的)内存泄漏。因此,如果您有可能会使用dlopen,则静态链接libstdc ++似乎是一个非常糟糕的主意。请注意,与PR 65434中提到的良性泄漏相比,这是真正的泄漏。


1
该函数__gnu_cxx::__freeres()似乎至少提供了一些有关此问题的帮助,因为它释放了池对象的内部缓冲区。但是对我来说,对于此后意外抛出的异常,对该函数的调用有何含义尚不清楚。
phlipsy

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将是正在运行的应用程序或本示例中的App.exe的RPATH。这意味着,如果库依赖于gcc5_5 / libstdc ++。so.y中的符号,但不依赖于gcc4_9 / libstdc ++。so.x中的符号,则该库将无法加载。

这只是警告,因为我过去自己也遇到过这些问题。RPATH是一个非常有用的工具,但是其实现仍然存在一些陷阱。


因此,共享库的RPATH毫无意义!我希望他们在过去的20年中在这方面对Linux有所改善……
Frank Puck

2

您可能还需要确保不依赖动态glibc。ldd在生成的可执行文件上运行,并注意所有动态依赖项(libc / libm / libpthread通常是可疑的)。

额外的练习将是使用此方法构建一堆涉及的C ++ 11示例,并在实际的10.04系统上实际尝试生成的二进制文件。在大多数情况下,除非您对动态加载做一些奇怪的事情,否则您会立即知道该程序是否有效或崩溃。


1
依赖动态glibc有什么问题?
Nick Hutchinson

我相信至少在一段时间之前,libstdc ++暗示了对glibc的依赖。不确定今天的情况。
亚历山大·贝里科夫

9
libstdc ++确实依赖于glibc(例如iostreams是根据来实现的printf),但是只要Ubuntu 10.04上的glibc提供了较新的libstdc ++所需的所有功能,那么依赖于动态glibc就不会有问题,实际上,强烈建议您不要链接静态地对glibc
Jonathan Wakely 2012年

1

我想在Jonathan Wakely的回答中添加以下内容。

-static-libstdc++在linux上玩耍时,我遇到了问题dlclose()。假设我们有一个静态链接到应用程序“ A”,libstdc++并且libstdc++在运行时动态加载了与插件“ P”的动态链接。没关系。但是,当“ A”卸载“ P”时,会发生分段错误。我的假设是,在卸载后libstdc++.so,“ A”不再可以使用与相关的符号libstdc++。请注意,如果'A'和'P'都静态链接到libstdc++,或者'A'是动态链接而'P'静态链接,则不会发生此问题。

摘要:如果您的应用程序加载/卸载了可能动态链接到的插件,则libstdc++该应用程序也必须动态链接到它。这只是我的观察,我想听听您的意见。


1
这可能类似于混合libc实现(例如,动态链接到插件,而插件又动态链接glibc,而应用程序本身静态链接到musl-libc)。musl-libc的作者Rich Felker声称,在这种情况下,问题在于glibc内存管理(使用sbrk)做出了一定的假设,并且几乎期望在一个进程中单独存在……不确定是否仅限于一个进程。特定的glibc版本或其他。
0xC0000022L

人们仍然看不到Windows堆接口的优点,该接口能够在单个进程中处理libc ++ / libc的多个独立副本。这样的人不应该设计软件。
弗兰克·帕克

@FrankPuck具有相当不错的Windows和Linux经验,我可以告诉您,当MSVC是决定使用哪种分配器以及如何使用分配器的一方时,“ Windows”的使用方式将无济于事。我在Windows上看到的堆的主要优点是,您可以分发零碎的碎片,然后一口气释放它们。但是,使用MSVC时,您仍然会遇到上述问题,例如,在传递另一个VC运行时分配的指针时(释放与调试或静态与动态链接)。因此,“ Windows”无法幸免。两个系统都必须小心。
0xC0000022L
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.