HardenedBSD/share/doc/ja_JP.EUC/handbook/kerneldebug.sgml
Masafumi Max NAKANE a785ce7ee3 Bump the revision number in the Original revision: line by one iafter
all $Id$ changed to $FreeBSD$.
1997-01-15 13:16:51 +00:00

420 lines
17 KiB
Plaintext

<!-- $FreeBSD$ -->
<!-- The FreeBSD Japanese Documentation Project -->
<!-- Original revision: 1.11 -->
<chapt><heading>カーネルデバッグ<label id="kerneldebug"></heading>
<p><em>原作 &a.paul; and &a.joerg;</em>
<p><em>訳: &a.yoshiaki;. <newline>
2 November 1996. </em>
<sect><heading>kgdbによるカーネルのクラッシュダンプのデバッグ</heading>
<p>ここではクラッシュダンプ (crash dump : 訳注 この文脈では kernel 自身
の異常によって停止した場合に出力されるイメージを指します) によるカー
ネルデバッグの方法を示します.
ここではダンプするための十分なスワップ (swap) の容量があるものとし
ます.
もし複数のスワップパーティションを持ち, 最初のパーティションがダンプ
を保持するのに十分な大きさを持たない場合は別のダンプデバイスを使うよ
うに (<tt>config kernel</tt> 行で) カーネルのコンフィグをおこなうか,
dumpon(8)コマンドを使って別のデバイスを示すことができます. スワップ
をおこなわないデバイスへのダンプ, 例えばテープへのダンプは現在サポートさ
れていません. カーネルのコンフィグは <tt>config -g</tt> によって行っ
てください.
<ref id="kernelconfig" name="FreeBSDカーネルのコンフィグレーション">
には FreeBSDのカーネルの設定の詳細がありますので参照してください.
<tt>dumpon(8)</tt>コマンドを使ってどこへダンプするかカーネルに伝えて
ください(swapon(8)によってそのパーティションがスワップとして設定された
後でなければならないことに注意してください). これは普通は
<tt>/etc/sysconfig</tt> や <tt>/etc/rc</tt>で設定されます. あるいは
別の方法としてカーネルコンフィグレーションファイルの `config'行の `dump'節 で
ダンプデバイスをハードコードすることができます. この方法はあまりよくは
ありません. カーネルがブート時に crash する場合のクラッシュダンプを取り
たい時だけ使うべきです.
<em><bf>Note:</bf> 以下では `<tt>kgdb</tt>'という用語は <tt>gdb</tt>を
カーネルデバッグモードで動かしていることを意味します. <tt>gdb</tt>を
<tt>-k</tt>オプションをつけて起動するか <tt>kgdb</tt>という名前でリン
クして起動することでこのモードになります. デフォルトでは このリンク
は作られていません.</em>
カーネルを作った時にそのコピーを <tt>kernel.debug</tt>という名前で作
りましょう. また, オリジナルに対して <tt>strip -d</tt>を実行します.
オリジナルを普通にインストールします. また strip していないカーネル
も同様にインストールすることができますが, シンボルテーブルの参照時間
がいくつかのプログラムでは劇的に増加するでしょう. また, カーネル全体
はブート時に読み込まれスワップアウトされないため数メガバイトの物理メ
モリが無駄になります.
例えばブートプロンプトで新しいカーネルの名前をタイプすることによって,
新しいカーネルをテストした場合で, 再びシステムを動かすのに別のカーネ
ルで立ち上げることが必要な場合はブートプロンプトで <tt>-s</tt>フラグ
を使いシングルユーザの状態にしてください. そして以下のような操作をおこな
います.
<tscreen><verb>
fsck -p
mount -a -t ufs # /var/crash 用のファイルシステムを書き込み可能にする
savecore -N /kernel.panicked /var/crash
exit # ...マルチユーザモードへ移行
</verb></tscreen>
ここに示した <tt>savecore(8)</tt>は (現在動いているものとは別の) カー
ネルのシンボル名の抽出をおこなうために使っています. 抽出はデフォルトで
は現在動いているカーネルに対しておこなわれ, クラッシュダンプとカーネルシンボ
ルのくい違いのためにまったく何もしません (訳注:そのためにオプション
で実際にダンプをおこしたカーネルを指定します).
クラッシュダンプの起きた後に <tt>/sys/compile/WHATEVER</tt>へ行き
<tt>kgdb</tt>を動かします. <tt>kgdb</tt> より次のようにします.
<tscreen><verb>
symbol-file kernel.debug
exec-file /var/crash/kernel.0
core-file /var/crash/vmcore.0
</verb></tscreen>
こうすると, クラッシュダンプを使ってカーネルソースを他のプログラムと同様に
デバッグすることができます.
次に <tt>kgdb</tt> での手順のセッションのログを示します. 長い行は読
みやすくするために改行しました. また, 参照のために行番号を入れてあり
ます. ただし, これは実際の pcvtコンソールドライバの開発中の実際のエ
ラーのトレースです.
<tscreen><verb>
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(&amp;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
</verb></tscreen>
上の出力についてのコメントをします.
<descrip>
<tag/line 6:/ これは DDB (後述) からのダンプです. このため ``because you
said to!'' という panicコメントがつき, ページフォルトのト
ラップによって DDBに入ったことが原因の, やや長いスタックトレー
スがあります.
<tag/line 20:/ スタックトレースでのこれは <tt>trap()</tt>関数の位置で
す.
<tag/line 36:/ 新しいスタックフレームの使用を指定しています. これは現
在は必要ありません. trapの場合ではスタックフレームは正
しい場所を指していると考えられます. (私は新しいコアダンプ
を持っていません. 私のカーネルは長い間 panicを起こしていま
せん.) ソースコードの 403行を見ると,``tp''ポインタのアク
セスが失敗しているか配列のアクセスが範囲外である可能性が高
いことがわかります.
<tag/line 52:/ 怪しいポインタですが, アクセスは正常におこなえました.
<tag/line 56:/ ところが, 明らかにポインタはゴミを指しています. これで
エラーを見つけました! (ここのコードの部分からはよくわかり
ませんが, <tt>tp-&gt;t_line</tt>はコンソールデバイスの規定
する行を参照しているので, もっと小さな整数でなければなりませ
ん. )
</descrip>
<sect><heading>突然ダンプした場合の解析</heading>
<p>カーネルが予想もしない時にコアダンプして <tt>config -g</tt>
を行ってコンパイルされていなかった場合にはどうしたらよいでしょう.
すべてが失われるわけではありません. パニックを起こさないでください.
もちろん, クラッシュダンプを使えるようにする必要があります.
使い方は前述の部分を見てください.
カーネルのコンパイルディレクトリで, (Makefileの)
<tt>COPTFLAGS?=-O</tt>とある行を編集します. <tt>-g</tt>オプショ
ンをここに加えます(オプティマイズオプションのレベルは <em>変更しな
いでください</em> ). もし大まかにコードのどこで問題が起きているか (例
えば, 上の例では <tt>pcvt</tt>ドライバ) わかっているのでしたら, その部
分のコードについてのすべてのオブジェクトファイルを消してください. カーネ
ルを再構築しましょう. Makefileのタイムスタンプの変更により, 例えば
<tt> trap.o </tt>などのいくつかの他のオブジェクトファイルも作り直さ
れます. 少しの幸運があれば, <tt>-g</tt>オプションが追加されても作ら
れるコードは変更されず, いくらかのデバッグシンボル以外には問題を
起こしたコードとそっくりな新しいカーネルを手に入れることができます.
少なくとも <tt>size</tt>コマンドで古い方と新しい方のサイズを比較すべ
きです. これが食い違っていれば, 多分あきらめなければならないでしょう.
ダンプを使って前述のように動かして調べます. デバッグシンボルは
必ずしも十分ではありません. 上の例ではスタックトレースでいくつかの関
数の行番号や引数リストが表示されないかもしれません. もしより多くのデ
バッグシンボルが必要であれば,十分になるまで適切なオブジェクトファイ
ルを消して (makeして) <tt>kgdb</tt>セッションを繰り返してください.
これは必ずしもうまく動くと保証はできません. しかしほとんどの場合でう
まくいくでしょう.
<sect><heading>DDBを使ったオンラインカーネルデバッグ</heading>
<p> <tt>kgdb</tt> は非常に高レベルのユーザインタフェースを提
供するオフラインデバッガですが, いくつかのことはできません.
(できないことの中で)極めて重要なことはカーネルコードへのブレークポイ
ントの設定とシングルステップ実行です.
カーネルの低レベルデバッグが必要であれば, DDBと呼ばれる on-lineデバッ
ガが使えます. ブレークポイントの設定, シングルステップのカーネルの実
行, 変数の検査と変更などができます. ただし,これはカーネルのソースファ
イルにアクセスすることはできません. <tt>kgdb</tt>のようにすべてのデ
バッグ情報にはアクセスできず, globalと staticのシンボルにアクセス
することができるだけです.
カーネルに DDBを含めるためにはコンフィグファイルに次のようなオプショ
ンを加えて,
<tscreen><verb>
options DDB
</verb></tscreen>
再構築をおこないます. ( FreeBSDのカーネルの設定の詳細については<ref
id="kernelconfig" name="FreeBSDカーネルのコンフィグレーション">を参照してくださ
い. もしブートブロックが古いバージョンですと, デバッガのシンボルが完
全にはロードされないかもしれませんので注意してください. DDBシンボル
がロードされるようにブートブロックを最新の物にアップデートしてくださ
い)
DDB カーネルの実行において, DDBに入るいくつかの方法があります. 最初
の, 最も早い方法はブートプロンプトが出ている時に<tt>-d</tt>のブート
フラグをタイプすることです. カーネルはデバッグモードで起動し, デバ
イスのプローブ以前に DDBに入ります. したがって, デバイスのプローブ/初期
設定ファンクションのデバッグができます.
2つ目のシナリオはキーボードのホットキーで, 通常は Ctrl-Alt-ESCです.
syscons ではホットキーは再設定することができ, 配付されているいくつかの
キーマッピングでは別のキーに再設定されていますので確認しておいてください.
シリアルラインの BREAKを使って シリアルコンソールから DDBへ入ることを可
能にするオプションもあります (カーネルコンフィグレーションファイルの
``<tt>options BREAK_TO_DEBUGGER</tt>''). これは 多くのつまらないシリ
アルアダプタが, 例えばケーブルを引き抜いた時に BREAK状態を意味もなく
作り出してしまうのでデフォルトでは無効になっています.
3つ目は, DDBを使うようになっているカーネルがパニック状態になると DDB
へ入るというものです. このため, 無人運転するマシンのカーネルにDDBを
入れるのは賢明ではありません.
DDBのコマンドはおおまかには <tt>gdb</tt> のいくつかのコマンドと似て
います。おそらく最初にブレークポイントを設定する必要があるでしょう。
<tscreen><verb>
b function-name
b address
</verb></tscreen>
数値はデフォルトでは16進数で, シンボル名とはまったく異ります. 16進数で
<tt>a</tt>-<tt>f</tt> の文字で始まる場合は, 先頭に
<tt>0x</tt>をつける必要があります(それ以外の数字の場合はどちらでもか
まいません). <tt>function-name + 0x103</tt>のような単純な式を使うこ
とができます.
割り込みされたカーネルから処理を続行するためには,
<tscreen><verb>
c
</verb></tscreen>
とタイプするだけです.
スタックのトレースには
<tscreen><verb>
trace
</verb></tscreen>
とします.
DDB にホットキーで入った場合は, カーネルはその (ホットキーの) 割り込み
の処理を行っていますのでスタックトレースはあまり役にたたないことに注
意してください.
ブレークポイントを削除したい場合は,
<tscreen><verb>
del
del address-expression
</verb></tscreen>
とします. 最初の形式はブレークポイントにヒットしたすぐ後で使うことが
でき, 現在のブレークポイントを削除します. 2番目の形式では任意のブレー
クポイントを削除することができますが, 次の形式で得られるような正確な
アドレスを与えることが必要です.
<tscreen><verb>
show b
</verb></tscreen>
カーネルをシングルステップ実行させるには
<tscreen><verb>
s
</verb></tscreen>
としてみてください. これは関数呼出し先までステップ実行 (step into
function) するでしょう. 次のステートメントが終了するまでのDDBトレースは
<tscreen><verb>
n
</verb></tscreen>
によっておこなうことができます.
<bf>Note:</bf> これは <tt>gdb</tt> の `next' 命令とは異ります.
<tt>gdb</tt>の `finish'命令と似ています.
メモリ上のデータを調べるには (例として) 次のようにします.
<tscreen><verb>
x/wx 0xf0133fe0,40
x/hd db_symtab_space
x/bc termbuf,10
x/s stringbuf
</verb></tscreen>
word/halfword/byte 単位でアクセスをおこない, hex (16進) /dec (10進) /
char (文字) /string (文字列) で表示します. カンマの後ろの数字はオブジェク
トカウントです. 次の 0x10個の要素を表示するには, 単純に
<tscreen><verb>
x ,10
</verb></tscreen>
とします. 同様に次のように使うことができます.
<tscreen><verb>
x/ia foofunc,10
</verb></tscreen>
<tt>foofunc</tt>の最初の 0x10個の命令語をディスアセンブルし,
<tt>foofunc</tt>の先頭からのオフセットとともに表示します.
メモリの内容を変更するには writeコマンドを使います.
<tscreen><verb>
w/b termbuf 0xa 0xb 0
w/w 0xf0010030 0 0
</verb></tscreen>
コマンドモディファイアの (<tt>b</tt>/<tt>h</tt>/<tt>w</tt>) はデータを
書くサイズを定義し, これに続く最初の式は書き込むアドレス, 残りがこれ
に続く連続するメモリアドレスに書き込まれるデータになります.
現在のレジスタ群の内容を知りたい場合は
<tscreen><verb>
show reg
</verb></tscreen>
とします. また, 単一のレジスタの値を表示するには, 例えば
<tscreen><verb>
p $eax
</verb></tscreen>
とします. また値の変更は
<tscreen><verb>
set $eax new-value
</verb></tscreen>
とします.
DDBからカーネルの関数を呼び出す必要がある場合は, 単に
<tscreen><verb>
call func(arg1, arg2, ...)
</verb></tscreen>
とします. return 値が出力されます.
動いているプロセスの <tt>ps(1)</tt>スタイルの概要は
<tscreen><verb>
ps
</verb></tscreen>
です.
カーネルの失敗の原因の調査が終わったらリブートすべきです. それまでの
不具合によりカーネルのすべての部分が期待するような動作をしているわけ
ではないということを忘れないでください. 以下のうちいずれかの方法でシ
ステムのシャットダウンおよびリブートを行ってください.
<tscreen><verb>
call diediedie()
</verb></tscreen>
カーネルをコアダンプしてリブートしますので, 後で kgdbによってコアの高
レベル解析をすることができます. このコマンドは通常
`<tt>continue</tt>'命令にエイリアスされています.
`<tt>panic</tt>'にエイリアスされている
<tscreen><verb>
call boot(0)
</verb></tscreen>
は動いているシステムを `clean' に shut downするよい方法です. すべて
のディスクを <tt>sync()</tt>して最後にリブートします. ディスクとカー
ネルのファイルシステムインタフェースが破損していない限り, ほぼ完全
に `clean'にシャットダウンするよい方法でしょう.
<tscreen><verb>
call cpu_reset()
</verb></tscreen>
は大惨事を防ぐための最後の手段で「赤い大きなボタン」を押すのとほとんど
同じです.(訳注: リセットボタンを押すのとほぼ同じであるという意味です)
短いコマンドの要約は
<tscreen><verb>
help
</verb></tscreen>
をタイプします. ただし, デバッグセッションのために <tt>ddb(4)</tt> の
マニュアルページのプリントアウトを用意しておくことを強くお奨めします.
カーネルのシングルステップ中にオンラインマニュアルを読むことは難しい
ということを覚えておいてください.
<sect><heading>コンソールドライバのデバッグ</heading>
<p>DDBを動かすためにはコンソールドライバが必要ですから, コンソールドラ
イバ自身に不具合のある場合は複雑になります. シリアルコンソールを利
用する方法 (ブートブロックを変更するか <tt>Boot:</tt>プロンプトで
<tt><bf>-h</bf></tt>と入力する) を思い出してください. そして標準ター
ミナルを最初のシリアルポートに設定します. DDBは, もちろんシリアルコ
ンソールを含むいずれのコンソールドライバの設定でも動作します.