Libstdc++ Static Linking in Dynamic Library

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.

How to set libstdc++ linking to static when other linked library link stdc++ as dynamic?


I have libraries that link stdc ++ dynamically. I want to create new shared library with new files, link them and link libstdc++ statically.

That is a really bad idea(TM). When your binary executes on a system with a different version of libstdc++.so.6, you will have symbol collisions (unless you are extremely careful about hiding all relevant symbols inside your shared library), which will likely lead to very hard to debug crashes or other undefined behavior.

I tried to add -static-libstdc++ to compilation but it doesn't work. I checked with ldd and my library is still dynamically linked.

First, adding -static-libstdc++ to compilation does nothing. You need to add to linking.

Second, it's unclear what you ran ldd on, and whether your library depends on other shared libraries. If it does, ldd will show transitive dependency on libstdc++, which is entirely expected.

To see whether your library directly depends on libstdc++.so.6, do this:

readelf -d yourlib.so | grep 'NEEDED.*libstdc'

-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.

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)

Compiling with -static-libgcc -static-libstdc++ still results in dynamic dependency on libc.so

GNU libc is not designed to be statically linked. Important functions, e.g. gethostbyname and iconv, will malfunction or not work at all in a static binary. Arguably even worse, under some conditions a static binary will attempt to dynamically open and use libc.so.6, even though the whole point of static linkage is to avoid such dependencies.

You should compile your program against uClibc or musl libc instead.

(This has been true for at least 15 years.)

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