[この節は, freebsd-current mailing list に Bill Paul が投稿したメールを, Dag-Erling Coïdan Smørgrav が校正し,括弧内のコ メントを追加して引用したものです. ]
From: Bill Paul <wpaul@skynet.ctr.columbia.edu>
Subject: Re: the fs fun never stops
To: ben@rosengart.com
Date: Sun, 20 Sep 1998 15:22:50 -0400 (EDT)
Cc: current@FreeBSD.ORG
      
[<ben@rosengart.com> が以下のパニックメッセージを 投稿しました.]
> Fatal trap 12: page fault while in kernel mode
> fault virtual address   = 0x40
> fault code              = supervisor read, page not present
> instruction pointer     = 0x8:0xf014a7e5
                                ^^^^^^^^^^
> stack pointer           = 0x10:0xf4ed6f24
> frame pointer           = 0x10:0xf4ed6f28
> code segment            = base 0x0, limit 0xfffff, type 0x1b
>                         = DPL 0, pres 1, def32 1, gran 1
> processor eflags        = interrupt enabled, resume, IOPL = 0
> current process         = 80 (mount)
> interrupt mask          =
> trap number             = 12
> panic: page fault
      
このようなメッセージが表示された場合, 問題が起きる状況を確認し て, 情報を送るだけでは十分ではありません. 下線をつけた命令ポインタ 値は重要な値ですが, 残念ながらこの値は構成に依存します. つまり, こ の値は使っているカーネルのイメージに依存するのです. もしスナップショッ トなどの GENERIC カーネルを使っているのであれば, 他の人間が問題の ある関数について追試をすることができますが, カスタマイズされたカー ネルの場合は, 使っている本人にしか問題の起こった場所は特定できない のです.
何をすれば良いのでしょう?
0x8: という部分は今回必
要ありません. 必要なのは 0xf0xxxxxx という部分です. 
% nm /kernel.that.caused.the.panic | grep f0xxxxxx
          
ここで, f0xxxxxx は命令ポインタ値です. カーネルシンボルの
テーブルは関数のエントリポイントを含み, 命令ポインタ値は, 
関数内部のある点であり, 最初の点ではないため, この操作を行っても
完全に一致するものが表示されない場合もあります. この場合は, 
最後の桁を省いてもういちどやってみてください. このようになりま
す:
% nm /kernel.that.caused.the.panic | grep f0xxxxx
          
これでも一致しない場合は, 桁を減らしながら何らかの出力があるま
で繰り返してください. 何か出力されたら, それがカーネルパニック
を引き起こした可能性のある関数のリストです. これは, 問題点を見付ける
正確な方法ではありませんが, 何もないよりましです. 
このようなパニックメッセージを投稿している人はよく見掛けますが, このように, 命令ポインタ値を, カーネルシンボルテーブルの中の関数 とつき合わせて調べている人はまれです.
パニックの原因を突き止める最良の方法は, クラッシュダンプをとり, 
gdb(1) でスタックトレースを行うことです. もちろん -current 
で gdb(1) がちゃんと動いていればですが (私は動くことを保証
できません. ELF 化された gdb(1) はカーネルクラッシュダンプを
正しく扱えないと言っている人がいました. 3.0 がβテストを終える前, 
に調べなければいけません. さもないと CD 出荷後に大顰蹙を買うことに
なります). 
どっちにしろ, 私は普通以下のようにします.
config -g KERNELCONFIG としてビルドディレクトリを設
定します. 
cd /sys/compile/KERNELCONFIG; makecp kernel kernel.debugstrip -d kernelmv kernel /kernel.orig/cp kernel / [注: 現在 FreeBSD 3.x kernel はデフォルトで ELF 形式となっており, 
strip -d の代りに strip -g を使う必要があります.
何らかの理由でまだ a.out 形式の kernel を使っている場合は, 
strip -aout -d を使ってください. ]
 全てのデバッグシンボルを含んだカーネルを, 実際にブートする必要は
ありません. -g をつけてコンパイルされたカーネルは, 
簡単に 10MB 近くの大きさになってしまいます. こんな大きなカーネル 
を実際にブートする必要はありません. この大きなカーネルイメージは 
後でgdb(1)を使うときにのみ必要です(gdb(1) がシンボル 
テーブルを必要とするため). シンボルを含んだカーネルのコピーを保
存 しておき, strip -d を使ってシンボルを除いたカーネルを作
成して ブートします.
 確実にクラッシュダンプをとるには, /etc/rc.conf を編
集して dumpdev を使用しているスワップパーティションに指定す
る必 要があります. こうすると rc(8) スクリプトから 
dumpon(8) コマンドが実行されクラッシュダンプ機能が有効にな
ります. 手動で dumpon(8) コマンドを実行してもかまいませ
ん. パニックの後, クラッシュダンプは savecore(8) コマンドを
使用して取り出すこと ができます. dumpdev が 
/etc/rc.conf で設定されていれ ば, rc(8) スクリプト
から savecore(8) が自動的に実行され, クラッシュダンプを 
/var/crash に保存します.
 注: FreeBSD のクラッシュダンプのサイズは, ふつう物理メモリサ
イズと同じです. つまり 64MB のメモリを積んでいれば, 64MB のクラッ
シュ ダンプが生成されることになります. /var/crash に十
分な空き 容量があることを確認してください. 手動で 
savecore(8) を実行す れば, もっと空き容量のあるディレクトリ
にクラッシュダンプを保存でき ます. options MAXMEM=(foo) と
いう行をカーネルコンフィグファイ ルに追加することで, カーネルの
メモリ使用量を制限できます. たとえば 128MB のメモリがある場合も, 
カーネルのメモリ使用量を 16MB に制限し クラッシュダンプのサイズ
も 128MB ではなく 16MB にすることができます.
 クラッシュダンプを取り出せたら, 以下のように gdb(1) を使っ
てスタックトレースをとります:
% gdb -k /sys/compile/KERNELCONFIG/kernel.debug /var/crash/vmcore.0
(gdb) where
      
 必要な情報が 1 画面に収まらないことも多いので, できれば
script(1) を使って出力を記録します. strip していないカーネ
ル イメージを使うことで, 全てのデバッグシンボルが参照でき, パニッ
ク の発生したカーネルのソースコードの行が表示されているはずです.
通常, 正確なクラッシュへの過程を追跡するには, 出力を最後の行から 
逆方向に読まなければなりません. また gdb(1) を使って, 変数
や 構造体の内容を表示させ, クラッシュした時のシステムの状態を調
べられ ます.
 もしあなたがデバッグ狂で, 同時に別のコンピュータを利用できる
環境にあれば, gdb(1) をリモートデバッグに使うこともできます.
リモートデバッグを使うと, あるコンピュータ上の gdb(1) を使っ
て, 別のコンピュータのカーネルをデバッグできます. ブレークポイン
トの設 定, カーネルコードのステップ実行など, ふつうのプログラム
のデバッグ と変わりません. コンピュータを 2 台並べてデバッグする
チャンスには, なかなか恵まれないので, 私はまだリモートデバッグを
試したことはあり ません.
[Bill による注: DDB を有効にしていてカーネルがデバッガに 落ちたら, ddb のプロンプトで 'panic' と入力すれば, 強制的にパニッ クを 起こしクラッシュダンプさせることができます. パニックの途中 で, 再び デバッガに落ちるかもしれませんが, 'continue' と入力すれ ば, クラッシュダンプを最後まで実行させられます.]