セグメンテーション違反の原因となるコードのどこに間違いがあるかをどのように判断しますか?
コンパイラー(gcc
)はプログラム内の障害の場所を表示できますか?
セグメンテーション違反の原因となるコードのどこに間違いがあるかをどのように判断しますか?
コンパイラー(gcc
)はプログラム内の障害の場所を表示できますか?
回答:
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が発生する場所は、一般に、コード内の「原因となる間違い」がどこにあるかについての手掛かりにすぎません。指定された場所は、必ずしも問題が存在する場所ではありません。
bt
省略形として使用しbacktrace
ます。
また、valgrind
試してみることができます:インストールvalgrind
して実行した場合
valgrind --leak-check=full <program>
次に、プログラムを実行し、segfaultのスタックトレース、および無効なメモリの読み取りまたは書き込みとメモリリークを表示します。とても便利です。
--leak-check=full
segfaultsのデバッグには役立ちません。メモリリークのデバッグにのみ役立ちます。
コアダンプを使用して、gdbで調べることもできます。有用な情報を取得するには、-g
フラグを付けてコンパイルする必要もあります。
メッセージを受け取ったときはいつでも:
Segmentation fault (core dumped)
コアファイルが現在のディレクトリに書き込まれます。そして、あなたはコマンドでそれを調べることができます
gdb your_program core_file
ファイルには、プログラムがクラッシュしたときのメモリの状態が含まれています。コアダンプは、ソフトウェアの展開中に役立ちます。
システムがコアダンプファイルのサイズをゼロに設定していないことを確認してください。あなたはそれを無制限に設定することができます:
ulimit -c unlimited
注意してください!コアダンプが巨大になる可能性があります。
セグメンテーション違反のデバッグに役立つツールがいくつかあります。お気に入りのツールをリストに追加したいと思います。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は、単なるセグメンテーションフォールトだけではありません。そのメモリ領域がプロセスにアクセス可能であったとしても、多くの範囲外のアクセスがキャッチされます。
コアダンプに関するルーカスの回答は適切です。私の.cshrcには次のものがあります:
alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'
'core'と入力してバックトレースを表示します。そして、日付スタンプは、私が正しいファイルを見ていることを確認するためです:(。
追加:スタック破損のバグがある場合、コアダンプに適用されるバックトレースがガベージであることがよくあります。この場合、gdb内でプログラムを実行すると、受け入れられた回答に従って、より良い結果が得られます(障害が容易に再現可能であると想定)。また、コアを同時にダンプする複数のプロセスにも注意してください。一部のOSでは、コアファイルの名前にPIDを追加します。
ulimit -c unlimited
そもそもコアダンプを有効にすることを忘れないでください。
上記の答えはすべて正しく、推奨されています。この回答は、前述の方法のいずれも使用できない場合の最後の手段としてのみ意図されています。
他のすべてが失敗した場合fprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);
は、コードの関連部分であると思われるもの全体に散らばったさまざまな一時的なデバッグ出力ステートメント(例:)を使用して、常にプログラムを再コンパイルできます。次に、プログラムを実行し、クラッシュが発生する直前に最後に出力されたデバッグプリントを確認します。プログラムがそこまで到達したことがわかっているため、その時点以降にクラッシュが発生したはずです。デバッグプリントを追加または削除し、コードを1行に絞り込むまで、テストを再コンパイルして再実行します。その時点で、バグを修正し、一時的なデバッグ出力をすべて削除できます。
かなり退屈ですが、どこにでも機能するという利点があります-何らかの理由でstdoutまたはstderrにアクセスできない場合、または修正しようとしているバグが競合である場合にのみ可能です。 -プログラムのタイミングが変わると動作が変わる条件(デバッグ出力はプログラムの速度を低下させ、そのタイミングを変えるため)