c++ - C++ 时,G++ static 链接,导致分割错误,为什么?

  显示原文与译文双语对照的内容
89 5

#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 startn";


 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 releasen";


 for(int i=0; i<AMOUNT; i++)


 {


 auto iter = container.find(i);


 delete iter->second;


 container.erase(iter);


 }


 std::cout <<"test endn";


}



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++ 代码。

编译使用: g++ -pthread -o one one.cpp -Wall -std=c++11 -O3

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,一切都正常。

然后我尝试一个 static 链接: g++ -pthread -o one one.cpp -Wall -std=c++11 -O3 -static

ldd one,有:


 not a dynamic executable



但当我运行它的时候,有些事情出错了。


test start


Segmentation fault (core dumped)



-g 编译,gdb显示:


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) 



为什么?

英镑的更新英镑

使用 boost::thread 库( boost版本: 1.60 )

boost::thread 替换 std::thread,并创建 static 链接,

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

没有出现问题 !

时间: 原作者:

133 2

首先,解决方案这里将工作:


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


 -Wl,--whole-archive -lpthread -Wl,--no-whole-archive



在使用 -pthread 时,编译器将链接到 pthread ( 根据平台,它确实定义了额外的MACROS,如 -D_REENTRANT,请参见这个问题。) 。

如果 -pthread 表示 -lpthread,那么在静态链接时为什么需要指定 -lpthread? 那么 Wl,--whole-archive 做什么?

了解弱符号

在Unix上,使用了文件格式,它具有弱的概念和强符号的概念。 要从维基百科Wikipedia引用:

默认情况下,如果没有任何注释,对象文件中的符号为 链接期间,强符号可以重写相同 NAME的符号。 相反,共享 NAME的两个强符号在链接时产生链接错误。

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

要从 man ld 引用:

在服务器的--whole-archive: 选项后面的每个归档中,包含每个对象文件,而不是搜索存档文件中所需的对象文件。 通常用于将归档文件转换为共享库,强制将每个对象包括在生成的共享库中。 这里选项可以使用多次。

通过解释 gcc,你必须将该选项作为 -Wl,--whole-archive 传递:

从gcc使用这里选项时的两个注释: 首先,gcc不知道这个选项,所以你必须使用 -Wl 。-whole-archive 。 它的次,不要忘记在归档列表之后使用 -Wl,因为gcc将自己的档案列表添加到链接中。

它还解释了如何把它关掉

--no-whole-archive: 关闭--whole-archive选项对后续存档文件的影响。

pthread和 libstdc+ + 中的弱符号

弱符号的一个用例是能够用优化的方法交换实现。 另一种方法是使用存根,如果有必要,以后可以替换。

例如,POSIX要求 fputc ( printf 使用的 conceptionally 。) 是线程安全的,需要同步,这是成昂的。 在单一线程环境中,你不希望支付成本。 因此实现可以实现同步函数作为空存根,并将函数声明为弱符号。

随后,如果链接了多线程库( 。比如,pthread ),那么很明显,单线程支持不是预期的。 链接多线程库时,链接器可以通过实际的同步函数( 定义为强符号并由线程库实现) 替换存根。 另一方面,如果没有链接多线程库,则可以执行程序将使用同步函数的存根。

glibc ( 提供 fputc ) 和pthreads似乎使用了这个技巧。 有关详细信息,请参阅关于glibc中弱符号用法的这个问题。 上面的例子来自于这个答案。

你可以查看 nm,这与上面提到的答案一致。


$ nm/usr/lib/libc.a 2>/dev/null | grep pthread_mutex_lock


w __pthread_mutex_lock


... (repeats)



""代表"弱",所以静态链接的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 是必需的。

找出来有点像 trial-and-error 。 至少我没有找到关于这个主题的清晰的文档。 这是因为在Linux上链接 static 已经成为了一个边缘案例,而动态链接通常是关于如何使用库的标准方法。 我看到和个人使用过一段时间的最极端的例子是链路的静态 is 。

附录:挂载程序的解决方案

如果你使用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@



原作者:
...