ここではクラッシュダンプ (crash dump : 訳注 この文脈では kernel 自身 の異常によって停止した場合に出力されるイメージを指します) によるカー ネルデバッグの方法を示します.
ここではダンプするための十分なスワップ (swap) の容量があるものとし
ます.
もし複数のスワップパーティションを持ち, 最初のパーティションがダンプ
を保持するのに十分な大きさを持たない場合は別のダンプデバイスを使うよ
うに (config kernel
行で) カーネルのコンフィグをおこなうか,
dumpon(8)コマンドを使って別のデバイスを示すことができます.
dumpon(8)を使うもっともよい方法は変数 dumpdev
を
/etc/rc.conf
で設定することです. 一般的には
/etc/fstab
で設定されているスワップデバイスが使われる
でしょう.
スワップ
に使えないデバイスへのダンプ, 例えばテープへのダンプは現在サポートさ
れていません. カーネルのコンフィグは config -g
によって行っ
てください.
FreeBSDカーネルのコンフィグレーション
には FreeBSDのカーネルの設定の詳細がありますので参照してください.
dumpon(8)
コマンドを使ってどこへダンプするかカーネルに伝えて
ください(swapon(8)によってそのパーティションがスワップとして設定された
後でなければならないことに注意してください). これは普通は
/etc/rc.conf
や /etc/rc
で設定されます. あるいは
別の方法としてカーネルコンフィグレーションファイルの
`config'行の `dump'節 で
ダンプデバイスをハードコードすることができます. この方法はあまりよくは
ありません. カーネルがブート時に crash する場合のクラッシュダンプを取り
たい時だけ使うべきです.
注: 以下では `kgdb
'という用語は gdb
を
カーネルデバッグモードで動かしていることを意味します. gdb
を
-k
オプションをつけて起動するか kgdb
という名前でリン
クして起動することでこのモードになります. デフォルトでは このリンク
は作られていません. また, このアイデアは GNU関係者たちが彼らのツール
を別の名前で呼び出した時に異なった動作をするということを好まない, と
いう点で不評です. あるいは将来この機能を廃止することになるかもしれません.
カーネルを作った時にそのコピーを 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
上の出力についてのコメントをします.
これは DDB (後述) からのダンプです. このため ``because you said to!'' という panicコメントがつき, ページフォルトのト ラップによって DDBに入ったことが原因の, やや長いスタックトレー スがあります.
スタックトレースでのこれは trap()
関数の位置で
す.
新しいスタックフレームの使用を指定しています. これは現 在は必要ありません. trapの場合ではスタックフレームは正 しい場所を指していると考えられます. (私は新しいコアダンプ を持っていません. 私のカーネルは長い間 panicを起こしていま せん.) ソースコードの 403行を見ると,``tp''ポインタのアク セスが失敗しているか配列のアクセスが範囲外である可能性が高 いことがわかります.
怪しいポインタですが, アクセスは正常におこなえました.
ところが, 明らかにポインタはゴミを指しています. これで
エラーを見つけました! (ここのコードの部分からはよくわかり
ませんが, tp->t_line
はコンソールデバイスの規定
する行を参照しているので, もっと小さな整数でなければなりませ
ん. )