g++静态链接pthread时,导致Segmentation fault,为什么?
#include #include <地图>#include <线程>#define 尺寸 1024#define 数量 100000#define 线程 4A级{私人的:字符 [大小];};无效测试(){std::cout <<"测试开始
";std::map容器;for(int i=0; ip = std::make_pair(i, a);容器.插入(p);}std::cout <<"测试版
";for(int i=0; isecond;container.erase(iter);}std::cout <<"测试结束
";}int main(){std::thread ts[线程];for(int i=0; i
以上是一个简单的 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
,一切正常.
然后我尝试一个静态链接:g++ -pthread -o one one.cpp -Wall -std=c++11 -O3 -static
ldd one
,得到:
不是动态可执行文件
但是当我运行它时,出了点问题...
测试开始分段错误(核心转储)
用-g
重新编译,gdb显示:
wang[00:35][~/test]$ gdb 一GNU gdb (Ubuntu 7.10-1ubuntu2) 7.10版权所有 (C) 2015 Free Software Foundation, Inc.许可证 GPLv3+:GNU GPL 版本 3 或更高版本 这是免费软件:您可以自由更改和重新分发它.在法律允许的范围内,不提供任何保证.输入显示复制"和显示保修"了解详情.这个 GDB 被配置为x86_64-linux-gnu".键入show configuration"以获取配置详细信息.有关错误报告的说明,请参阅:<http://www.gnu.org/software/gdb/bugs/>.在线查找 GDB 手册和其他文档资源:<http://www.gnu.org/software/gdb/documentation/>.如需帮助,请键入帮助".输入apropos word"以搜索与word"相关的命令...从一个人读取符号......完成.(gdb) 运行启动程序:/home/wang/test/one[启用使用 libthread_db 的线程调试]使用主机 libthread_db 库/lib/x86_64-linux-gnu/libthread_db.so.1".[新线程 0x7ffff7ffa700 (LWP 3623)]测试开始[新线程 0x7ffff77f8700 (LWP 3624)]测试开始[新线程 0x7ffff6ff7700 (LWP 3625)]测试开始[新线程 0x7ffff67f6700 (LWP 3626)]测试开始程序收到信号 SIGSEGV,分段错误.0x0000000000000000 在 ??()(gdb)
为什么会这样?
更新================================
使用 boost::thread
库(boost 版本:1.60),
用 boost::thread
替换 std::thread
,并建立一个静态链接,
g++ -pthread -o one1 one.cpp -Wall -std=c++11 -O3 -I/opt/boost/include/-L/opt/boost/lib/-lboost_system -lboost_thread -static
没有出现问题!
困惑...
解决方案一、解决方案.这在这里会起作用:
更新: 自 Ubuntu 18.04 起,您还需要链接 librt(添加 <代码>-lrt):
g++ -o one one.cpp -Wall -std=c++11 -O3 -static -lrt -pthread -Wl,--whole-archive -lpthread -Wl,--no-whole-archive
<小时>
(继续原答案)
g++ -o one one.cpp -Wall -std=c++11 -O3 -static -pthread -Wl,--whole-archive -lpthread -Wl,--no-whole-archive
当您使用 -pthread
时,编译器已经链接到 pthread(并且根据平台,它确实定义了额外的宏,如 -D_REENTRANT
,请参阅 这个问题了解更多详情.
那么,如果-pthread
意味着-lpthread
,那么在静态链接时为什么需要指定-lpthread
?Wl,--whole-archive
有什么作用?
理解弱符号
在 Unix 上,使用 ELF 文件格式,其中有 弱符号和强符号.引用维基百科页面:
<块引用>默认情况下,没有任何注释,目标文件中的符号是strong.在链接期间,强符号可以覆盖同名的弱符号.相比之下,共享名称的两个强符号会在链接时产生链接错误.
动态库和静态库之间存在细微差别.在静态库中,链接器将在第一个符号处停止,即使它是弱符号,并停止寻找强符号.为了强制它查看所有符号(就像它对动态链接库所做的那样),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++ 中的弱符号
弱符号的用例之一是能够用优化的实现替换.另一种是使用存根,如果需要,可以稍后替换.
例如,fputc
(概念上被printf
使用) 是 POSIX 要求线程安全的,需要同步,成本高.在单线程环境中,您不想支付费用.因此,实现可以将同步函数实现为空存根,并将函数声明为弱符号.
稍后,如果链接了多线程库(例如 pthread),则很明显不打算支持单线程.当链接多线程库时,链接器可以用真正的同步函数(定义为强符号并由线程库实现)替换存根.另一方面,如果没有链接多线程库,可执行文件将使用存根作为同步函数.
glibc(提供 fputc
)和 pthread 似乎正是使用了这个技巧.有关详细信息,请参阅此有关 glibc 中弱符号使用的问题.上面的例子取自这个答案.
nm 可以让你详细看一下,貌似和上面引用的答案:
$ nm/usr/lib/libc.a 2>/dev/null |grep pthread_mutex_lockw __pthread_mutex_lock...(重复)
w"代表弱",所以静态链接的 libc 库包含 __pthread_mutex_lock
作为弱符号.静态链接的 pthread 库包含它作为一个强符号:
$ nm/usr/lib/libpthread.a 2>/dev/null |grep pthread_mutex_lock启动线程互斥锁pthread_mutex_lock.o:00000000000006a0 T __pthread_mutex_lock00000000000006a0 T pthread_mutex_lock0000000000000000 t __pthread_mutex_lock_full
返回示例程序
通过查看动态链接可执行文件的共享库依赖关系,我在我的机器上得到了几乎相同的 ldd
输出:
$ ldd 一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 ./onestd::ios_base::Init::Init()(0x563ab8df71b1, 0x7ffdc483cae8, 0x7ffdc483caf8, 160) = 0__cxa_atexit(0x7fab3023bc20, 0x563ab8df71b1, 0x563ab8df7090, 6) = 0运算符新(无符号长)(16,0x7ffdc483cae8,0x7ffdc483caf8,192)= 0x563ab918bc20std::thread::_M_start_thread(std::unique_ptr, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x320fab2fa5, 0x7fab2fa43a80) = 0运算符 new(unsigned long)(16, 0x7fab2f6a1fb0, 0, 0x800000) = 0x563ab918bd70std::thread::_M_start_thread(std::unique_ptr, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x320fab2fa5, 0x7fab2fa43a80) = 0运算符 new(unsigned long)(16, 0x7fab2eea0fb0, 0, 0x800000) = 0x563ab918bec0std::thread::_M_start_thread(std::unique_ptr, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x320fab2fa5, 0x7fab2fa43a80 测试开始) = 0运算符 new(unsigned long)(16, 0x7fab2e69ffb0, 0, 0x800000) = 0x563ab918c010std::thread::_M_start_thread(std::unique_ptr, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x320fab2fa5, 0x7fab2fa43a80 测试开始测试开始) = 0std::thread::join()(0x7ffdc483c9a0, 0x7fab2de9efb0, 0, 0x800000test start测试版测试版测试版测试版测试结束测试结束测试结束测试结束) = 0std::thread::join()(0x7ffdc483c9a8, 0x7fab2eea19c0, 0x7fab2f6a2700, 0) = 0std::thread::join()(0x7ffdc483c9b0, 0x7fab2e6a09c0, 0x7fab2eea1700, 0) = 0std::thread::join()(0x7ffdc483c9b8, 0x7fab2de9f9c0, 0x7fab2e6a0700, 0) = 0+++ 退出(状态 0)+++
例如,std::thread::join
被调用,它很可能会在内部使用 pthread_join
.该符号可以在 ldd
输出中列出的(动态链接的)库中找到,即在 libstdc++.so.6
和 libpthread.so.0代码>:
$ nm/usr/lib/libstdc++.so.6 |grep pthread_joinw pthread_join$ nm/usr/lib/libpthread.so.0 |grep pthread_join0000000000008280 T pthread_join
在动态链接的可执行文件中,链接器将用强符号替换弱符号.在这个例子中,我们必须对静态链接的库强制执行相同的语义.这就是为什么需要 -Wl,--whole-archive -lpthread -Wl,--no-whole-archive
的原因.
找到它需要一些反复试验.至少,我没有找到关于该主题的明确文档.我认为这是因为 Linux 上的静态链接已成为一种边缘情况,而动态链接通常是关于如何使用库(要进行比较,请参阅静态链接与动态链接).我见过的最极端的例子是静态链接TBB.>
附录: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@
#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
";
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
";
for(int i=0; i<AMOUNT; i++)
{
auto iter = container.find(i);
delete iter->second;
container.erase(iter);
}
std::cout << "test end
";
}
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...
解决方案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@
相关文章