Statically linking libstdc++

I recently found myself wanting to statically link libstdc++ into a library I was compiling and found it to be a surprising challenging process.

Small example library

I first started playing with a small example library:

#include <iostream>

__attribute__((noinline))
void bar()
{
  std::cout << __FILE__ <<  " " << __func__ << std::endl;
}

void foo()
{
  bar();
  std::cout << "Hello" << std::endl;
}

Before I even got to statically compiling libstdc++, I noticed something strange: The symbol bar was getting resolved through the PLT!

$ gcc -O3 -shared -fPIC foo.cpp -o foo.so
$ gdb foo.so --batch --ex "disas foo"
Dump of assembler code for function _Z3foov:
   0x0000000000000b70 <+0>: push   %rbp
   0x0000000000000b71 <+1>: push   %rbx
   0x0000000000000b72 <+2>: sub    $0x8,%rsp
   0x0000000000000b76 <+6>: callq  0x960 <_Z3barv@plt>
... <snip> ...

I played around with a couple of things, and eventualy figured out I could mark bar as having hidden visibility and then it wouldn't use this PLT.

Static libstdc++?

g++ has a command line option -static-libstdc++ which appears to do exactly what I want. Unfortunately, the calls to the libstdc++ symbols are resolved via the PLT, as above with bar:

$ gcc -static-libstdc++ -O3 -shared -fPIC foo.cpp -o foo.so
$ /tmp% gdb foo.so --batch --ex "disas foo"
   0x0000000000000b21 <+1>: push   %rbx
   0x0000000000000b22 <+2>: sub    $0x8,%rsp
   0x0000000000000b26 <+6>: callq  0xa80 <_Z3barv>
   0x0000000000000b2b <+11>:  mov    0x20049e(%rip),%rbx        # 0x200fd0
   0x0000000000000b32 <+18>:  lea    0x7a(%rip),%rsi        # 0xbb3
   0x0000000000000b39 <+25>:  mov    $0x5,%edx
   0x0000000000000b3e <+30>:  mov    %rbx,%rdi
   0x0000000000000b41 <+33>:  callq  0x930 <_ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l@plt>
... <snip> ...

I tried to do a cute trick to make all the C++ functions have hidden visibility:

#pragma GCC visibility push(hidden)
#include <iostream>
#pragme GCC visibility pop

Unfortunately, the symbols were still resolved via the PLT. What was going on? Could I force g++ to just use the symbols in the binary it had a copy of rather than using the PLT?

Bsymbolic-functions

Some deep soul searching lead me to a stack overflow post where someone else had the same questions and talked about a flag that solved this exact problem. This allowed me to get rid of the visiblity annotation on bar, but it still didn't solve my libstdc++ problem.

$ gcc -static-libstdc++ -Wl,-Bsymbolic-functions -O3 -shared -fPIC foo.cpp -o foo.so

I eventually found the real issue: library order during linking. If I manually specified the libstc++.a library as the last library, the symbols would not be resolved via the plt:

$ gcc -static-libstdc++ -Wl,-Bsymbolic-functions -O3 -shared -fPIC foo.cpp -o foo.so $(g++ $CXXFLAGS -print-file-name=libstdc++.a)
$ gdb foo.so --batch --ex "disas foo"
Dump of assembler code for function _Z3foov:
   0x00000000000422e0 <+0>: push   %rbp
   0x00000000000422e1 <+1>: push   %rbx
   0x00000000000422e2 <+2>: sub    $0x8,%rsp
   0x00000000000422e6 <+6>: callq  0x42240 <_Z3barv>
   0x00000000000422eb <+11>:  mov    0x2756ae(%rip),%rbx        # 0x2b79a0
   0x00000000000422f2 <+18>:  lea    0x518ef(%rip),%rsi        # 0x93be8
   0x00000000000422f9 <+25>:  mov    $0x5,%edx
   0x00000000000422fe <+30>:  mov    %rbx,%rdi
   0x0000000000042301 <+33>:  callq  0x45670 <_ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l>
... <snip> ...

Specifing this static libstdc++.a as a final arg worked with both the #pragma GCC visibility push(hidden) trick and with -Bsymbolic-functions.

In ./configure

I had a ./configure library that I wanted to build with a static libstdc++. Using what I learned above, this seemed relatively straightforward:

./configure CXXFLAGS="-static-libstc++" LDFLAGS="-Bsymbolic-functions" LIBS="$(g++ -print-file-name=libstdc++.a)"

Unfortunately, this didn't work. Using make VERBOSE=1, I looked at the command used to link my library and saw the issue: make was calling gcc to link the library rather than ld but wasn't wrapping LDFLAGS. I did so manually and got my expected results, a static libstdc++ where none of the symbols were resolved using the PLT. My final configure invocation was:

./configure CXXFLAGS="-static-libstc++" LDFLAGS="-Wl,-Bsymbolic-functions" LIBS="$(g++ -print-file-name=libstdc++.a)"

Comments !