これがセグメンテーション違反を与えるプログラムです。
_#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
_から)。ただし、_libstdc++
_から_$project_dir/build/lib
_を選択するのは想定であることに注意してください。
今、私はそれを次のように実行します:
_$ LD_LIBRARY_PATH=$project_dir/build/lib ./build/bin/run
segmentation fault
_
ただし、_LD_LIBRARY_PATH
_を設定せずに実行すると。正常に動作します。
両方の場合のldd
情報を次に示します(出力を編集して、ライブラリのfullバージョン違いがある場合に言及していることに注意してください)
_$ 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)
_
_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)
_
また、セグメンテーション違反の場合に_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を使用しています。
次の行にコメントすると:
_std::vector<std::shared_ptr<int>> y {};
_
コンパイルして正常に実行されます!
プログラムに次のヘッダーを含めました。
_#include <boost/filesystem.hpp>
_
それに応じてリンクされます。これで、セグメンテーション違反なしで動作します。したがって、_libboost_system.so.1.53.0.
_に依存している、いくつかの要件が満たされている、または問題が回避されているようです。
実行可能ファイルを_libboost_system.so.1.53.0
_に対してリンクするようにしたときに機能するのを見たので、次のことを段階的に実行しました。
コード自体で_#include <boost/filesystem.hpp>
_を使用する代わりに、元のコードを使用し、次のように_libboost_system.so
_を使用して_LD_PRELOAD
_をプリロードして実行しました。
_$ LD_PRELOAD=$project_dir/build/lib/libboost_system.so $project_dir/build/bin/run
_
そしてそれはうまくいきました!
次に、_libboost_system.so
_でldd
を実行しました。これにより、ライブラリのリストが表示され、そのうちの2つは次のとおりです。
_ /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
_を明示的に追加しようとしましたが、preloadinglibpthread
ですでに確認したように、機能しました。
buildこれら2つのケースの違い(when-it-worksとwhen-it-gives-segfault)を確認するために、ビルドしましたverboseモードで_-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.od- 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 -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 run /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-必要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_c99f -lboost_math_c99l -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/a/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
_が言及されています2回!最初の_-lpthread
_(その後に_--as-needed
_が続く)はmissingsegfaultが発生した場合です。これは、これら2つのケースのonlyの違いです。
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
_
クラッシュのポイントと、libpthread
のプリロードで修正されたように見えるという事実を考えると、2つのケースの実行は locale_init.cc:315
で分岐すると思います。コードの抜粋は次のとおりです。
void
locale::_S_initialize()
{
#ifdef __GTHREADS
if (__gthread_active_p())
__gthread_once(&_S_once, _S_initialize_once);
#endif
if (!_S_classic)
_S_initialize_once();
}
__gthread_active_p()
は、プログラムがpthreadに対してリンクされている場合、特にpthread_key_create
が使用可能かどうかをチェックする場合にtrueを返します。私のシステムでは、このシンボルは「/usr/include/c++/7.2.0/x86_64-pc-linux-gnu/bits/gthr-default.h」でstatic inline
として定義されているため、ODR違反の潜在的な原因です。
LD_PRELOAD=libpthread,so
は常に__gthread_active_p()
にtrueを返すことに注意してください。
__gthread_once
は、常にpthread_once
に転送する必要があるもう1つのインラインシンボルです。
デバッグせずに何が起こっているのかを推測するのは難しいですが、__gthread_active_p()
の真のブランチにヒットしているはずがない場合でも、呼び出すpthread_once
がないため、プログラムがクラッシュしていると思われます。
[〜#〜] edit [〜#〜]:そこで、いくつかの実験を行いました。std::locale::_S_initialize
でクラッシュする唯一の方法は、__gthread_active_p
の場合です。 trueを返しますが、pthread_once
はにリンクされていません。
libstdc ++はpthread
に対して直接リンクしませんが、pthread_xx
の半分を弱いオブジェクトとしてインポートします。つまり、それらは未定義であり、リンカーエラーを引き起こしません。
明らかにpthreadをリンクするとクラッシュが消えますが、私が正しければ、主な問題は、pthreadをリンクしていなくても、libstdc++
がマルチスレッド実行可能ファイル内にあると見なすということです。
現在、__gthread_active_p
は__pthread_key_create
を使用して、スレッドがあるかどうかを判断します。これは、実行可能ファイルで弱いオブジェクトとして定義されています(nullptrでも問題ありません)。 shared_ptr
が原因でシンボルが存在することを99%確信しています(シンボルを削除し、nm
をもう一度確認してください)。したがって、どういうわけか__pthread_key_create
は有効なアドレスにバインドされます。これは、おそらくリンカーフラグの最後の-lpthread
が原因です。この理論を検証するには、locale_init.cc:315
にブレークポイントを設定し、どのブランチを使用するかを確認します。
EDIT2:
コメントの要約ですが、この問題は、次のすべてがある場合にのみ再現可能です。
ld.gold
の代わりにld.bfd
を使用してください--as-needed
を使用する__pthread_key_create
の弱い定義を強制します。この場合は、std::shared_ptr
のインスタンス化を介します。pthread
にリンクしていない、またはpthread
after--as-needed
にリンクしていない。コメントの質問に答えるには:
なぜデフォルトで金を使用するのですか?
デフォルトでは、/usr/bin/ld
を使用します。これは、ほとんどのディストリビューションで、/usr/bin/ld.bfd
または/usr/bin/ld.gold
へのシンボリックリンクです。このようなデフォルトは、update-alternatives
を使用して操作できます。 RHEL5にはデフォルトでld.gold
が付属していると私が理解している限り、なぜあなたの場合はld.bfd
であるのかわかりません。
また、必要に応じて、ゴールドがバイナリにpthread.so依存関係を追加しないのはなぜですか?
何が必要かという定義はどういうわけか怪しげだからです。 man ld
は言う(私の強調):
- 必要に応じて
-必要に応じて
このオプションは、-as-neededオプションの後にコマンドラインで指定されたダイナミックライブラリのELF DT_NEEDEDタグに影響します。通常、リンカーは次のDT_NEEDEDタグを追加します。ライブラリが実際に必要かどうかに関係なく、コマンドラインに記載されている各ダイナミックライブラリ。 --as-neededにより、DT_NEEDEDタグは、リンク内のその時点で非弱い未定義シンボルからの参照を満たすライブラリに対してのみ発行されます。通常のオブジェクトファイル、またはライブラリが他の必要なライブラリのDT_NEEDEDリストに見つからない場合は、別の必要なダイナミックライブラリからの弱くない未定義のシンボル参照。問題のライブラリの後にコマンドラインに表示されるオブジェクトファイルまたはライブラリは、ライブラリが必要に応じて表示されるかどうかには影響しません。これは、アーカイブからオブジェクトファイルを抽出するためのルールに似ています。 --no-as-neededは、デフォルトの動作を復元します。
さて、 このバグレポート によると、gold
は「弱い未定義のシンボル」の部分を尊重し、ld.bfd
は必要に応じて弱いシンボルを認識します。 TBH私はこれについて完全に理解していません。これがld.gold
バグと見なされるのか、それともlibstdc++
バグと見なされるのかについて、そのリンクについていくつかの議論があります。
-pthreadと-lpthreadの両方に言及する必要があるのはなぜですか? (-pthreadはビルドシステムによってデフォルトで渡され、ゴールドで動作するように-lpthreadを渡しました)。
-pthread
と-lpthread
は異なることをします( pthread vs lpthread を参照)。前者は後者を意味するべきだと私は理解しています。
とにかく、おそらく-lpthread
を1回だけ渡すことができますが、before--as-needed
を渡すか、最後のライブラリの後、--no-as-needed
の前に-lpthread
を使用する必要があります。
また、ゴールドリンカーを使用しても、システム(GCC 7.2)でこの問題を再現できなかったことにも言及する価値があります。したがって、より新しいバージョンのlibstdc ++で修正されたと思われます。これは、システム標準ライブラリを使用した場合にセグメンテーション違反が発生しない理由も説明している可能性があります。
これは、_libstdc++
_ ABI間の微妙な不一致によって引き起こされる問題である可能性があります。 GCC4.9はRedHat Enterprise Linux 5のシステムコンパイラではないため、そこで何を使用しているかは明確ではありません(DTS3?)。
ロケールの実装は、ABIの不一致に非常に敏感であることが知られています。 gcc-helpリストで次のスレッドを参照してください。
最善の策は、_libstdc++
_のどのビットがどこにリンクされているかを把握し、何らかの方法で一貫性を実現することです(シンボルを非表示にするか、互換性があるように再コンパイルします)。
Red Hatの開発者ツールセット(新しいビットは静的にリンクされていますが、C++標準ライブラリの大部分は既存のシステムDSOを使用しています)の_libstdc++
_に使用されるハイブリッドリンケージモデルを調査することも役立つ場合がありますが、システムは_libstdc++
_現在の言語機能のサポートが必要な場合、Red Hat Enterprise Linux5の__は古すぎる可能性があります。