セグメンテーション違反の原因となるコード行を特定しますか?


151

セグメンテーション違反の原因となるコードのどこに間違いがあるかをどのように判断しますか?

コンパイラー(gcc)はプログラム内の障害の場所を表示できますか?


5
gcc / gdbはできません。segfaultが発生した場所を見つけることができますが、実際のエラーはまったく異なる場所にある可能性があります。

回答:


218

GCCはそれを行うことができませんが、GDB(デバッガー)は確実にできます。次の-gように、スイッチを使用してプログラムをコンパイルします。

gcc program.c -g

次にgdbを使用します。

$ gdb ./a.out
(gdb) run
<segfault happens here>
(gdb) backtrace
<offending code is shown here>

これは、GDBを使い始めるのに適したチュートリアルです。

segfaultが発生する場所は、一般に、コード内の「原因となる間違い」がどこにあるかについての手掛かりにすぎません。指定された場所は、必ずしも問題が存在する場所ではありません。


28
segfaultが発生する場所は、通常、コード内の「原因となる間違い」がどこにあるかについての手掛かりにすぎないことに注意してください。重要な手がかりですが、必ずしも問題が存在する場所ではありません。
mpez0

9
(bt full)を使用して詳細を取得することもできます。
ant2009

1
私はこれが便利だと思います:gnu.org/software/gcc/bugs/segfault.html
Loves Probability

2
bt省略形として使用しbacktraceます。
rustyx

43

また、valgrind試してみることができます:インストールvalgrindして実行した場合

valgrind --leak-check=full <program>

次に、プログラムを実行し、segfaultのスタックトレース、および無効なメモリの読み取りまたは書き込みとメモリリークを表示します。とても便利です。


2
+1、Valgrindはメモリエラーの発見に非常に高速で使いやすいです。デバッグシンボルを含む最適化されていないビルドでは、segfaultが発生した場所と理由が正確にわかります。
Tim Post

1
悲しいことに、-g -O0でコンパイルしてvalgrindと組み合わせると、segfaultが消えます。
JohnMudd

2
--leak-check=fullsegfaultsのデバッグには役立ちません。メモリリークのデバッグにのみ役立ちます。
ks1322 2018

@JohnMudd segfaultがテストされた入力ファイルの約1%しか表示されない場合、失敗した入力を繰り返しても失敗しません。私の問題はマルチスレッド化が原因でした。これまでのところ、この問題を引き起こしているコード行を理解していません。今のところ、この問題をカバーするために再試行を使用しています。-gオプションを使用すると、障害は解消されます。
Kemin Zhou

18

コアダンプを使用して、gdbで調べることもできます。有用な情報を取得するには、-gフラグを付けてコンパイルする必要もあります。

メッセージを受け取ったときはいつでも:

 Segmentation fault (core dumped)

コアファイルが現在のディレクトリに書き込まれます。そして、あなたはコマンドでそれを調べることができます

 gdb your_program core_file

ファイルには、プログラムがクラッシュしたときのメモリの状態が含まれています。コアダンプは、ソフトウェアの展開中に役立ちます。

システムがコアダンプファイルのサイズをゼロに設定していないことを確認してください。あなたはそれを無制限に設定することができます:

ulimit -c unlimited

注意してください!コアダンプが巨大になる可能性があります。


最近、arch-linuxに切り替えました。現在のディレクトリにコアダンプファイルが含まれていません。どうすれば生成できますか?
Abhinav 2016年

あなたはそれを生成しません。Linuxにはあります。コアダンプは別のLinucesの別の場所に保存されます-Googleの周り。Arch Linuxについては、こちらのwiki.archlinux.org/index.php/Core_dumpをお
Mawgはモニカを

7

セグメンテーション違反のデバッグに役立つツールがいくつかあります。お気に入りのツールをリストに追加したいと思います。AddressSanitizers(多くの場合、ASANと略される)です。

最新のコンパイラには便利な-fsanitize=addressフラグが付属しており、コンパイル時間と実行時のオーバーヘッドが追加され、エラーチェックが強化されます。

ドキュメントによるとこれらのチェックにはデフォルトでセグメンテーション違反のキャッチが含まれています。ここでの利点は、gdbの出力と同様のスタックトレースを取得できることですが、デバッガー内でプログラムを実行する必要はありません。例:

int main() {
  volatile int *ptr = (int*)0;
  *ptr = 0;
}
$ gcc -g -fsanitize=address main.c
$ ./a.out
AddressSanitizer:DEADLYSIGNAL
=================================================================
==4848==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5654348db1a0 bp 0x7ffc05e39240 sp 0x7ffc05e39230 T0)
==4848==The signal is caused by a WRITE memory access.
==4848==Hint: address points to the zero page.
    #0 0x5654348db19f in main /tmp/tmp.s3gwjqb8zT/main.c:3
    #1 0x7f0e5a052b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
    #2 0x5654348db099 in _start (/tmp/tmp.s3gwjqb8zT/a.out+0x1099)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /tmp/tmp.s3gwjqb8zT/main.c:3 in main
==4848==ABORTING

出力はgdbが出力するものより少し複雑ですが、次のような利点があります。

  • スタックトレースを受け取るために問題を再現する必要はありません。開発中にフラグを有効にするだけで十分です。

  • ASANは、単なるセグメンテーションフォールトだけではありません。そのメモリ領域がプロセスにアクセス可能であったとしても、多くの範囲外のアクセスがキャッチされます。


¹つまりクラン3.1以上GCC 4.8+


これは私にとって最も役に立ちます。1%の頻度でランダムに発生する非常に微妙なバグがあります。(16の主要なステップ。それぞれが異なるCまたはC ++バイナリによって実行される)を使用して、多数の入力ファイルを処理します。後の1つのステップでは、マルチスレッドが原因でランダムにのみセグメンテーションエラーがトリガーされます。デバッグするのは難しいです。このオプションは、デバッグ情報出力をトリガーしました。少なくとも、バグの場所を見つけるためのコードレビューの開始点となりました。
Kemin Zhou

2

コアダンプに関するルーカスの回答は適切です。私の.cshrcには次のものがあります:

alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'

'core'と入力してバックトレースを表示します。そして、日付スタンプは、私が正しいファイルを見ていることを確認するためです:(。

追加スタック破損のバグがある場合、コアダンプに適用されるバックトレースがガベージであることがよくあります。この場合、gdb内でプログラムを実行すると、受け入れられた回答に従って、より良い結果が得られます(障害が容易に再現可能であると想定)。また、コアを同時にダンプする複数のプロセスにも注意してください。一部のOSでは、コアファイルの名前にPIDを追加します。


4
ulimit -c unlimitedそもそもコアダンプを有効にすることを忘れないでください。
James Morris、

@ジェームズ:そうです。ルーカスはすでにこれについて述べました。そして、まだcshで立ち往生している私たちのために、「制限」を使用します。そして、私はCYGWINスタックダンプを読むことができませんでした(しかし、2、3年は試していません)。
ジョセフクインジー、

2

上記の答えはすべて正しく、推奨されています。この回答は、前述の方法のいずれも使用できない場合の最後の手段としてのみ意図されています。

他のすべてが失敗した場合fprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);は、コードの関連部分であると思われるもの全体に散らばったさまざまな一時的なデバッグ出力ステートメント(例:)を使用して、常にプログラムを再コンパイルできます。次に、プログラムを実行し、クラッシュが発生する直前に最後に出力されたデバッグプリントを確認します。プログラムがそこまで到達したことがわかっているため、その時点以降にクラッシュが発生したはずです。デバッグプリントを追加または削除し、コードを1行に絞り込むまで、テストを再コンパイルして再実行します。その時点で、バグを修正し、一時的なデバッグ出力をすべて削除できます。

かなり退屈ですが、どこにでも機能するという利点があります-何らかの理由でstdoutまたはstderrにアクセスできない場合、または修正しようとしているバグが競合である場合にのみ可能です。 -プログラムのタイミングが変わると動作が変わる条件(デバッグ出力はプログラムの速度を低下させ、そのタイミングを変えるため)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.