<!-- $Id: kerneldebug.sgml,v 1.6 1997/03/25 06:31:27 hanai Exp $ -->
<!-- The FreeBSD Japanese Documentation Project -->
<!-- Original revision: 1.14 -->

<chapt><heading>カーネルデバッグ<label id="kerneldebug"></heading>

<p><em>原作 &a.paul; and &a.joerg;</em>
<p><em>訳: &a.yoshiaki;. <newline>
	 18 March 1997. </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>という名前でリン
  クして起動することでこのモードになります. デフォルトでは このリンク
  は作られていません. また, このアイデアは GNU関係者たちが彼らのツール
  を別の名前で呼び出した時に異なった動作をするということを好まない, と
  いう点で不評です. あるいは将来この機能を廃止することになるかもしれません. </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>リモート GDB を使ったオンラインカーネルデバッグ</heading>

<p>この機能は FreeBSD 2.2 からサポートされました. これは本当にすばらし
い機能です. 

  GDB はすでにかなり以前より <em/リモートデバッグ/ をサポートしてい
  ます. これはシリアル回線を使い非常に単純なプロトコルで行ないます. 
  もちろん, この方法では今までに示した方法とは違い, 2台のマシンが必
  要になります. 1台はデバッグ環境のためのホストで, すべてのソースとす
  べてのシンボルを含んだバイナリのコピーを持っています. もう 1台は
  ターゲットマシンで, 同一のカーネルのコピー (ただしデバッグ情報は 
  取り除いてあるもの) を単に実行するためのものです. 

  この場合, カーネルのコンフィグレーションは <tt>config -g</tt> で行な
  い, <em/DDB/ を含めなくてはなりません. そうして通常通りコンパイルし
  ます. こうして作ったバイナリファイルはデバッグ情報のために非常に大き
  くなります.  このカーネルをターゲットマシンにコピーして
  <tt>strip -x</tt> でデバッグシンボルを取り除きます. そして <tt/-d/ 
  ブートオプションを使いブートします. ターゲットマシンの 1番目の
  シリアル回線をデバッグホストのいずれかのシリアル回線につないでおきま
  しょう. それからデバッグ(訳注:ホスト)マシン上で, ターゲットとなって
  いるカーネルのコンパイルディレクトリで gdb を起動します:

<tscreen><verb>
% gdb -k kernel
GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.16 (i386-unknown-freebsd), 
Copyright 1996 Free Software Foundation, Inc...
(kgdb) 
</verb></tscreen>

  リモートデバッグセッションの初期化 (1番目のシリアルポートを使用する
  ことの設定) を以下のように行ないます. 

<tscreen><verb>
(kgdb) target remote /dev/cuaa0
</verb></tscreen>

  次にターゲットマシン (デバイスのプローブ直前で DDB に入っています)
  で次のように入力します:

<tscreen><verb>
Debugger("Boot flags requested debugger")
Stopped at	Debugger+0x35: movb	$0, edata+0x51bc
db> gdb
</verb></tscreen>

  DDB は次のような出力を返すでしょう. 
<tscreen><verb>
Next trap will enter GDB remote protocol mode
</verb></tscreen>

  ``gdb''と入力するたびに リモート GDB とローカル DDB が交互に切り替わ
  ります.  トラップをすぐに起こすために単に ``s'' (step) と入力して下
  さい.  そうするとホストの GDB はターゲットのカーネルの制御を行なうよ
  うになります. 

<tscreen><verb>
Remote debugging using /dev/cuaa0
Debugger (msg=0xf01b0383 "Boot flags requested debugger")
    at ../../i386/i386/db_interface.c:257
(kgdb) 
</verb></tscreen>

  このセッションではソースコードへのフルアクセスや Emacs の window 上
  の gud-mode (これは別の Emacs window に自動的にソースコードを表示し
  ます) で動かすなど, 通常の GDB セッションでできることのほとんどのこ
  とができます.

<p>リモート GDB は LKM のデバッグも行なうことができます. 最初に LKM を
 デバッグシンボルを含めた形で作ります. 
<tscreen><verb>
# cd /usr/src/lkm/linux
# make clean; make COPTS=-g
</verb></tscreen>

 そしてターゲットマシン上でモジュールのこのバージョンをインストールし
 ます. これをロードしてから, <tt>modstat</tt> を使ってロードされている
 ことを確認してください:
<tscreen><verb>
# linux
# modstat
Type     Id Off Loadaddr Size Info     Rev Module Name
EXEC      0   4 f5109000 001c f510f010   1 linux_mod
</verb></tscreen>

示されたロードアドレスに 0x20 (a.outのヘッダはおそらくこの大きさでしょ
う) を加えます. それがモジュールコードの再配置されるアドレスです. 
GDB の <tt>add-symbol-file</tt> コマンドを使ってデバッガにモジュールの
情報をつたえます. 
<tscreen><verb>
(kgdb) add-symbol-file /usr/src/lkm/linux/linux_mod.o 0xf5109020
add symbol table from file "/usr/src/lkm/linux/linux_mod.o" at
text_addr = 0xf5109020?
(y or n) y
(kgdb)
</verb></tscreen>

これで LKM のすべてのシンボルにアクセスできるようになります. 

<sect><heading>コンソールドライバのデバッグ</heading>

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