代码
这是导致段错误的程序。
#include <iostream>
#include <vector>
#include <memory>
int main()
{
std::cout << "Hello World" << std::endl;
std::vector<std::shared_ptr<int>> y {};
std::cout << "Hello World" << std::endl;
}
当然,程序本身没有任何问题。segfault的根本原因取决于其构建和运行的环境。
背景
我们在亚马逊使用一个构建系统,以一种几乎与机器无关的方式构建和部署二进制文件(lib
和bin
)。对于我们的情况,这基本上意味着它将可执行文件(从上述程序构建而来)部署到$project_dir/build/bin/
中,将几乎所有依赖项(即共享库)部署到$project_dir/build/lib/
中。我之所以用“几乎”这个短语,是因为对于共享库,如libc.so
、libm.so
、ld-linux-x86-64.so.2
和可能还有其他一些库,可执行文件会从系统(即从/lib64
)中选择。请注意,它应该从$project_dir/build/lib
中选择libstdc++
。
现在我按以下方式运行它:
$ LD_LIBRARY_PATH=$project_dir/build/lib ./build/bin/run
segmentation fault
然而,如果我不设置LD_LIBRARY_PATH
,它可以正常运行。
诊断
1. ldd
以下是两种情况下的ldd
信息(请注意,我已编辑输出以在差异处提到库的完整版本)。
$ LD_LIBRARY_PATH=$project_dir/build/lib ldd ./build/bin/run
linux-vdso.so.1 => (0x00007ffce19ca000)
libstdc++.so.6 => $project_dir/build/lib/libstdc++.so.6.0.20
libgcc_s.so.1 => $project_dir/build/lib/libgcc_s.so.1
libc.so.6 => /lib64/libc.so.6
libm.so.6 => /lib64/libm.so.6
/lib64/ld-linux-x86-64.so.2 (0x0000562ec51bc000)
没有LD_LIBRARY_PATH:
$ ldd ./build/bin/run
linux-vdso.so.1 => (0x00007fffcedde000)
libstdc++.so.6 => /usr/lib64/libstdc++.so.6.0.16
libgcc_s.so.1 => /lib64/libgcc_s-4.4.6-20110824.so.1
libc.so.6 => /lib64/libc.so.6
libm.so.6 => /lib64/libm.so.6
/lib64/ld-linux-x86-64.so.2 (0x0000560caff38000)
2. gdb when it segfaults
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7dea45c in _dl_fixup () from /lib64/ld-linux-x86-64.so.2
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.209.62.al12.x86_64
(gdb) bt
#0 0x00007ffff7dea45c in _dl_fixup () from /lib64/ld-linux-x86-64.so.2
#1 0x00007ffff7df0c55 in _dl_runtime_resolve () from /lib64/ld-linux-x86-64.so.2
#2 0x00007ffff7b1dc41 in std::locale::_S_initialize() () from $project_dir/build/lib/libstdc++.so.6
#3 0x00007ffff7b1dc85 in std::locale::locale() () from $project_dir/build/lib/libstdc++.so.6
#4 0x00007ffff7b1a574 in std::ios_base::Init::Init() () from $project_dir/build/lib/libstdc++.so.6
#5 0x0000000000400fde in _GLOBAL__sub_I_main () at $project_dir/build/gcc-4.9.4/include/c++/4.9.4/iostream:74
#6 0x00000000004012ed in __libc_csu_init ()
#7 0x00007ffff7518cb0 in __libc_start_main () from /lib64/libc.so.6
#8 0x0000000000401021 in _start ()
(gdb)
3. LD_DEBUG=all
我尝试启用LD_DEBUG=all
来查看链接器信息,以解决段错误的问题。我发现有些可疑的地方,因为它在搜索pthread_once
符号时无法找到,导致了段错误(以下输出片段是我的解释):
initialize program: $project_dir/build/bin/run
symbol=_ZNSt8ios_base4InitC1Ev; lookup in file=$project_dir/build/bin/run [0]
symbol=_ZNSt8ios_base4InitC1Ev; lookup in file=$project_dir/build/lib/libstdc++.so.6 [0]
binding file $project_dir/build/bin/run [0] to $project_dir/build/lib/libstdc++.so.6 [0]: normal symbol `_ZNSt8ios_base4InitC1Ev' [GLIBCXX_3.4]
symbol=_ZNSt6localeC1Ev; lookup in file=$project_dir/build/bin/run [0]
symbol=_ZNSt6localeC1Ev; lookup in file=$project_dir/build/lib/libstdc++.so.6 [0]
binding file $project_dir/build/lib/libstdc++.so.6 [0] to $project_dir/build/lib/libstdc++.so.6 [0]: normal symbol `_ZNSt6localeC1Ev' [GLIBCXX_3.4]
symbol=pthread_once; lookup in file=$project_dir/build/bin/run [0]
symbol=pthread_once; lookup in file=$project_dir/build/lib/libstdc++.so.6 [0]
symbol=pthread_once; lookup in file=$project_dir/build/lib/libgcc_s.so.1 [0]
symbol=pthread_once; lookup in file=/lib64/libc.so.6 [0]
symbol=pthread_once; lookup in file=/lib64/libm.so.6 [0]
symbol=pthread_once; lookup in file=/lib64/ld-linux-x86-64.so.2 [0]
但是我没有看到任何关于成功运行时的pthread_once
!
问题
我知道这样调试非常困难,可能我没有提供关于环境等方面的很多信息。但是,我的问题是:这个段错误可能的根本原因是什么?如何进一步调试并找到它?一旦我找到了问题,修复将变得容易。
编译器和平台
我正在使用 RHEL5 上的 GCC 4.9。
实验
E#1
如果我注释掉以下行:
std::vector<std::shared_ptr<int>> y {};
编译和运行都很顺利!
E#2
我刚刚把以下头文件包含到我的程序中:
#include <boost/filesystem.hpp>
并且相应地进行了链接。现在它可以正常工作,没有任何段错误。因此,似乎通过对libboost_system.so.1.53.0
有依赖性,一些要求得到了满足,或者问题被规避了!
E#3
由于我看到它在将可执行文件链接到libboost_system.so.1.53.0
时工作,所以我按照以下步骤进行了操作。
不是在代码本身中使用#include <boost/filesystem.hpp>
,而是使用原始代码,并通过使用LD_PRELOAD
预装libboost_system.so
来运行它,如下所示:
$ LD_PRELOAD=$project_dir/build/lib/libboost_system.so $project_dir/build/bin/run
它成功运行了!
接下来我对 libboost_system.so
进行了 ldd
操作,得到了一系列库文件列表,其中有两个:
/lib64/librt.so.1
/lib64/libpthread.so.0
所以我不是预加载libboost_system
,而是分别预加载librt
和libpthread
:
$ LD_PRELOAD=/lib64/librt.so.1 $project_dir/build/bin/run
$ LD_PRELOAD=/lib64/libpthread.so.0 $project_dir/build/bin/run
在这两种情况下,它都成功运行了。
现在我的结论是,通过加载
librt
或libpthread
(或两者都是),满足了一些要求或绕过了问题!尽管我仍然不知道问题的根本原因。
编译和链接选项
由于构建系统很复杂,并且有许多默认选项。因此,我尝试使用CMake的set
命令显式添加-lpthread
,然后它就可以工作了,正如我们已经看到的,通过预装载libpthread
它可以工作!为了看到这两种情况(当它工作时和当它给出segfault时)之间的构建差异,我以详细模式构建它,通过将
-v
传递给GCC,查看编译阶段和实际传递给cc1plus
(编译器)和collect2
(链接器)的选项。(请注意,路径已经被编辑为简洁起见,使用美元符号和虚拟路径。)
无论它是否工作,$/gcc-4.9.4/cc1plus -quiet -v -I /a/include -I /b/include -iprefix $/gcc-4.9.4/ -MMD main.cpp.d -MF main.cpp.o.d -MT main.cpp.o -D_GNU_SOURCE -D_REENTRANT -D __USE_XOPEN2K8 -D _LARGEFILE_SOURCE -D _FILE_OFFSET_BITS=64 -D __STDC_FORMAT_MACROS -D __STDC_LIMIT_MACROS -D NDEBUG $/lab/main.cpp -quiet -dumpbase main.cpp -msse -mfpmath=sse -march=core2 -auxbase-strip main.cpp.o -g -O3 -Wall -Wextra -std=gnu++1y -version -fdiagnostics-color=auto -ftemplate-depth=128 -fno-operator-names -o /tmp/ccxfkRyd.s
cc1plus
的命令行参数都完全相同。根本没有区别。这似乎并不是很有帮助。然而,区别在于链接时间。下面是我看到的,在它工作时的情况下:
您可以看到,
-lpthread
被提及了两次!第一个 -lpthread
(后面跟着 --as-needed
)在给出段错误的情况下是缺少的。这是这两种情况之间唯一的区别。
两种情况下 nm -C
命令的输出结果
有趣的是,两种情况下 nm -C
命令的输出结果相同(如果忽略第一列中的整数值)。0000000000402580 d _DYNAMIC
0000000000402798 d _GLOBAL_OFFSET_TABLE_
0000000000401000 t _GLOBAL__sub_I_main
0000000000401358 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
U _Unwind_Resume
0000000000401150 W std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_destroy()
0000000000401170 W std::vector<std::shared_ptr<int>, std::allocator<std::shared_ptr<int> > >::~vector()
0000000000401170 W std::vector<std::shared_ptr<int>, std::allocator<std::shared_ptr<int> > >::~vector()
0000000000401250 W std::vector<std::unique_ptr<int, std::default_delete<int> >, std::allocator<std::unique_ptr<int, std::default_delete<int> > > >::~vector()
0000000000401250 W std::vector<std::unique_ptr<int, std::default_delete<int> >, std::allocator<std::unique_ptr<int, std::default_delete<int> > > >::~vector()
U std::ios_base::Init::Init()
U std::ios_base::Init::~Init()
0000000000402880 B std::cout
U std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
0000000000402841 b std::__ioinit
U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
U operator delete(void*)
U operator new(unsigned long)
0000000000401510 r __FRAME_END__
0000000000402818 d __JCR_END__
0000000000402818 d __JCR_LIST__
0000000000402820 d __TMC_END__
0000000000402820 d __TMC_LIST__
0000000000402838 A __bss_start
U __cxa_atexit
0000000000402808 D __data_start
0000000000401100 t __do_global_dtors_aux
0000000000402820 t __do_global_dtors_aux_fini_array_entry
0000000000402810 d __dso_handle
0000000000402828 t __frame_dummy_init_array_entry
w __gmon_start__
U __gxx_personality_v0
0000000000402838 t __init_array_end
0000000000402828 t __init_array_start
00000000004012b0 T __libc_csu_fini
00000000004012c0 T __libc_csu_init
U __libc_start_main
w __pthread_key_create
0000000000402838 A _edata
0000000000402990 A _end
000000000040134c T _fini
0000000000400e68 T _init
0000000000401028 T _start
0000000000401054 t call_gmon_start
0000000000402840 b completed.6661
0000000000402808 W data_start
0000000000401080 t deregister_tm_clones
0000000000401120 t frame_dummy
0000000000400f40 T main
00000000004010c0 t register_tm_clones
pthread_once
在libthread
中。如果您使用-pthread
选项编译程序,它会解决问题吗?您说包含libboost_system.so.1.53.0
解决了您的问题,但请注意,libboost_system.so.1.53.0
链接到libpthread
。从您提供的跟踪中,build/private/builds/RelWithDebInfo/runpools
在可搜索文件列表中。问题:runpools
需要与libphtreads
链接吗? - Amadeuspthread_once
在静态变量的初始化中是必需的,可能被locale
所需要。这是 C++11 语言支持的一部分,可以使用-f(no)threadsafe-statics
来禁用/启用它。 - sbabbi