カーネルデバッグ

原作 &a.paul; and &a.joerg;

訳: &a.yoshiaki;. 2 November 1996. kgdbによるカーネルのクラッシュダンプのデバッグ

ここではクラッシュダンプ (crash dump : 訳注 この文脈では kernel 自身 の異常によって停止した場合に出力されるイメージを指します) によるカー ネルデバッグの方法を示します. ここではダンプするための十分なスワップ (swap) の容量があるものとし ます. もし複数のスワップパーティションを持ち, 最初のパーティションがダンプ を保持するのに十分な大きさを持たない場合は別のダンプデバイスを使うよ うに (config kernel 行で) カーネルのコンフィグをおこなうか, dumpon(8)コマンドを使って別のデバイスを示すことができます. スワップ をおこなわないデバイスへのダンプ, 例えばテープへのダンプは現在サポートさ れていません. カーネルのコンフィグは config -g によって行っ てください. には FreeBSDのカーネルの設定の詳細がありますので参照してください. dumpon(8)コマンドを使ってどこへダンプするかカーネルに伝えて ください(swapon(8)によってそのパーティションがスワップとして設定された 後でなければならないことに注意してください). これは普通は /etc/sysconfig/etc/rcで設定されます. あるいは 別の方法としてカーネルコンフィグレーションファイルの `config'行の `dump'節 で ダンプデバイスをハードコードすることができます. この方法はあまりよくは ありません. カーネルがブート時に crash する場合のクラッシュダンプを取り たい時だけ使うべきです. Note: 以下では `kgdb'という用語は gdbを カーネルデバッグモードで動かしていることを意味します. gdb-kオプションをつけて起動するか kgdbという名前でリン クして起動することでこのモードになります. デフォルトでは このリンク は作られていません. カーネルを作った時にそのコピーを kernel.debugという名前で作 りましょう. また, オリジナルに対して strip -dを実行します. オリジナルを普通にインストールします. また strip していないカーネル も同様にインストールすることができますが, シンボルテーブルの参照時間 がいくつかのプログラムでは劇的に増加するでしょう. また, カーネル全体 はブート時に読み込まれスワップアウトされないため数メガバイトの物理メ モリが無駄になります. 例えばブートプロンプトで新しいカーネルの名前をタイプすることによって, 新しいカーネルをテストした場合で, 再びシステムを動かすのに別のカーネ ルで立ち上げることが必要な場合はブートプロンプトで -sフラグ を使いシングルユーザの状態にしてください. そして以下のような操作をおこな います. fsck -p mount -a -t ufs # /var/crash 用のファイルシステムを書き込み可能にする savecore -N /kernel.panicked /var/crash exit # ...マルチユーザモードへ移行 ここに示した savecore(8)は (現在動いているものとは別の) カー ネルのシンボル名の抽出をおこなうために使っています. 抽出はデフォルトで は現在動いているカーネルに対しておこなわれ, クラッシュダンプとカーネルシンボ ルのくい違いのためにまったく何もしません (訳注:そのためにオプション で実際にダンプをおこしたカーネルを指定します). クラッシュダンプの起きた後に /sys/compile/WHATEVERへ行き kgdbを動かします. kgdb より次のようにします. symbol-file kernel.debug exec-file /var/crash/kernel.0 core-file /var/crash/vmcore.0 こうすると, クラッシュダンプを使ってカーネルソースを他のプログラムと同様に デバッグすることができます. 次に kgdb での手順のセッションのログを示します. 長い行は読 みやすくするために改行しました. また, 参照のために行番号を入れてあり ます. ただし, これは実際の pcvtコンソールドライバの開発中の実際のエ ラーのトレースです. 1:Script started on Fri Dec 30 23:15:22 1994 2:uriah # cd /sys/compile/URIAH 3:uriah # kgdb kernel /var/crash/vmcore.1 4:Reading symbol data from /usr/src/sys/compile/URIAH/kernel...done. 5:IdlePTD 1f3000 6:panic: because you said to! 7:current pcb at 1e3f70 8:Reading in symbols for ../../i386/i386/machdep.c...done. 9:(kgdb) where 10:#0 boot (arghowto=256) (../../i386/i386/machdep.c line 767) 11:#1 0xf0115159 in panic () 12:#2 0xf01955bd in diediedie () (../../i386/i386/machdep.c line 698) 13:#3 0xf010185e in db_fncall () 14:#4 0xf0101586 in db_command (-266509132, -266509516, -267381073) 15:#5 0xf0101711 in db_command_loop () 16:#6 0xf01040a0 in db_trap () 17:#7 0xf0192976 in kdb_trap (12, 0, -272630436, -266743723) 18:#8 0xf019d2eb in trap_fatal (...) 19:#9 0xf019ce60 in trap_pfault (...) 20:#10 0xf019cb2f in trap (...) 21:#11 0xf01932a1 in exception:calltrap () 22:#12 0xf0191503 in cnopen (...) 23:#13 0xf0132c34 in spec_open () 24:#14 0xf012d014 in vn_open () 25:#15 0xf012a183 in open () 26:#16 0xf019d4eb in syscall (...) 27:(kgdb) up 10 28:Reading in symbols for ../../i386/i386/trap.c...done. 29:#10 0xf019cb2f in trap (frame={tf_es = -260440048, tf_ds = 16, tf_\ 30:edi = 3072, tf_esi = -266445372, tf_ebp = -272630356, tf_isp = -27\ 31:2630396, tf_ebx = -266427884, tf_edx = 12, tf_ecx = -266427884, tf\ 32:_eax = 64772224, tf_trapno = 12, tf_err = -272695296, tf_eip = -26\ 33:6672343, tf_cs = -266469368, tf_eflags = 66066, tf_esp = 3072, tf_\ 34:ss = -266427884}) (../../i386/i386/trap.c line 283) 35:283 (void) trap_pfault(&frame, FALSE); 36:(kgdb) frame frame->tf_ebp frame->tf_eip 37:Reading in symbols for ../../i386/isa/pcvt/pcvt_drv.c...done. 38:#0 0xf01ae729 in pcopen (dev=3072, flag=3, mode=8192, p=(struct p\ 39:roc *) 0xf07c0c00) (../../i386/isa/pcvt/pcvt_drv.c line 403) 40:403 return ((*linesw[tp->t_line].l_open)(dev, tp)); 41:(kgdb) list 42:398 43:399 tp->t_state |= TS_CARR_ON; 44:400 tp->t_cflag |= CLOCAL; /* cannot be a modem (:-) */ 45:401 46:402 #if PCVT_NETBSD || (PCVT_FREEBSD >= 200) 47:403 return ((*linesw[tp->t_line].l_open)(dev, tp)); 48:404 #else 49:405 return ((*linesw[tp->t_line].l_open)(dev, tp, flag)); 50:406 #endif /* PCVT_NETBSD || (PCVT_FREEBSD >= 200) */ 51:407 } 52:(kgdb) print tp 53:Reading in symbols for ../../i386/i386/cons.c...done. 54:$1 = (struct tty *) 0x1bae 55:(kgdb) print tp->t_line 56:$2 = 1767990816 57:(kgdb) up 58:#1 0xf0191503 in cnopen (dev=0x00000000, flag=3, mode=8192, p=(st\ 59:ruct proc *) 0xf07c0c00) (../../i386/i386/cons.c line 126) 60: return ((*cdevsw[major(dev)].d_open)(dev, flag, mode, p)); 61:(kgdb) up 62:#2 0xf0132c34 in spec_open () 63:(kgdb) up 64:#3 0xf012d014 in vn_open () 65:(kgdb) up 66:#4 0xf012a183 in open () 67:(kgdb) up 68:#5 0xf019d4eb in syscall (frame={tf_es = 39, tf_ds = 39, tf_edi =\ 69: 2158592, tf_esi = 0, tf_ebp = -272638436, tf_isp = -272629788, tf\ 70:_ebx = 7086, tf_edx = 1, tf_ecx = 0, tf_eax = 5, tf_trapno = 582, \ 71:tf_err = 582, tf_eip = 75749, tf_cs = 31, tf_eflags = 582, tf_esp \ 72:= -272638456, tf_ss = 39}) (../../i386/i386/trap.c line 673) 73:673 error = (*callp->sy_call)(p, args, rval); 74:(kgdb) up 75:Initial frame selected; you cannot go up. 76:(kgdb) quit 77:uriah # exit 78:exit 79: 80:Script done on Fri Dec 30 23:18:04 1994 上の出力についてのコメントをします. trap()関数の位置で す. tp->t_lineはコンソールデバイスの規定 する行を参照しているので, もっと小さな整数でなければなりませ ん. ) 突然ダンプした場合の解析

カーネルが予想もしない時にコアダンプして config -g を行ってコンパイルされていなかった場合にはどうしたらよいでしょう. すべてが失われるわけではありません. パニックを起こさないでください. もちろん, クラッシュダンプを使えるようにする必要があります. 使い方は前述の部分を見てください. カーネルのコンパイルディレクトリで, (Makefileの) COPTFLAGS?=-Oとある行を編集します. -gオプショ ンをここに加えます(オプティマイズオプションのレベルは 変更しな いでください ). もし大まかにコードのどこで問題が起きているか (例 えば, 上の例では pcvtドライバ) わかっているのでしたら, その部 分のコードについてのすべてのオブジェクトファイルを消してください. カーネ ルを再構築しましょう. Makefileのタイムスタンプの変更により, 例えば trap.o などのいくつかの他のオブジェクトファイルも作り直さ れます. 少しの幸運があれば, -gオプションが追加されても作ら れるコードは変更されず, いくらかのデバッグシンボル以外には問題を 起こしたコードとそっくりな新しいカーネルを手に入れることができます. 少なくとも sizeコマンドで古い方と新しい方のサイズを比較すべ きです. これが食い違っていれば, 多分あきらめなければならないでしょう. ダンプを使って前述のように動かして調べます. デバッグシンボルは 必ずしも十分ではありません. 上の例ではスタックトレースでいくつかの関 数の行番号や引数リストが表示されないかもしれません. もしより多くのデ バッグシンボルが必要であれば,十分になるまで適切なオブジェクトファイ ルを消して (makeして) kgdbセッションを繰り返してください. これは必ずしもうまく動くと保証はできません. しかしほとんどの場合でう まくいくでしょう. DDBを使ったオンラインカーネルデバッグ

kgdb は非常に高レベルのユーザインタフェースを提 供するオフラインデバッガですが, いくつかのことはできません. (できないことの中で)極めて重要なことはカーネルコードへのブレークポイ ントの設定とシングルステップ実行です. カーネルの低レベルデバッグが必要であれば, DDBと呼ばれる on-lineデバッ ガが使えます. ブレークポイントの設定, シングルステップのカーネルの実 行, 変数の検査と変更などができます. ただし,これはカーネルのソースファ イルにアクセスすることはできません. kgdbのようにすべてのデ バッグ情報にはアクセスできず, globalと staticのシンボルにアクセス することができるだけです. カーネルに DDBを含めるためにはコンフィグファイルに次のようなオプショ ンを加えて, options DDB 再構築をおこないます. ( FreeBSDのカーネルの設定の詳細についてはを参照してくださ い. もしブートブロックが古いバージョンですと, デバッガのシンボルが完 全にはロードされないかもしれませんので注意してください. DDBシンボル がロードされるようにブートブロックを最新の物にアップデートしてくださ い) DDB カーネルの実行において, DDBに入るいくつかの方法があります. 最初 の, 最も早い方法はブートプロンプトが出ている時に-dのブート フラグをタイプすることです. カーネルはデバッグモードで起動し, デバ イスのプローブ以前に DDBに入ります. したがって, デバイスのプローブ/初期 設定ファンクションのデバッグができます. 2つ目のシナリオはキーボードのホットキーで, 通常は Ctrl-Alt-ESCです. syscons ではホットキーは再設定することができ, 配付されているいくつかの キーマッピングでは別のキーに再設定されていますので確認しておいてください. シリアルラインの BREAKを使って シリアルコンソールから DDBへ入ることを可 能にするオプションもあります (カーネルコンフィグレーションファイルの ``options BREAK_TO_DEBUGGER''). これは 多くのつまらないシリ アルアダプタが, 例えばケーブルを引き抜いた時に BREAK状態を意味もなく 作り出してしまうのでデフォルトでは無効になっています. 3つ目は, DDBを使うようになっているカーネルがパニック状態になると DDB へ入るというものです. このため, 無人運転するマシンのカーネルにDDBを 入れるのは賢明ではありません. DDBのコマンドはおおまかには gdb のいくつかのコマンドと似て います。おそらく最初にブレークポイントを設定する必要があるでしょう。 b function-name b address 数値はデフォルトでは16進数で, シンボル名とはまったく異ります. 16進数で a-f の文字で始まる場合は, 先頭に 0xをつける必要があります(それ以外の数字の場合はどちらでもか まいません). function-name + 0x103のような単純な式を使うこ とができます. 割り込みされたカーネルから処理を続行するためには, c とタイプするだけです. スタックのトレースには trace とします. DDB にホットキーで入った場合は, カーネルはその (ホットキーの) 割り込み の処理を行っていますのでスタックトレースはあまり役にたたないことに注 意してください. ブレークポイントを削除したい場合は, del del address-expression とします. 最初の形式はブレークポイントにヒットしたすぐ後で使うことが でき, 現在のブレークポイントを削除します. 2番目の形式では任意のブレー クポイントを削除することができますが, 次の形式で得られるような正確な アドレスを与えることが必要です. show b カーネルをシングルステップ実行させるには s としてみてください. これは関数呼出し先までステップ実行 (step into function) するでしょう. 次のステートメントが終了するまでのDDBトレースは n によっておこなうことができます. Note: これは gdb の `next' 命令とは異ります. gdbの `finish'命令と似ています. メモリ上のデータを調べるには (例として) 次のようにします. x/wx 0xf0133fe0,40 x/hd db_symtab_space x/bc termbuf,10 x/s stringbuf word/halfword/byte 単位でアクセスをおこない, hex (16進) /dec (10進) / char (文字) /string (文字列) で表示します. カンマの後ろの数字はオブジェク トカウントです. 次の 0x10個の要素を表示するには, 単純に x ,10 とします. 同様に次のように使うことができます. x/ia foofunc,10 foofuncの最初の 0x10個の命令語をディスアセンブルし, foofuncの先頭からのオフセットとともに表示します. メモリの内容を変更するには writeコマンドを使います. w/b termbuf 0xa 0xb 0 w/w 0xf0010030 0 0 コマンドモディファイアの (b/h/w) はデータを 書くサイズを定義し, これに続く最初の式は書き込むアドレス, 残りがこれ に続く連続するメモリアドレスに書き込まれるデータになります. 現在のレジスタ群の内容を知りたい場合は show reg とします. また, 単一のレジスタの値を表示するには, 例えば p $eax とします. また値の変更は set $eax new-value とします. DDBからカーネルの関数を呼び出す必要がある場合は, 単に call func(arg1, arg2, ...) とします. return 値が出力されます. 動いているプロセスの ps(1)スタイルの概要は ps です. カーネルの失敗の原因の調査が終わったらリブートすべきです. それまでの 不具合によりカーネルのすべての部分が期待するような動作をしているわけ ではないということを忘れないでください. 以下のうちいずれかの方法でシ ステムのシャットダウンおよびリブートを行ってください. call diediedie() カーネルをコアダンプしてリブートしますので, 後で kgdbによってコアの高 レベル解析をすることができます. このコマンドは通常 `continue'命令にエイリアスされています. `panic'にエイリアスされている call boot(0) は動いているシステムを `clean' に shut downするよい方法です. すべて のディスクを sync()して最後にリブートします. ディスクとカー ネルのファイルシステムインタフェースが破損していない限り, ほぼ完全 に `clean'にシャットダウンするよい方法でしょう. call cpu_reset() は大惨事を防ぐための最後の手段で「赤い大きなボタン」を押すのとほとんど 同じです.(訳注: リセットボタンを押すのとほぼ同じであるという意味です) 短いコマンドの要約は help をタイプします. ただし, デバッグセッションのために ddb(4) の マニュアルページのプリントアウトを用意しておくことを強くお奨めします. カーネルのシングルステップ中にオンラインマニュアルを読むことは難しい ということを覚えておいてください. コンソールドライバのデバッグ

DDBを動かすためにはコンソールドライバが必要ですから, コンソールドラ イバ自身に不具合のある場合は複雑になります. シリアルコンソールを利 用する方法 (ブートブロックを変更するか Boot:プロンプトで -hと入力する) を思い出してください. そして標準ター ミナルを最初のシリアルポートに設定します. DDBは, もちろんシリアルコ ンソールを含むいずれのコンソールドライバの設定でも動作します.