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

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

目前,我正在使用 -static-libstdc++ -static-libgcc进行编译,正如这篇博客文章所建议的: 静态链接 libstdc + + 。作者警告在静态编译 libstdc + + 时不要使用任何动态加载的 C + + 代码,我还没有检查过这一点。尽管如此,到目前为止,一切似乎进展顺利: 我可以利用 Ubuntu 10.04上的 C + + 11特性,这正是我所追求的。

我注意到这篇文章是从2005年开始的,也许从那时起已经发生了很大的变化。它的建议现在还流行吗?有什么潜在的问题需要我注意吗?

84392 次浏览

You might also need to make sure that you don't depend on the dynamic glibc. Run ldd on your resulting executable and note any dynamic dependencies (libc/libm/libpthread are usal suspects).

Additional exercise would be building a bunch of involved C++11 examples using this methodology and actually trying the resulting binaries on a real 10.04 system. In most cases, unless you do something weird with dynamic loading, you'll know right away whether the program works or it crashes.

That blog post is pretty inaccurate.

As far as I know C++ ABI changes have been introduced with every major release of GCC (i.e. those with different first or second version number components).

Not true. The only C++ ABI changes introduced since GCC 3.4 have been backward-compatible, meaning the C++ ABI has been stable for nearly nine years.

To make matters worse, most major Linux distributions use GCC snapshots and/or patch their GCC versions, making it virtually impossible to know exactly what GCC versions you might be dealing with when you distribute binaries.

The differences between distributions' patched versions of GCC are minor, and not ABI changing, e.g. Fedora's 4.6.3 20120306 (Red Hat 4.6.3-2) is ABI compatible with the upstream FSF 4.6.x releases and almost certainly with any 4.6.x from any other distro.

On GNU/Linux GCC's runtime libraries use ELF symbol versioning so it's easy to check the symbol versions needed by objects and libraries, and if you have a libstdc++.so that provides those symbols it will work, it doesn't matter if it's a slightly different patched version from another version of your distro.

but no C++ code (or any code using the C++ runtime support) may be linked dynamically if this is to work.

This is not true either.

That said, statically linking to libstdc++.a is one option for you.

The reason it might not work if you dynamically load a library (using dlopen) is that libstdc++ symbols it depends on might not have been needed by your application when you (statically) linked it, so those symbols will not be present in your executable. That can be solved by dynamically-linking the shared library to libstdc++.so (which is the right thing to do anyway if it depends on it.) ELF symbol interposition means symbols that are present in your executable will be used by the shared library, but others not present in your executable will be found in whichever libstdc++.so it links to. If your application doesn't use dlopen you don't need to care about that.

Another option (and the one I prefer) is to deploy the newer libstdc++.so alongside your application and ensure it is found before the default system libstdc++.so, which can be done by forcing the dynamic linker to look in the right place, either using $LD_LIBRARY_PATH environment variable at run-time, or by setting an RPATH in the executable at link-time. I prefer to use RPATH as it doesn't rely on the environment being set correctly for the application to work. If you link your application with '-Wl,-rpath,$ORIGIN' (note the single quotes to prevent the shell trying to expand $ORIGIN) then the executable will have an RPATH of $ORIGIN which tells the dynamic linker to look for shared libraries in the same directory as the executable itself. If you put the newer libstdc++.so in the same directory as the executable it will be found at run-time, problem solved. (Another option is to put the executable in libstdc++.so0 and the newer libstdc++.so in libstdc++.so1 and link with libstdc++.so2 or any other fixed location relative to the executable, and set the RPATH relative to $ORIGIN)

One addition to Jonathan Wakely's excellent answer, why dlopen() is problematic:

Due to the new exception handling pool in GCC 5 (see PR 64535 and PR 65434), if you dlopen and dlclose a library that is statically linked to libstdc++, you will get a memory leak (of the pool object) each time. So if there's any chance that you'll ever use dlopen, it seems like a really bad idea to statically link libstdc++. Note that this is a real leak as opposed to the benign one mentioned in PR 65434.

I'd like to add to Jonathan Wakely's answer the following.

Playing around -static-libstdc++ on linux, I've faced the problem with dlclose(). Suppose we have an application 'A' statically linked to libstdc++ and it loads dynamically linked to libstdc++ plugin 'P' at runtime. That's fine. But when 'A' unloads 'P', segmentation fault occurs. My assumption is that after unloading libstdc++.so, 'A' no longer can use symbols related to libstdc++. Note that if both 'A' and 'P' are statically linked to libstdc++, or if 'A' is linked dynamically and 'P' statically, the problem does not occur.

Summary: if your application loads/unloads plugins that may dynamically link to libstdc++, the app must also be linked to it dynamically. This is just my observation and I'd like to get your comments.

Add-on to Jonathan Wakely's answer regarding the RPATH:

RPATH will only work if the RPATH in question is the RPATH of the running application. If you have a library which dynamically links to any library through its own RPATH, the library's RPATH will be overwritten by the RPATH of the application which loads it. This is a problem when you cannot guarantee that the RPATH of the application is the same as that of your library, e.g. if you expect your dependencies to be in a particular directory, but that directory is not part of the application's RPATH.

For example, let us say you have an application App.exe which has a dynamically-linked dependency on libstdc++.so.x for GCC 4.9. The App.exe has this dependency resolved through the RPATH, i.e.

App.exe (RPATH=.:./gcc4_9/libstdc++.so.x)

Now let's say there is another library Dependency.so, which has a dynamically-linked dependency on libstdc++.so.y for GCC 5.5. The dependency here is resolved through the RPATH of the library, i.e.

Dependency.so (RPATH=.:./gcc5_5/libstdc++.so.y)

When App.exe loads Dependency.so, it neither appends nor prepends the RPATH of the library. It doesn't consult it at all. The only RPATH which is considered will be that of the running application, or App.exe in this example. That means that if the library relies on symbols which are in gcc5_5/libstdc++.so.y but not in gcc4_9/libstdc++.so.x, then the library will fail to load.

This is just as a word of warning, since I've run into these issues myself in the past. RPATH is a very useful tool but its implementation still has some gotchas.