セグメンテーション・フォルト SIGSEGV や SIGBUS エラーの原因を特定する

インテル® Fortran コンパイラー

この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Determining Root Cause of Segmentation Faults SIGSEGV or SIGBUS errors」(https://www.intel.com/content/www/us/en/developer/articles/troubleshooting/determining-root-cause-of-sigsegv-or-sigbus-errors.html) の日本語参考訳です。


問題: インテル® Fortran コンパイラーでコードをコンパイルすると、Linux* で ‘sigsegv’ (または Mac OS* X で ‘sigbus’) エラーが発生することがあります。このコードは、以前のコンパイラー/プラットフォームでは問題なく動作していました。インテル® コンパイラーが問題なのではありませんか、という問い合わせを受けることがあります。この記事ではこれに関する回避方法を紹介します。

環境: Linux* または Mac OS* X

原因: 複数の原因が考えられます。セグメンテーション・フォルト (Mac OS* X のバスエラー) はさまざまな原因によって引き起こされる一般的な問題です。これまでに判明している原因とセグメンテーション・フォルトを回避するための方法を以下に示します。

原因 #1: Fortran 固有のスタック領域不足。解決方法: -heap-arrays コンパイラー・オプションを指定します。

インテル® Fortran コンパイラーは、スタック領域を使用して配列データの一時または中間コピーの割り当てを行っています。

OpenMP* および自動並列化を使用していないアプリケーション: プログラムで OpenMP* または自動並列化 (-parallel コンパイラー・オプション) を使用していない場合で、Mac OS* X 版コンパイラー (またはバージョン 9.1.037 以降の Linux* 版コンパイラー) を使用している場合は、-heap-arrays コンパイラー・オプションを試してください。OpenMP* または自動並列化を使用している場合で、バージョン 9.1.037 よりも前の Linux* 版コンパイラーを使用している場合は、原因 #2 の解決方法を参照して、スタックサイズを無制限に設定してください。

-heap-arrays

上記の方法で sigsegv やバスエラーが発生しなくなった場合は、この先に進む必要はありません。配列の一時コピーがいつどこで作成されるかについての詳細は、プレゼンテーション PDF (ページの下部にリンクがあります) を参照してください。コードに多少の変更を加えることで、配列の一時コピーを作成しないように設定できます。一時コピーを作成しないようにすることで、アプリケーションのパフォーマンスが向上します。

-heap-arrays コンパイラー・オプションでは、オプションの引数 [size] で、ヒープ領域に割り当てる配列サイズのしきい値を指定できます (単位は KB)。[size] を超える配列はヒープ領域に、残りはスタック領域にそれぞれ割り当てられます。次に例を示します。

-heap-arrays 10

10KB を超えるすべての自動配列と一時配列はヒープ領域に割り当てられます。

原因 #2: スタック領域不足。解決方法: OpenMP* およびほかのアプリケーションのスタックサイズを無制限にします。

最初のステップでは、Linux* および Mac OS* X シェルのスタックサイズを増やしました。ただし、この方法は、OpenMP* または自動並列化コードを使用して共有しているデータに悪影響を及ぼすことがあります。そのため、OpenMP* および自動並列化を使用しているユーザーは、-heap-arrays オプションを指定しないで、シェルのスタックサイズを無制限にすることを推奨します。

bash: ulimit -s unlimited csh/tcsh: unlimit stacksize

スタックサイズを確認するには、以下のコマンドを実行します。

bash: ulimit -a csh: limit

シェル環境のスタックサイズの制限値が表示されます。

注: バッチ・サブシステムでプログラムを実行する場合は、スタートアップ・ファイル ( ~/.bashrc ~/.profile または ~/.cshrc ) に上記のコマンドを追加してください。

Mac OS* X の場合、シェルのスタックサイズに上限があります。多くのシステムでは、

bash: ulimit -s 65532

で 64MB に制限されています。

他の方法として、http://software.intel.com/en-us/articles/intel-fortran-compiler-increased-stack-usage-of-80-or-higher-compilers-causes-segmentation-fault/ で紹介しているように、リンカーオプションを指定して実行ファイルの (デフォルトの) シェルのスタックサイズを増やすことです。

アプリケーションを再実行します。問題が修正された場合は、この先に進む必要はありません。アプリケーションで sigsegv やバスエラーがまだ発生する場合は、この先に進んでください。

原因 #2 -prime: ヒープまたはメモリー不足によるスタック領域不足。

プロセス・メモリー・マップで、ヒープとスタックは下位と上位から互いの方向に増えます。ヒープとスタックが衝突すると、ヒープ割り当てまたは次のスタック割り当てのいずれかでセグメンテーション・フォルトが発生します。

また、アプリケーションの物理メモリーとスワップ空間をすべて使用してしまうこともあります。64 ビット・アプリケーションでは仮想メモリーに実質的な制限はありませんが、現実的に使用可能なメモリー量は、物理メモリー + スワップ領域 (通常は物理メモリーの 2 倍) が上限になります。使用可能なメモリー量は、’free’ コマンドを使用して確認できます。物理メモリーは、’cat /proc/meminfo’ コマンドを使用して ‘MemTotal’ および ‘SwapTotal’ フィールドで確認することもできます。

システムカーネルもメモリーを使用することから、一般に、アプリケーションが利用するメモリーの上限は MemTotal の約 80% に抑える必要があります。また、MemTotal と SwapTotal の合計を決して超えてはなりません。

コードが異常終了する場所を特定するには、-g -traceback オプションを指定してコンパイルおよびリンクします。

原因 #3: コーディング・ミスによるスタック破損。

開発者のコーディング・ミスによるスタック破損が原因で、ランタイムに sigsegv やバスエラーが発生することがあります。プログラムのスタックが最初に破損された場所とは関係ないセクションで後からセグメンテーション・フォルトが発生するため、原因の特定は非常に困難です。

まず、フォルトが発生している場所を特定するために、実行の「トレースバック」を作成します。ifort コマンドで以下のオプションを指定してコンパイルおよびリンクします。

-g -traceback

問題が発生すると、そのときのコールスタックを示すレポートが生成されます。スタックがトレースバックしない場合、コンパイルとリンクの両方で -g オプションを指定し、コンパイルで -traceback オプションを指定していることを確認してください。プログラムがカーネル空間にあるときにセグメンテーション・フォルトが発生して、ユーザースタックがトレースバックしないことがあります。この問題については、将来のリリースで修正される予定です。

トレースバックのレポートは、下から上に読みます。コードの最上位のサブルーチンまたは関数とその行番号を確認して、フォルトが発生している場所を特定します。場所を特定したら、コーディング・ミスがないか確認します。コーディング・ミスがない場合は、この先に進んでください。

原因 #4: 配列境界を超えた。解決方法: -check bounds コンパイラー・オプションを指定します。

-check bounds コンパイラー・オプションを指定すると、配列アクセスと文字列式のランタイム・チェックが行われ、インデックスが配列の境界内にあることが保証されます。このチェックは、インデックスが宣言した配列のサイズ外にあるかどうかを確認するのに便利です。

このオプションはパフォーマンスに大きな影響を及ぼします。影響の大きさは、アプリケーションで実行される配列アクセスの数に依存します。さらに、-check bounds による配列境界チェックは、最後の次元境界が * で指定される仮引数の配列や上限と下限の両方が 1 の配列では行われません。   境界チェックを有効にするには、

-check bounds -g

オプションを指定してコンパイルを行った後、プログラムを実行します。チェックは、コンパイル時ではなくランタイムに行われます。問題が修正された場合は、この先に進む必要はありません。問題がまだ修正されない場合は、この先に進んでください。

原因 #5: 関数をサブルーチンとして呼び出した、またはサブルーチンを関数として呼び出した。

例えば、開発者が以下のようなコーディング・ミスをすることがあります。

— main program —

call ThisIsIllegal( some_arguments )

— end main program —

— ThisIsIllegal —
integer function ThisIsIllegal( some_arguments )

— end ThisIsIllegal —

上記の例で、メイン・プログラムは関数として宣言した ThisIsIllegal をサブルーチンとして呼び出しています。このミスはスタック破損の原因になります。スタックの状態を確認するには、

-fp-stack-check -g -traceback

オプションを指定してコンパイルを行った後、プログラムを実行します。   上記と同様のミスによってスタックが破損した場合、コードが終了してスタックトレースが出力されます。

コンパイル時にプロシージャーのインターフェイスをチェックすることができます。

-gen-interfaces および -warn interfaces

-gen-interfaces オプションを指定してコンパイルすると、プロシージャーのインターフェイス・ブロックを含む mod ファイルが生成されます。次に、-warn interfaces オプションを指定してコンパイルすると、mod ファイルを使用して、呼び出し元と呼び出し先の間で引数とインターフェイスが一致しているかどうか、プロシージャーの呼び出しがチェックされます。   このチェックは Fortran ソースファイルのみが対象となることに注意してください。言語が混在したプログラムのインターフェイスはチェックできません。

原因 #6: 連続しない配列セクションが渡されたことによる配列の一時コピーの増加。

解決方法: -check arg_temp_created オプションを指定して作成された引数をチェックし、明示的なインターフェイスと想定される形式の配列を含むようにコーディングを変更します。

以下の例について考えてみます。

— main program —
real(8) :: f(1800,3600,1)
external sub

call sub( f(1:900,:,:) )

— end main program —

“sub” サブルーチンは別にコンパイルされたソースファイルに含まれているものとします。

— external subroutine “sub” —
subroutine sub( f )
real(8) :: f(900,3600,1)

— end subroutine “sub” —

この例で、”sub” はサイズ 900x3600x1 の連続した配列を想定していますが、実際の呼び出しでは、メモリー内の連続していない配列が渡されます。この状況で、コンパイラーは、呼び出し時の配列の一時コピーで、非連続チャンクの配列 “f” の要素が (“sub” が想定している) 連続した配列にコピーされるようにします。-heap-arrays が指定された場合を除いて、この一時コピーはスタック上に割り当てられます。

この問題がコードで発生しているかどうかチェックするには、

-check arg_temp_created

オプションを指定してコンパイルを行った後、プログラムを実行します。引数のコピーが作成されている場合、メッセージが表示されます。この問題を回避するために、明示的なインターフェイスを作成して “sub” で想定される形式の配列を使用すると、配列の一時コピーを行う必要がなくなります。

— main —
real(8) :: f(1800,3600,1)
interface
subroutine sub(f)
real(8) :: f(:,:,:)
end subroutine sub
end interface

call sub( f(1:900,:,:) )

— end main program —

— “sub” —
subroutine sub( f )
real(8) :: f(:,:,:)

end subroutine sub

この結果、配列の一時コピーは回避されますが、コンパイラーは “sub” 内の配列 “f” が非連続であるとみなすようになり、”f” を使用するステートメントでいくつかの最適化を無効にするため、パフォーマンスに影響します。

上記の原因に該当しない場合、より詳細な解析が必要です

sigsegv やバスエラーの 99% は上記のいずれかが原因ですが、このほかにもセグメンテーション・フォルトが発生する場合があります。

アプリケーションで外部ライブラリーをリンクしている場合、コンパイラーと互換性があることを確認します。その外部ライブラリーがインテル® コンパイラーでコンパイルされている場合、メジャーバージョンが同じであることを確認します。つまり、ライブラリーがインテル® Fortran 9.1 でコンパイルされているのに、アプリケーションがインテル® Fortran 10.x 、11.x や 12.x でコンパイルされていないかを確認します。インテルでは、同じメジャーバージョン内 (9、10、11、12 など) の互換性のみを保証しています。

ソフトウェア・ベンダーが提供しているライブラリーを外部ライブラリーとして使用している場合、そのベンダーがインテル® コンパイラーと互換性があることを明示しているか確認します。明示している場合は、互換性を確認したバージョンを調べて、ベンダーが保証しているインテル® コンパイラーのバージョンを使用します。インテル® コンパイラーの以前のバージョンが必要な場合は、http://software.intel.com/en-us/articles/older-version-product/ をお読みください。

まだ問題が解決しない場合

ユーザーフォーラムで問題を報告してください。   問題が発生したコード、スタックトレースの結果 (提供可能な場合)、使用したコンパイラー・オプションをお知らせください。アプリケーション、入力ファイル、プログラムの実行方法をすべて tar で圧縮してお送りいただけると、迅速に確認することができます。

製品のサポートサービスが有効であれば、http://premier.intel.com で問題を報告することもできます。

Dr. Fortran の記事『Don’t Blow Your Stack!』(http://software.intel.com/en-us/forums/intel-visual-fortran-compiler-for-windows/topic/41911/reply/12580/ (英語)) に詳細な説明があります。

また、プレゼンテーション PDF 『Fortran Compiler Use of Temporaries』 (http://software.intel.com/file/20415) にも参考になる情報が掲載されています。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

タイトルとURLをコピーしました