【发布时间】:2018-04-22 11:36:35
【问题描述】:
代码
这是给出段错误的程序。
#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;
}
当然,程序本身绝对没有错。段错误的根本原因取决于其构建和运行的环境。
背景
我们在 Amazon 使用构建系统,该系统以几乎机器独立的方式构建和部署二进制文件(lib 和 bin)。对于我们的案例,这基本上意味着它将可执行文件(由上述程序构建)部署到$project_dir/build/bin/ 中,几乎将其所有依赖项(即共享库)部署到 $project_dir/build/lib/ 中。为什么我使用短语 "almost" 是因为对于共享库,例如 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
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=全部
我还尝试通过为 segfault 案例启用 LD_DEBUG=all 来查看链接器信息。我发现了一些可疑的东西,因为它搜索 pthread_once 符号,当它找不到这个时,它给出了段错误(这是我对以下输出 sn-p BTW 的解释):
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(或 both ),可以满足某些要求或规避问题!不过,我仍然不知道问题的根本原因。
编译和链接选项
由于构建系统很复杂,并且默认情况下有很多选项。所以我尝试使用 CMake 的 set 命令显式添加 -lpthread,然后它起作用了,正如我们已经看到的那样,通过 预加载 libpthread 它起作用了!
为了查看这两种情况的build区别(when-it-works和when-it-gives-segfault) ,我通过将-v 传递给GCC 以verbose 模式构建它,以查看编译阶段和它实际传递给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 的命令行参数都是完全相同的。完全没有区别。这似乎不是很有帮助。
然而,区别在于链接时间。以下是我所看到的,适用的情况:
$/gcc-4.9.4/collect2 -plugin $/gcc-4.9.4/liblto_plugin.so
-plugin-opt=$/gcc-4.9.4/lto-wrapper -plugin-opt=-fresolution=/tmp/cchl8RtI.res -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass- through=-lgcc -plugin-opt=-pass-through=-lpthread -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through= -lgcc --eh-frame-hdr -m elf_x86_64 -export-dynamic -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o 运行 /usr/lib/../lib64/crt1.o /usr/lib/../lib64/crti.o $/gcc-4.9.4/crtbegin.o -L/a/lib -L/b/lib -L/c/lib -lpthread --as-needed main.cpp.o -lboost_timer -lboost_wave -lboost_chrono -lboost_filesystem -lboost_graph -lboost_locale -lboost_thread -lboost_wserialization -lboost_atomic -lboost_context -lboost_date_time -lboost_iostreams -lboost_math_c99 -lboost_math_c lboost_math_tr1 -lboost_math_tr1f -lboost_math_tr1l -lboost_mpi -lboost_prg_exec_monitor -lboost_program_options -lboost_random -lboost_regex -lboost_serialization -lboost_signals -lboost_system -lboost_unit_test_framework -lboost_exception -lboost_test_exec_monitor -lbz2 -licui18n -licuuc -licudata -lz -rpath /一个/ lib中:/ b / lib中:/ c/lib: -lstdc++ -lm -lgcc_s -lgcc -lpthread -lc -lgcc_s -lgcc $/gcc-4.9.4/crtend.o /usr/lib/../lib64/crtn. o
如您所见,-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
【问题讨论】:
-
从 C++ 的角度来看,由于 ODR 违规,它看起来像 UB。编译过程中使用的标准库的符号必须与链接过程中的符号相匹配,这似乎有时不是这里的情况。只需在目标机器上重新编译即可解决此问题。如果您不能这样做,请检查哪些库版本是二进制兼容的,并确保目标机器具有并使用这样的版本。或者,您可以尝试静态链接某些库而不是使用动态链接,但您可能无法对所有内容都这样做。
-
只是一些随意的想法:
pthread_once在libthread中。如果你用-pthread选项编译你的程序,它可以解决问题吗?您说包含libboost_system.so.1.53.0解决了您的问题,但请注意libboost_system.so.1.53.0与libpthread相关联。根据您提供的跟踪,build/private/builds/RelWithDebInfo/runpools位于可搜索文件列表中。问:runpools需要和libphtreads关联? -
@Amadeus:我确实使用
-pthread编译我的代码(默认情况下,在我继承的构建设置中)。但是,由于我的代码没有使用来自libpthread的任何symbol,因此链接器不会将其添加到可执行文件中。此外,LD_DEBUG 输出中pthread_once的搜索 似乎是红鲱鱼,因为它在成功运行时不存在。这意味着,它甚至不需要。 (runpools路径应该是$project_dir/build/bin/run顺便说一句;编辑了问题)。 -
只是一些随机的想法:
pthread_once是静态变量初始化所需要的,locale可能需要。这是 C++11 语言支持的一部分,可以使用-f(no)threadsafe-statics禁用/启用它。 -
你有没有机会使用黄金链接器?这个bug 看起来很像……
标签: c++ gcc segmentation-fault redhat ld