Chapter 23. カーネルデバッグ

Table of Contents
23.1. gdb によるカーネルのクラッシュダンプのデバッグ
23.2. DDD によるクラッシュダンプのデバッグ
23.3. 突然ダンプした場合の解析
23.4. DDBを使ったオンラインカーネルデバッグ
23.5. リモート GDB を使ったオンラインカーネルデバッグ
23.6. GDB を使ったローダブルモジュールのデバッグ
23.7. コンソールドライバのデバッグ

原作 Paul Richards and Jörg Wunsch

訳: 内川 喜章 . 1997 年 3 月 18 日.

23.1. gdb によるカーネルのクラッシュダンプのデバッグ

ここではクラッシュダンプ (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 する場合のクラッシュダンプを取り たい時だけ使うべきです.

Note: 以下では gdbという用語は gdb"カーネルデバッグモード"で動かしていることを意味します. gdb-kオプションをつけて起動することで, このモードになります. カーネルデバッグモードでは, プロンプトが (kgdb) に変わります.

Tip: FreeBSD 3 およびそれ以前のシステムを使っているなら, 巨大なデバッグカーネルをそのままインストールするのではなく strip されたデバッグ用カーネルをつくるべきでしょう.

    # cp kernel kernel.debug
    # strip -g kernel

この手順は必須ではありませんが, ぜひ行なうことをおすすめします (FreeBSD 4 およびそれ以降では, カーネルの make の段階で自動的にこれが行なわれます). 自動的に, あるいは上のコマンドを手動で実行してカーネルが strip されたら, 普通に make install と実行し, カーネルをインストールして構いません.

FreeBSD の古いリリース (3.1 を含まない以前のもの) は, 標準で a.out カーネルを使っていることに注意してください. これはシンボルテーブルが常に物理メモリ上に存在することを要求するため, strip されていないデバッグカーネルに含まれる大きなシンボルテーブルは非常に無駄になります. ELF カーネルを使った FreeBSD の最近のリリースでは, そのような問題がなくなりました.

カーネルを作った時にそのコピーを kernel.debug という名前で作りましょう. また, オリジナルに対して strip -gを実行します. オリジナルを普通にインストールします. また strip していないカーネル も同様にインストールすることができますが, シンボルテーブルの参照時間 がいくつかのプログラムでは劇的に増加するでしょう. また, カーネル全体 はブート時に読み込まれ スワップアウトされないため数メガバイトの物理メ モリが無駄になります.

例えばブートプロンプトで 新しいカーネルの名前をタイプすることによって, 新しいカーネルをテストした場合で, 再びシステムを動かすのに別のカーネ ルで立ち上げることが必要な場合はブートプロンプトで -sフラグ を使いシングルユーザの状態にしてください. そして以下のような操作をおこな います.

    # fsck -p
    # mount -a -t ufs       # /var/crash 用のファイルシステムを書き込み可能にする
    # savecore -N /kernel.panicked /var/crash
    # exit                  # ...マルチユーザモードへ移行

ここに示した savecore(8) は (現在動いているものとは別の) カーネルのシンボル名の抽出をおこなうために使っています. 抽出はデフォルトで は現在動いているカーネルに対しておこなわれ, クラッシュダンプとカーネルシンボ ルのくい違いのためにまったく何もしません (訳注:そのためにオプション で実際にダンプをおこしたカーネルを指定します).

クラッシュダンプの起きた後に /sys/compile/WHATEVERへ行き gdbを動かします. gdb より次のようにします.

    symbol-file kernel.debug
    exec-file /var/crash/kernel.0
    core-file /var/crash/vmcore.0

こうすると, クラッシュダンプを使ってカーネルソースを他のプログラムと同様に デバッグすることができます.

次に gdb での手順のセッションのログを示します. 長い行は読 みやすくするために改行しました. また, 参照のために行番号を入れてあり ます. ただし, これは実際の pcvtコンソールドライバの開発中の実際のエ ラーのトレースです.

     1:Script started on Fri Dec 30 23:15:22 1994
     2:# cd /sys/compile/URIAH
     3:# gdb -k 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:# exit
    78:exit
    79:
    80:Script done on Fri Dec 30 23:18:04 1994

上の出力についてのコメントをします.

line 6:

これは DDB (後述) からのダンプです. このため "because you said to!" という panicコメントがつき, ページフォルトのト ラップによって DDBに入ったことが原因の, やや長いスタックトレー スがあります.

line 20:

スタックトレースでのこれは trap()関数の位置で す.

line 36:

新しいスタックフレームの使用を指定しています. これは現 在は必要ありません. trapの場合ではスタックフレームは正 しい場所を指していると考えられます. (私は新しいコアダンプ を持っていません. 私のカーネルは長い間 panicを起こしていま せん.) ソースコードの 403 行を見ると, "tp" ポインタのアク セスが失敗しているか配列のアクセスが範囲外である可能性が高 いことがわかります.

line 52:

怪しいポインタですが, アクセスは正常におこなえました.

line 56:

ところが, 明らかにポインタはゴミを指しています. これで エラーを見つけました! (ここのコードの部分からはよくわかり ませんが, tp->t_lineはコンソールデバイスの規定 する行を参照しているので, もっと小さな整数でなければなりませ ん. )