Linking Libstdc++ Statically: Any Gotchas

Linking libstdc++ statically: any gotchas?

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 /some/path/bin/ and the newer libstdc++.so in /some/path/lib/ and link with '-Wl,-rpath,$ORIGIN/../lib' or any other fixed location relative to the executable, and set the RPATH relative to $ORIGIN)

-static-libstdc++ works on g++ but not on pure gcc?

The GCC manual, Link Options says:

-static-libstdc++

When the g++ program is used to link a C++ program, it normally automatically links
against libstdc++. If libstdc++ is available as a shared library, and the -static
option is not used, then this links against the shared version of libstdc++.
That is normally fine. However, it is sometimes useful to freeze the version of
libstdc++ used by the program without going all the way to a fully static link.
The -static-libstdc++ option directs the g++ driver to link libstdc++ statically,
without necessarily linking other libraries statically.

This makes clear that the option -static-libstdc++ is meaningful only to
the g++ compiler driver, not gcc or any other.

On the other hand the option -l<name> is meaningful and means the same thing
to all GCC compiler drivers. On that basis it is not surprising that:

gcc file.cc -lstdc++ -static-libstdc++

has the same meaning as:

gcc file.cc -lstdc++ 

However, that observation does not truly illuminate why the first of those
commandlines dynamically links libstdc++:-

-static-libstdc++ is meaningful only to g++ because only g++ links
libstdc++ automatically. So it is only for g++ that the question arises
whether the automatically linked libstdc++ will be the dynamic version
or the static version. The dynamic version is the default: -static-libstdc++
insists on the static version.

The automatic linking of libstdc++ by g++ means this: g++ silently
appends -lstdc++ to whatever linkage options you specify (along with
quite a lot of other boiler-plate for a C++ linkage). You can reveal all
the boilerplate by requesting verbose linkage (g++ ... -Wl,-v ...).

By itself, the appended -lstdc++ will cause the linker to link the dynamic version
of libstdc++, per its default behaviour. The only difference made by
-static-libstdc++ is that in the place where -lstdc++ would otherwise
be silently passed to the linker, the options:

-Bstatic -lstdc++ -Bdynamic

are silently passed to it instead. These tell the linker:

  • -Bstatic: Do not link dynamic libraries until further notice
  • -lstdc++: Link libstdc++
  • -Bdynamic: Link dynamic libraries until further notice.

You see how that works to secure the static linkage of libstdc++ without
out side-effects on the linkage of any other library.

But you can also see that the automatic linkage of libstdc++, whether
dynamically or statically, has no retroactive effect on the linkage
of any libraries you have specified yourself
.

Hence, if your linkage already includes -lstdc++ before any boiler-plate
options are silently appended by the compiler driver, then libstdc++ will be linked
in just the same way as any -l<name> at that position in the linkage
sequence. And if silently appended boiler-plate options result in -lstdc++
reappearing later in the linkage sequence, whether by itself or with
the surroundings:

-Bstatic -lstdc++ -Bdynamic

then the later appearance will simply be redundant, because the library has
already been linked.

So there is nothing peculiar about gcc that results in:

gcc file.cc -lstdc++ -static-libstdc++

producing a program in which libstdc++ is dynamically linked. So does

g++ file.cc -lstdc++ -static-libstdc++

or indeed:

g++ file.cc -static-libstdc++ -lstdc++

because the generated linker commandline is of the form:

... file.o -lstdc++ ... -Bstatic -lstdc++ -Bdynamic ...

where -Bstatic -lstdc++ -Bdynamic is too late to make any difference.

Check it out:

file.cc

#include <iostream>

int main()
{
std::cout << "Hello World" << std::endl;
return 0;
}

Compile and link normally and inspect the dynamic dependencies with ldd:

$ g++ -o prog file.cc
$ ldd prog
linux-vdso.so.1 => (0x00007ffede76a000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f42fa74c000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f42fa385000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f42fa07c000)
/lib64/ld-linux-x86-64.so.2 (0x0000558ab42bc000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f42f9e65000)

libstdc++.so is present.

Now just with -static-libstdc++:

$ g++ -o prog file.cc -static-libstdc++
$ ldd prog
linux-vdso.so.1 => (0x00007fff448d7000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fe5f7c71000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe5f78aa000)
/lib64/ld-linux-x86-64.so.2 (0x0000556ebf272000)

libstdc++.so is absent.

And finally with -static-libstdc++ -lstdc++:

$ g++ -o prog file.cc -static-libstdc++ -lstdc++
$ ldd prog
linux-vdso.so.1 => (0x00007ffd12de9000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fd5a1823000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd5a145c000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fd5a1153000)
/lib64/ld-linux-x86-64.so.2 (0x000055bbe31c3000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fd5a0f3c000)

libstdc++.so is back.

(This is Linux, of course, but you'll find the same thing on Windows).

So whether you drive your linkage with g++ or gcc, the reason that

{gcc|g++} file.cc -lstdc++ ...

will result in libstdc++ being dynamically linked is simply that

{gcc|g++} file.cc -lfoo ...

will cause libfoo to be dynamically linked, if it can be, regardless of
what ... is, provided only that ... does not contain the option -static.

libstdc++ static linking in dynamic library

The API your shared library exposes must use the same ABI that the host application expects, i.e. the types involved must be of the same layout, size and alignment.

If there are std:: types exposed in the API or it throws C++ exceptions, then that means the shared library must be compiled using the same standard library headers and macros defined. In this case you can link dynamically to that libstdc++ that comes with the host application.

If there are no std:: types exposed in the API and no exceptions are thrown from the shared library, you can link statically to libstdc++. However, all external symbols are still going to be exposed from the shared library, so that when it is loaded with a call to dlopen without RTLD_DEEPBIND flag, it will use the symbols with the same name from the host application if they are available instead of the ones you hoped to link in statically, which may cause that undefined behavior your friend probably refers to. To avoid this, a linker version script is required to make all symbols in your shared library local and only expose the API symbols as global. Something like:

MYHALFLIFEPLUGIN_0.0 {
global: half_life_foo,half_life_bar; # Explicitly list symbols to be exported.
local: *; # Hide everything else.
};

And direct the linker to use this script with -Wl,--version-script=<filename> compiler linker option (LDFLAGS). In addition to -static-libstdc++ option, you will need -static-libgcc linker option.

Also, have a read through

  • https://gcc.gnu.org/wiki/Cxx11AbiCompatibility
  • ABI Policy and Guidelines

for more details.

Is it a good practices to link libstdc++ static?

My 50 cents:

I'm a big fan of linking stuff statically (especially on Windows), but the price is that in case there are bugs/security problem, you have to re-ship your product or offer an update. I'm reluctant to doing that in Linux/Unix, because you generally don't have binary compatibility over all Unix operating systems. If you compile for a target system, then it doesn't matter (except, again, you have to take care of bugs yourself with updates).

Performance wise, shared libraries have this little overhead of loading the libraries, which is ridiculously negligible nowadays.

Legally, you're fine (Disclaimer, I'm not a lawyer, you may want to consult your company's lawyer). GNU has an exception on their Runtime libraries:

The source code is distributed under the GNU General Public License
version 3, with the addition under section 7 of an exception described
in the “GCC Runtime Library Exception, version 3.1” as follows (or see
the file COPYING.RUNTIME)

If that wasn't the case, then no propietary products would have been ever available on Linux.

Also you might want to consider using the Clang compiler if you're concerned about licensing. It has a very tolerant BSD license. Well, since you're rewriting stuff from scratch.

CMake: Linking statically against libgcc and libstdc++ into a shared library

Yes, target_link_libraries is a correct way to set linker flags or linker options.

Documentation of target_link_libraries:

Specify libraries or flags to use when linking a given target.

Item names starting with -, but not -l or -framework, are treated as
linker flags.

https://cmake.org/cmake/help/latest/command/target_link_libraries.html (emphasis not in original)

Static link libstdc++ using clang

clang is compatible with gcc on this matter. Basically for hello-world program that uses iostream to ensure libstdc++ requirement (actual lib versions may vary between distributions):

$ clang++ test.cpp
$ ldd ./a.out
linux-vdso.so.1 (0x00007ffec65c0000)
libstdc++.so.6 => /usr/lib/gcc/x86_64-pc-linux-gnu/5.3.0/libstdc++.so.6 (0x00007ff937bb6000)
libm.so.6 => /lib64/libm.so.6 (0x00007ff9378b6000)
libgcc_s.so.1 => /usr/lib/gcc/x86_64-pc-linux-gnu/5.3.0/libgcc_s.so.1 (0x00007ff93769e000)
libc.so.6 => /lib64/libc.so.6 (0x00007ff9372fe000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff937f3e000)

Here is a dependency for libstdc++ and libgcc_s. But if you add -static-libgcc -static-libstdc++:

$ clang++ test.cpp -static-libgcc -static-libstdc++
$ ldd ./a.out
linux-vdso.so.1 (0x00007ffe5d678000)
libm.so.6 => /lib64/libm.so.6 (0x00007fb8e4516000)
libc.so.6 => /lib64/libc.so.6 (0x00007fb8e4176000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb8e4816000)

That still leaves dependency on libc, but that is a different question.

clang: warning: argument unused during compilation: '-static-libstdc++' means clang ignored this flag, because flag is useless in current situation. First two examples that coming to mind is compiling C code (which obviously don't depend on libstdc++), or issuing compile-only command without linking (-c flag). Since .o file cannot hold information about static or dynamic linking, this flag have to be specified on linking phase (and, to avoid warning, only on linking phase).



Related Topics



Leave a reply



Submit