Debugger ( gdb ) の 使 い 方 プログラムを 走 らせたとき #./AnaEN inputfile gomi.hbook data.dat Segmentation fault (core dumped) # となって コアダンプしたときほどいやなことはないと 思 います 短 いプログラムならソースファイルを 見 て あるいは ところどころに printf を 埋 め 込 んで( printf デバッグ ) 原 因 が 突 き 止 められるかもしれません しかし プログラムの 規 模 が 大 きくなると printf デバッグでは 効 率 が 悪 すぎます また printf を call すると 最 適 化 の 状 態 が 変 わり 正 常 に 動 く 場 合 があります デバッガを 使 うと コアダンプした 箇 所 がわかります また 任 意 の 場 所 で 実 行 を 止 めて (ブレークポイントとい う)その 状 態 での 変 数 の 値 を 見 ることができます gcc では 最 適 化 を 有 効 にしかつ デバッグ 用 のコードを 生 成 できます こ こでは gdb の 使 い 方 を 示 します 1. Debug 用 コードの 生 成 デバッグ 用 コードを 生 成 するには コンパイル 時 に -g オプションをつけてコンパイルします Makefile に 次 のように 書 い ておくとよい DEBUGFLAG = -g CC = gcc C++ = g++ CFLAGS = -O $(DEBUGFLAG).c.o:.cpp.o: $(CC) -c $(CFLAGS) $< $(C++) -c $(CFLAGS) $< これで Debug 用 の 情 報 が 埋 め 込 まれた 実 行 ファイルが 生 成 される このプログラムはオプションなしで 生 成 したものに 比 べ サイズが 大 きくなっており 実 行 時 パフォーマンスは 低 下 するが 通 常 はつけてコンパイルすることを 進 める 2. プログラムを 実 行 して コアダンプしたら プログラム 実 行 中 に 処 理 できない 例 外 が 発 生 すると プロセスはカーネルによりアボートされます アボート 時 には 通 常 そのときのプロセスイメージ(メモリ プロセスコンテキストなど)を core というファイルに 出 力 します これをコアダンプとい い デバッガを 使 って 解 析 する 事 により 例 外 発 生 時 の 状 態 が 調 べられます core ファイルは シェル 変 数 coredumpsize が 0 に 設 定 されていると 生 成 されません また 最 近 の RH8.0 では core.xxx (xxx はプロセス ID) というファイル 名 で 生 成 されます さて 上 の 例 でどこに 問 題 があったか 調 べてみましょう # gdb./anaen core とすると core ファイルを 読 み 込 み 例 外 が 起 きた 状 態 になる GNU gdb 5.2.1 Copyright 2002 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain condistions. Type "show copying" to see the conditions. Three is absolutely no warranty for GDB. The "show waranty" for details. This GDB was configured as "i686-pc-linux-gnu" Core was generated by `./AnaEN inputfile gomi.hbook..` (1) Program terminated with signal 11, Segmentation fault. (2) Reading symbols from /cern/geant4/lib/libg4digits+hits.sodone. Loading symbols for /lib/ld-linux.so.2
#0 0x0804d551 in AnalysEN::ReadParam(_ID_FILE *) (this=0x24ad4e58, param=0x24ad4e68 ) at AnalysEnergy.cpp:69 69 par->c0[i]=c0/1000.0; と ライセンス 表 示 の 後 "./AnaEN inputfile gomi.hbook data.dat" というプログラムの 実 行 で core が 生 成 されたこと((1)) Signal 11 の "Segmentation fault" が 例 外 であったこと((2)) の 表 示 のあと 共 有 ライブラリ 中 のシンボルテーブルを 読 み 込 み 例 外 の 起 きたコードのアドレス(0x0804d551)でこれは AnalysEN::ReadParam 関 数 の par-.>c0[i]=c0/1000.0; の 行 ( AnalysEnergy.cpp の 69 行 目 )であることを 示 している また この 関 数 の 実 引 数 も 示 されている この 部 分 のソースファイ ルを 見 ると /* AnalysEnergy.h */ #include "HBook.h" struct POS; struct PARAM; class AnalysEN public: AnalysEN(); ~AnalysEN(); bool ReadParam( FILE *param ); private: POS *position; PARAM *par; ; /* AnalysEnergy.cpp */ #include "AnalysEnergy.h" struct PARAM double C0[2]; double C1[2]; double C2[2]; double C3[2]; double C4[2]; ; bool AnalysEN::ReadParam( FILE *param ) std::cout << "Read parameter " << std::endl; char str[144]; double c0, c1, c2, c3, c4; for( int i=0; i<2; ++i ) if( fgets( str, 144, param )!=0 ) sscanf( str, "%lf %lf %lf %lf %lf", &c0, &c1, &c2, &c3, %c4 ); par->c0[i]=c0/1000.0;
par->c1[i]=c1/1000.0; par->c2[i]=c2/1000.0; par->c3[i]=c3/1000.0; par->c4[i]=c4/1000.0; else return false; return true; Segmentation fault は 大 抵 存 在 しないアドレスへのアクセスをしたときに 発 生 する たとえば 1) 配 列 の 境 界 を 越 えたア クセス(アドレスが 存 在 すれば そのときは 問 題 が 起 きないが 別 の 変 数 を 書 き 換 えたときは 発 見 しづらいバグとなる ) 2)オブジェクトを 指 していないポインタ 経 由 でのアクセス( 何 が 起 こるかわからない) である まず 1)でないか 変 数 i の 値 を 調 べてみる $1 = 0 となり i の 値 は 0 であることがわかり 1)ではない par->c0[i] はどうだろうか? print par->c0[i] Cannot access memory at address 0x0 アドレスが 存 在 しない 様 に 見 える さらに print par $2 = (PARAM *) 0x0 となり ポインタ par は 何 も 指 していないヌルポインタであるとわかる これが 原 因 であった コンストラクタのソースを 見 る と AnalysEN::AnalysEN() std::cout << "AnalysEN::Constructor " << std::endl; とあって ポインタ position と par が(オブジェクトを 指 すように) 初 期 化 されていない 次 の 様 に 書 き 換 える 同 時 にデ ストラクタでは コンストラクタで 確 保 したメモリを 解 放 しないとメモリリーク(これもデバッグしづらい 深 刻 なバグである )にな るので 修 正 する AnalysEN::AnalysEN() : position( new POS ), par( new PARAM ) std::cout << "AnalysEN::Constructor " << std::endl; AnalysEN::~AnalysEN() if(position) delete position; if(par) delete par; この 修 正 で この 場 合 はうまく 動 くようになった 大 規 模 プログラムでは 問 題 の 部 分 がどこにあるかを 調 べるために プログラムの 開 始 からそこにたどりつくまでの 構 造 が わかった 方 がよい 場 合 がある そのときは bt #0 0x0804d551 in AnalysEN::ReadParam(_ID_FILE *) (this=0x24ad4e59, param=0x24ad4e68) at AnalysEnergy.cpp:69 #1 0x0804d27d in main (argc=0, argv=0x50) at Main.cpp:100 #2 0x416c8316 in libc_start_main(main=0x804d1a6 <main>, argc=4, ubp_av=0xbffffb3c, init=0x804c0d8 <_init>, fini=0x80a768c <_fini>,
rtld_fini=0x4000d2fc <_dl_fini>, stack_end=0xbffffb2c) at../sysdeps/generic/libc-start.c:129 のように スタートアップコードまで プログラムスタックをさかのぼって 表 示 してくれる 様 々な 関 数 から 呼 ばれる 関 数 では どこから 呼 ばれているのか?こうして 調 べられる Geant4 の G4Exception は コアダンプして プログラムを 終 了 する 関 数 であるが コアダンプの 原 因 はこの 関 数 にあるのではない Geant4 では 計 算 不 能 な 状 態 を 検 知 すると G4Exception を 呼 ぶ 様 にかかれているので bt ( Back Trace )で G4Exception を 呼 んだ 関 数 を 調 べるとよい ただし 例 外 が 起 きた 原 因 は その 関 数 にあるわけではない( 例 外 状 態 を 検 知 しただけ)ので 注 意 すること 3. gdb の 中 でプログラムを 実 行 する コアダンプしないけどプログラムの 挙 動 がおかしい コアダンプしたところ 以 前 に 問 題 がある などの 場 合 gdb の 中 で(1 文 1 文 ) 実 行 し 変 数 を 調 べられます そのためには プログラム 中 に Breakpoint というものを 設 定 します Breakpoint で gdb はプログラムの 実 行 を 一 時 中 断 します 以 下 にコマンドを 示 します * run [Command Arguement] プログラムを 実 行 ( 最 初 から)する Command Argument がある 場 合 は 与 える * break Filename:Line Breakpoint の 設 定 ソースファイルと 行 番 号 を 与 える * c 実 行 の 再 開 中 断 したところから 再 開 します ( 次 の Breakpoint で 止 まる ) * next または step ステップ 実 行 1 行 を 実 行 して 止 まる 実 行 中 断 中 は 上 で 述 べた print コマンドで 変 数 の 値 などを 調 べられます 以 下 に 実 行 例 を 示 します ( bool AnalysEN::ReadParam( FILE *param ) は AnalysEnergy.cpp の59 行 目 ) # gdb./anaen GNU gdb 5.2.1 This GDB was configured as "i686-pc-linux-gnu" break AnalysEnergy.cpp:59 Breakpoint 1 at 0x804d4e4: file AnalysEnergy.cpp, line 59. run inputfile gomi.hbook data.dat Starting program: /users/takahasi/nks/analyzer/enecal/anaen inputfile gomi.hbook data.dat Breakpoint 1, AnalysEN::ReadParam(_ID_FILE*) (this=0xbfffa3fc, param=0xbfffa40) at AnalysEnergy.cpp:60 60 (gbd) next AnalysEN::ReadParam(_ID_FILE*) (this=0x24ad4e98, param=0x24ad4f50) at AnalysEnergy.cpp:61 61 std::cout << "Read parameter " << std::endl; Read parameter 65 for(int i=0; i<2; ++i) $1 = 1 66 if( fgets( str, 144, param )!=0 ) 65 for(int i=0; i<2; ++i ) $2 = 0 69 par->c0[i]=c0/1000.0; $3 = 0 c
Program exited normally. 何 か 実 行 順 序 が 変 な 気 がするが 最 適 化 のためでしょう (そうなのか? 最 適 化 をはずしてみればわかる) また 実 行 文 でない 行 はとばされていることもわかります 4.おまけ 行 番 号 を 調 べるとき 行 番 号 表 示 付 きのエディタを 使 えればよいのだが ない 場 合 は 次 のようにする # cat -n AnalysEnergy.cpp less 59 bool AnalysEN::ReadParam( FILE *param) 60 61 std::cout << "Real parameter " << std::endl; 62 char str[144]: 上 の 例 で 最 適 化 をはずした 場 合 Read parameter 65 for(int i=0; i<2; ++i ) $1 = 1073830040 <- 変 数 が 未 定 義 (for 文 の 前 だから) 66 if( fgets( str, 144, param )!=0 ) 67 sscanf( str, "%lf %lf %lf %lf %lf", となり 最 適 化 の 有 無 によって for ループの 実 行 に 違 いがあることがわかる