32
#include <iostream>
#include <map>
#include <thread>

#define SIZE 1024
#define AMOUNT 100000
#define THREADS 4

class A
{
private:
    char a[SIZE];
};

void test()
{
    std::cout << "test start\n";
    std::map<int, A*> container;
    for(int i=0; i<AMOUNT; i++)
    {
        A* a = new A();
        std::pair<int, A*>p = std::make_pair(i, a);
        container.insert(p);
    }

    std::cout << "test release\n";
    for(int i=0; i<AMOUNT; i++)
    {
        auto iter = container.find(i);
        delete iter->second;
        container.erase(iter);
    }
    std::cout << "test end\n";
}

int main()
{
    std::thread ts[THREADS];
    for(int i=0; i<THREADS; i++)
    {
        ts[i] = std::thread(test);
    }

    for(std::thread& x: ts)
    {
        x.join();
    }

    return 0;
}

Above is a simple c++ code.

compile with: g++ -pthread -o one one.cpp -Wall -std=c++11 -O3

ldd one, gots:

    linux-vdso.so.1 =>  (0x00007ffebafce000)
    libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fb47352a000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fb473313000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fb4730f4000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb472d2a000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fb472a22000)
    /lib64/ld-linux-x86-64.so.2 (0x00005654c5112000)

run ./one, every thing is ok.

Then I try a static link: g++ -pthread -o one one.cpp -Wall -std=c++11 -O3 -static

ldd one, gots:

    not a dynamic executable

But when I run it, some thing goes wrong...

test start
Segmentation fault (core dumped)

re-compile with -g, and the gdb shows:

wang[00:35][~/test]$ gdb one
GNU gdb (Ubuntu 7.10-1ubuntu2) 7.10
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from one...done.
(gdb) run
Starting program: /home/wang/test/one 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff7ffa700 (LWP 3623)]
test start
[New Thread 0x7ffff77f8700 (LWP 3624)]
test start
[New Thread 0x7ffff6ff7700 (LWP 3625)]
test start
[New Thread 0x7ffff67f6700 (LWP 3626)]
test start

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) 

Why this ?

UPDATE ==============================

using boost::thread library (boost version: 1.60),

replace std::thread with boost::thread , and make a static link,

g++ -pthread -o one1 one.cpp -Wall -std=c++11 -O3 -I /opt/boost/include/ -L /opt/boost/lib/ -lboost_system -lboost_thread -static

no problem occurred!

confused...

Yueyoum
  • 2,205
  • 3
  • 18
  • 25
  • 1
    Looks quite similar to the issue in this [bug report](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52590). Why are you linking statically, btw? Does it work Why not just: `g++ -o one one.cpp -Wall -std=c++11 -O3 pthread`? – P.P Jan 31 '16 at 16:52
  • Thanks a lot! It saved my day, I'm using gem5 with g++, so it have to be linked statically, and the 1st answer works for me. My error mesg from GEM5 is ``` panic: Page table fault when accessing virtual address 0 ``` – Y00 Jun 15 '20 at 10:00
  • After including with boost::asio, this problem disapear as well. wierd. – Kidsunbo Mar 15 '21 at 06:05

2 Answers2

60

First, the solution. This here will work:

Update: Since Ubuntu 18.04, you need to link also against librt (add -lrt):

g++ -o one one.cpp -Wall -std=c++11 -O3 -static -lrt -pthread \
    -Wl,--whole-archive -lpthread -Wl,--no-whole-archive

(continue with original answer)

g++ -o one one.cpp -Wall -std=c++11 -O3 -static -pthread \
    -Wl,--whole-archive -lpthread -Wl,--no-whole-archive

When you use -pthread, the compiler will already link against pthread (and depending on the platform, it does define extra macros like -D_REENTRANT, see this question for more details).

So if -pthread implies -lpthread, why do you need you specify -lpthread when you are linking statically? And what does Wl,--whole-archive do?

Understanding weak symbols

On Unix, the ELF file format is used, which has the concept of weak and strong symbols. To quote from the Wikipedia page:

By default, without any annotation, a symbol in an object file is strong. During linking, a strong symbol can override a weak symbol of the same name. In contrast, two strong symbols that share a name yield a link error during link-time.

There is a subtle difference when it comes to dynamic and static libraries. In static libraries, the linker will stop at the first symbol, even if it is a weak one, and stops looking for strong ones. To force it to look at all symbols (like it would have done for a dynamically linked library), ld supports the --whole-archive option.

To quote from man ld:

--whole-archive: For each archive mentioned on the command line after the --whole-archive option, include every object file in the archive in the link, rather than searching the archive for the required object files. This is normally used to turn an archive file into a shared library, forcing every object to be included in the resulting shared library. This option may be used more than once.

It goes on by explaining that from gcc, you have to pass the option as -Wl,--whole-archive:

Two notes when using this option from gcc: First, gcc doesn't know about this option, so you have to use -Wl,-whole-archive. Second, don't forget to use -Wl,-no-whole-archive after your list of archives, because gcc will add its own list of archives to your link and you may not want this flag to affect those as well.

And it explains how to turn it off, again:

--no-whole-archive: Turn off the effect of the --whole-archive option for subsequent archive files.

Weak symbols in pthread and libstdc++

One of the use cases of weak symbols is to be able to swap out implementations with optimized ones. Another is to use stubs, which can later to replaced if necessary.

For example, fputc (conceptionally used by printf) is required by POSIX to be thread-safe and needs to be synchronized, which is costly. In a single-threaded environment, you do not want to pay the costs. An implementation could therefore implement the synchronization functions as empty stubs, and declare the functions as weak symbols.

Later, if a multi-threading library is linked (e.g., pthread), it becomes obvious that single-thread support is not intended. When linking the multi-threading library, the linker can then replace the stubs by the real synchronization functions (defined as strong symbols and implemented by the threading-library). On the other hand, if no multi-threading library is linked, the executable will use the stubs for the synchronization function.

glibc (providing fputc) and pthreads seem to use exactly this trick. For details, refer to this question about the usage of weak symbols in glibc. The example above is taken from this answer.

nm allows you to look at it in detail, which seems consistent with the cited answer above:

$ nm /usr/lib/libc.a 2>/dev/null | grep pthread_mutex_lock
w __pthread_mutex_lock
... (repeats)

"w" stands for "weak", so the statically linked libc library contains __pthread_mutex_lock as a weak symbol. The statically linked pthread library contains it as a strong symbol:

$ nm /usr/lib/libpthread.a 2>/dev/null | grep pthread_mutex_lock
             U pthread_mutex_lock
pthread_mutex_lock.o:
00000000000006a0 T __pthread_mutex_lock
00000000000006a0 T pthread_mutex_lock
0000000000000000 t __pthread_mutex_lock_full

Back to the example program

By looking at the shared library dependencies of the dynamically linked executable, I get almost the same output of ldd on my machine:

$ ldd one
linux-vdso.so.1 (0x00007fff79d6d000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007fcaaeeb3000)
libm.so.6 => /usr/lib/libm.so.6 (0x00007fcaaeb9b000)
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007fcaae983000)
libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007fcaae763000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007fcaae3bb000)
/lib64/ld-linux-x86-64.so.2 (0x00007fcaaf23b000)

Printing out the library calls with ltrace, leads to the following output:

$ ltrace -C ./one 
std::ios_base::Init::Init()(0x563ab8df71b1, 0x7ffdc483cae8, 0x7ffdc483caf8, 160) = 0
__cxa_atexit(0x7fab3023bc20, 0x563ab8df71b1, 0x563ab8df7090, 6)         = 0
operator new(unsigned long)(16, 0x7ffdc483cae8, 0x7ffdc483caf8, 192)    = 0x563ab918bc20
std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80) = 0
operator new(unsigned long)(16, 0x7fab2f6a1fb0, 0, 0x800000)            = 0x563ab918bd70
std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80) = 0
operator new(unsigned long)(16, 0x7fab2eea0fb0, 0, 0x800000)            = 0x563ab918bec0
std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80test start
) = 0
operator new(unsigned long)(16, 0x7fab2e69ffb0, 0, 0x800000)            = 0x563ab918c010
std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80test start
test start
) = 0
std::thread::join()(0x7ffdc483c9a0, 0x7fab2de9efb0, 0, 0x800000test start
test release
test release
test release
test release
test end
test end
test end
test end
)        = 0
std::thread::join()(0x7ffdc483c9a8, 0x7fab2eea19c0, 0x7fab2f6a2700, 0)  = 0
std::thread::join()(0x7ffdc483c9b0, 0x7fab2e6a09c0, 0x7fab2eea1700, 0)  = 0
std::thread::join()(0x7ffdc483c9b8, 0x7fab2de9f9c0, 0x7fab2e6a0700, 0)  = 0
+++ exited (status 0) +++

As an example, std::thread::join is called, which will most likely use pthread_join internally. That symbol can be found in the (dynamically linked) libraries listed in the ldd ouput, namely in libstdc++.so.6 and libpthread.so.0:

$ nm /usr/lib/libstdc++.so.6 | grep pthread_join
                 w pthread_join

$ nm /usr/lib/libpthread.so.0 | grep pthread_join
0000000000008280 T pthread_join

In the dynamically linked executable, the linker will replace weak symbols by strong symbols. In this example, we have to enforce the same semantic for the statically linked libraries. That is why -Wl,--whole-archive -lpthread -Wl,--no-whole-archive is needed.

Finding it out is a bit trial-and-error. At least, I found no clear documentation on that subject. I assume it is because static linking on Linux has become rather an edge case, whereas dynamic linking is often the canonical approach on how to use the libraries (for a comparison, see Static linking vs dynamic linking). The most extreme example that I have seen and personally struggled with a while to get it working is to link TBB statically.

Appendix: Workaround for Autotools

If you are using autotools as a build system, you need a workaround, as automake does not let you set options in the in LDADD. Unfortunately, you cannot write:

(Makefile.am)
mytarget_LDADD = -Wl,--whole-archive -lpthread -Wl,--no-whole-archive

As a workaround, you can avoid the check by defining the flags in configure.ac, and using them like this:

(configure.ac)
WL_WHOLE_ARCHIVE_HACK="-Wl,--whole-archive"
WL_NO_WHOLE_ARCHIVE_HACK="-Wl,--no-whole-archive"
AC_SUBST(WL_WHOLE_ARCHIVE_HACK)
AC_SUBST(WL_NO_WHOLE_ARCHIVE_HACK)

(Makefile.am)
mytarget_LDADD = @WL_WHOLE_ARCHIVE_HACK@ -lpthread @WL_NO_WHOLE_ARCHIVE_HACK@
Philipp Claßen
  • 32,622
  • 19
  • 125
  • 194
  • 1
    I have exactly the same problem. The dynamically linked executable works fine (correct results and valgrind is happy). My first naive attempt to create a statically linked executable failed miserably (valgrind errors, segfault on thread.join()). Your solution (thanks!) solved my problem only partially. I can create a statically linked executable (with multithreaded code) which works (it seems, no segfaults), but I have lots of valgrind errors. – Alexander Nov 11 '17 at 12:43
  • Years ago when I joined the project I'm working on they were using [XView](https://en.wikipedia.org/wiki/XView). As I was re-writing the build I found some of the tools were deadlocking mysteriously. XView provides its own implementation of various libc APIs (read, write, select, etc) to allow a sort of "task swapping" to ensure the gui doesn't stop rendering when they block. The order some libraries were being linked changed such that libc, libpthread, libsocket (etc) were encountered before libxview, hence the deadlock. – Brian Vandenberg Oct 11 '18 at 21:17
  • 1
    The following program: https://github.com/cirosantilli/linux-kernel-module-cheat/blob/4aff114c4c654014a97ef23b1513dda5409e79f3/userland/cpp/thread_get_id.cpp still segfaults even if I try to compile it with the extra options mentioned: `g++ thread_get_id.cpp -Wall -std=c++11 -O3 -static -pthread -Wl,--whole-archive -lpthread -Wl,--no-whole-archive` in Ubuntu 18.04, GCC 7.4.0. – Ciro Santilli新疆棉花TRUMP BAN BAD Nov 13 '19 at 18:08
  • 1
    @CiroSantilli新疆改造中心法轮功六四事件 Confirmed. I also get a segmentation fault (same setup: Ubuntu 18.04, gcc 7.4.0). Interesting example. – Philipp Claßen Nov 13 '19 at 19:58
  • 2
    @CiroSantilli新疆改造中心法轮功六四事件 I tried to understand why it no longer works, but unsuccessful so far. Something changed since gcc 5.4.0. I opened a new question for that: https://stackoverflow.com/q/58848694/783510 – Philipp Claßen Nov 14 '19 at 02:55
  • 2
    @CiroSantilli新疆改造中心法轮功六四事件 Still not clear what exactly changed, but in the meantime, the workaround is to add `-lrt` after `-lpthread`, or in other examples, before `-lpthread`. I could at least verify that it fixes the crash. – Philipp Claßen Nov 27 '19 at 00:46
  • @PhilippClaßen I'm using g++ (Debian 8.3.0-2) 8.3.0, this method don't work. :( I'll post update here if I got a way to fix it – Y00 Jul 05 '20 at 08:37
2

I had similar issue when linking a pre-built C++ .a archive that uses pthread. In my case I needed in addition to -Wl,--whole-archive -lpthread -Wl,--no-whole-archive also do -Wl,-u,... for each weak symbol.

My symptom was crashes at runtime and when using gdb to disassemble I could see that the crash was just after a callq 0x0 which seemed suspicious. Did some seaching and found that others had seen this with static pthread linking.

I figured out which symbols to force resolve by using nm and look for w symbols. After linking I could then see that the callq 0x0 instructions had been updated with various symbol addresses to pthread_mutex_lock etc.

Mattias Wadman
  • 10,527
  • 2
  • 39
  • 53