
「core dump」したらお手上げだぁ?
|
突然ですが、みなさんはC言語意外でも何らかの言語でプログラミングを
したことはありますか?
私は、プログラミング自体に興味があったので、C言語意外にも Perlや Javascriptなどに手を出した経験が あります(もちろんFORTRANや Basicなどもやったことはありますが…) 個人的な意見になってしまいますが、私は Perlや Javascriptなどがあまり好きではありません (Perl や Javascript 使いの方、ゴメンなちゃい!) どうしてかというと、これらの言語にはコンパイルというものが存在 しないからです。コンパイルが存在しないと何が問題か?というと、 具体的にどんなエラーかが分かれば良いのですが、エラーが発生して うまく実行できなかったことは分かるのですが、実際にどんなエラー なのかが分からない場合が多いのです。 一方、C言語にはコンパイルというものが 必ずついてきますので、実行する前にエラーがあることが分かりますし、 最低でもソース中のどの部分でエラーがあるのかを知ることができます。 でも、コンパイルはうまく通ったのに、 いざ実行してみると とか とかいう悲しいメッセージが出たことはありませんか? これが結構やっかいなのです。 ある程度プログラムが走ってから止まればまだしも、なんのアクションも 示さないで単に上記のエラーが出ると、 結局、どの部分に問題があるのか? が分からないのです。ある程度の経験を積めば大体の位置は予想できるの ですが、初心者の頃は全く分からなかったりして、泣きたい気分で いっぱいになってしまうのです。 だから、コンパイルエラーばかりで イライラしている方、 このページでは、「core dump」してしまった際の デバッグの手法について説明します。 特にデバッグツールとして有名な 「gdb」の簡単すぎる使い方 について説明します。 |
|
「cat コマンドを作ってみよう!」で
紹介したプログラムに似たものを作ってみましょう!
以下に示すプログラムを作成してみてください |
1: #include <stdio.h>
2:
3: int main( int argc, char *argv[] )
4: {
5: FILE *fpr;
6: char buf[BUFSIZ];
7:
8: if( argc < 2 ){
9: /* もしファイル名が指定されなかったらプログラムを終了する */
10: return( -1 );
11: }
12:
13: if(( fpr = fopen( argv[1] , "r" )) != NULL ){
14: /*指定したファイルがオープンできれば*/
15: /*ファイルの内容を表示する*/
16: while( fgets( buf, sizeof( buf ), fpr ) != NULL ){
17: fprintf( stdout, "%s", buf );
18: }
19: }else{
20: /*指定したファイルがオープンできなければ*/
21: /*何もしない*/
22: }
23:
24: /*ファイルを閉じる*/
25: fclose( fpr );
26:
27: return ( 0 );
28: }
|
|
このプログラムは単に
引数として指定されたファイルを表示する
だけのプログラムです。
コンパイルは としてコンパイルしてくだい。この「-g」 オプションは「デバッグ情報を付ける」 というオプションです。 コンパイルに成功したら、実際に実行してみましょう! とりあえず、このプログラムのソースを引数として与えて 実行してみましょう! |
> gdbcore gdbcore.c
#include <stdio.h>
int main( int argc, char *argv[] )
{
FILE *fpr;
char buf[BUFSIZ];
if( argc < 2 ){
/* もしファイル名が指定されなかったらプログラムを終了する */
return( -1 );
}
if(( fpr = fopen( argv[1] , "r" )) != NULL ){
/*指定したファイルがオープンできれば*/
/*ファイルの内容を表示する*/
while( fgets( buf, sizeof( buf ), fpr ) != NULL ){
fprintf( stdout, "%s", buf );
}
}else{
/*指定したファイルがオープンできなければ*/
/*何もしない*/
}
/*ファイルを閉じる*/
fclose( fpr );
return ( 0 );
}
>
|
|
正常に動きましたね?
それでは、今度は存在しないファイルを引数として指定して実行して みましょう! 例として「ls.txt」を引数として 指定してみましょう。ファイル名はどんなものでも構いませんが、 存在しないファイルを指定 してください。 |
|
> gdbcore ls.txt セグメントエラー (coreを出力しました) > |
|
core dump してしまいましたね。エラーメッセージはお使いの環境によって、
英語で表示されているかもしれません。
コンパイルはうまく通ったのに、正常に実行されませんでした。ソース中に 問題点があることは予想できますが、どこに問題があるのでしょうか? 一見すると問題点はなさそうなんですけどねぇー。まあ、経験のある プログラマーの方だったら容易に問題点を発見できるでしょう。 |
|
その1 「fprintf」を使う もしどこに問題があるのかに見当がつかなければデバッグをして 問題のある部分を探しださなければなりません。 デバッグの手法としてはいろいろ考えられますが、まず最初に考えられるのは 「fprintf」関数を使う方法です。 これは、一番簡単で分かりやすい方法です。具体的には適当な位置に この作業を繰り返し、最終的に問題のある記述を探しだすわけです。 では、実際にやってみましょう! まず、どこでも良いのですが大体の見当をつけます。とりあえず 変数を宣言した段階でエラーが発生していないか? もしくは 引数を処理している段階で問題が発生していないか?を確認するために、 上に示した ソース中の12行目 に「fprintf( stderr, "Check\n" );」 という記述を挿入します。 先ほどと同じようにコンパイルして実行してみてください。多分 下のような感じになると思います。
「Check」という文字が画面に表示 されましたね? このことから、 ソース中の 1〜12行目に問題はない ということになります。 今度は先ほど挿入した記述を23行目 に変えてみてください。 実際に実行すると、先ほどと同じように 「Check」という文字が表示されます。 今度は26行目に記述してみてください。 実行すると
「Check」という文字が 表示されなくなりました。このことから 問題点は23〜26行目にあるという ことになります。実際には25行目の という記述に問題があります。 |
|
その2 デバッグツール「gdb」を使う 次の方法として、デバッグツールである「gdb」 を使う方法について説明しましょう。私も gdb を完全に使いきっている わけではないのですが、ここでは簡単にコア・ファイルの解析方法を 紹介し、実際にどの部分でコア・ダンプしたのかを調べます。 では実際に「gdb」を動かしてみましょう。 gdb には引数として実行ファイルとコアファイルを指定します。 このように指定します。今回の「gdbcore」という実行ファイルに関しては 以下のようにします。
上記のように、あーだ・こーだといろいろなメッセージが出た後 「(gdb)」というプロンプト(?)が でます。ここで「where」コマンドを打って みましょう。
上のようなメッセージが出たと思います。全ての中身を知ろうとすると 大変ですが、青 で示した部分に注目してください。 まず「gdbcore.c:25」 というメッセージが出ています。これは、ソースファイル 「gdbcore.c」中の 25行目で fclose()関数を呼び出し、この関数を 実行した際に問題が発生したということを示しています。 先ほどのfprintfを使う方法と同じく、 25行目の fprintfで問題があることが分かりましたね? |
|
問題のある部分を探し出す方法については理解できたでしょうか? ソフトウェアの作成途中は要所要所で 「fprintf」を挿入して、常に正常に 動いているかを確認する癖をつけた方が後々便利です。 「fprintf」の方法でうまく探せ出せなかった 場合に、「gdb」を使って問題点を修正すれば 完璧です。 まあ、それは皆さんで考えて欲しいところなのですが、今回だけ特別に 解説しましょう。ソース中 25行目の fcloseは、ファイルオープンに失敗しても 実行されます。この場合 fclose 関数に渡される FILE型変数 fpr には NULLポインタが入っていますので、fclose はNULLポインタを閉じようと します。そんなことはできませんので、プログラムが飛んでしまったわけ です。 したがって 25行目の fclose関数は本来ならば18行目と19行目の間に 挿入されるべきなのです。 |