当g ++静态链接pthread时,导致分段错误,为什么?

时间:2016-01-31 16:40:17

标签: c++ c++11 gcc boost pthreads

#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;
}

上面是一个简单的c ++代码。

编译:{{1​​}}

g++ -pthread -o one one.cpp -Wall -std=c++11 -O3,gots:

ldd one

运行 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) ,一切正常。

然后我尝试静态链接:./one

g++ -pthread -o one one.cpp -Wall -std=c++11 -O3 -static,gots:

ldd one

但是当我运行它时,有些事情出错......

    not a dynamic executable

使用test start Segmentation fault (core dumped) 重新编译,gdb显示:

-g

为什么会这样?

更新 ==============================

使用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) 库(提升版本:1.60),

boost::thread替换为std::thread,并建立静态链接

boost::thread

没有问题!

...混淆

2 个答案:

答案 0 :(得分:37)

首先,解决方案。这将是有效的:

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

当您使用-pthread时,编译器已经链接到pthread(并且根据平台,它确实定义了额外的宏,如-D_REENTRANT,有关详细信息,请参阅this question。) / p>

因此,如果-pthread暗示-lpthread,为什么在静态链接时需要指定-lpthreadWl,--whole-archive做了什么?

了解弱符号

在Unix上,使用ELF文件格式,其格式为weak and strong symbols。引用Wikipedia page

  

默认情况下,如果没有任何注释,目标文件中的符号。在链接期间,强符号可以覆盖同名的符号。相反,共享名称的两个强符号在链接时会产生链接错误。

动态和静态库存在细微差别。在静态库中,链接器将停在第一个符号,即使它是一个弱符号,并停止寻找强符号。要强制它查看所有符号(就像它对动态链接库所做的那样),ld支持--whole-archive选项。

引用man ld

  

--whole-archive:       对于--whole-archive选项后命令行中提到的每个存档,包括存档中的每个目标文件    链接,而不是在存档中搜索所需的目标文件。这通常用于将存档文件转换为    共享库,强制每个对象都包含在生成的共享库中。此选项可能会被多次使用。

继续从gcc解释,您必须将选项作为-Wl,--whole-archive传递:

  

从gcc使用此选项时的两个注意事项:首先,gcc不了解此选项,因此您必须使用-Wl,-whole-archive。     其次,不要忘记在档案列表之后使用-Wl,-no-whole-archive,因为gcc会将自己的档案列表添加到     您的链接,您可能不希望此标志也影响这些。

它再次解释了如何关闭它:

  

--no-whole-archive:      关闭后续存档文件的--whole-archive选项的效果。

pthread和libstdc ++中的弱符号

弱符号的一个用例是能够用优化的符号交换实现。另一种方法是使用存根,以后可以在必要时进行更换。

例如,POSIX要求fputcconceptionally used by printf)是线程安全的并且需要同步,这是昂贵的。在单线程环境中,您不希望支付成本。因此,实现可以将同步函数实现为空存根,并将函数声明为弱符号。

稍后,如果链接多线程库(例如,pthread),很明显不支持单线程支持。链接多线程库时,链接器可以通过实际同步函数(定义为强符号并由线程库实现)替换存根。另一方面,如果没有链接多线程库,则可执行文件将使用存根来实现同步功能。

glibc(提供fputc)和pthreads似乎正好使用了这个技巧。有关详细信息,请参阅此question about the usage of weak symbols in glibc。上面的示例取自this answer

nm允许您详细查看,这与上面引用的答案一致:

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

&#34; W&#34;代表&#34;弱&#34;,因此静态链接的libc库包含__pthread_mutex_lock作为弱符号。静态链接的pthread库将其包含为强符号:

$ 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

返回示例程序

通过查看动态链接的可执行文件的共享库依赖项,我在我的机器上获得了几乎相同的ldd输出:

$ 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)

使用ltrace打印库调用,会产生以下输出:

$ 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) +++

例如,调用std::thread::join,很可能在内部使用pthread_join。该符号可以在ldd输出中列出的(动态链接)库中找到,即libstdc++.so.6libpthread.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

在动态链接的可执行文件中,链接器将用强符号替换弱符号。在此示例中,我们必须为静态链接库强制执行相同的语义。这就是为什么需要-Wl,--whole-archive -lpthread -Wl,--no-whole-archive

找出它有点试错。至少,我没有找到关于该主题的明确文件。我认为这是因为static linking on Linux has become rather an edge case,而动态链接通常是关于如何使用库的规范方法(有关比较,请参阅Static linking vs dynamic linking)。我见过的最极端的例子是个人努力工作一段时间才能使其发挥作用{/ 3}}。

附录:Autotools的解决方法

如果您使用autotools作为构建系统,则需要一种解决方法,因为automake不允许您在LDADD中设置选项。不幸的是,你不能写:

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

作为一种解决方法,您可以通过在configure.ac中定义标志并使用它们来避免检查:

(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@

答案 1 :(得分:2)

在链接使用pthread的预构建C ++ .a归档文件时,我遇到了类似的问题。就我而言,除了-Wl,--whole-archive -lpthread -Wl,--no-whole-archive,我还需要为每个弱符号做-Wl,-u,...

我的症状是在运行时崩溃,当使用gdb进行反汇编时,我可以看到崩溃只是在callq 0x0之后,这似乎很可疑。进行了一些搜索,发现其他人已经通过静态pthread链接看到了这一点。

我找出了要使用nm强制解析的符号,并寻找w符号。链接之后,我可以看到callq 0x0指令已使用各种符号地址更新为pthread_mutex_lock等。