<!--
     The FreeBSD Documentation Project
     The FreeBSD Japanese Documentation Project

     Original revision: 1.33
     $FreeBSD$
-->

<chapter id="kerneldebug">
  <title>カーネルデバッグ</title>

  <para><emphasis>原作 &a.paul; and &a.joerg;</emphasis></para>

  <para><emphasis>訳: &a.jp.yoshiaki;. 1997 年 3 月 18 日.</emphasis></para>

  <sect1>
    <title><command>gdb</command>
      によるカーネルのクラッシュダンプのデバッグ</title>

    <para>ここではクラッシュダンプ (crash dump : 訳注 この文脈では
      kernel 自身
      の異常によって停止した場合に出力されるイメージを指します)
      によるカーネルデバッグの方法を示します。</para>

    <para>ここではダンプするための十分なスワップ
      (swap) の容量があるものとします。
      もし複数のスワップパーティションを持ち、
      最初のパーティションがダンプ
      を保持するのに十分な大きさを持たない場合は
      別のダンプデバイスを使うよ
      うに (<literal>config kernel</literal> 行で)
      カーネルのコンフィグをおこなうか、&man.dumpon.8;
      コマンドを使って別のデバイスを示すことができます。&man.dumpon.8;
      を使うもっともよい方法は変数 <literal>dumpdev</literal> を
      <filename>/etc/rc.conf</filename> で設定することです。一般的には
      <filename>/etc/fstab</filename> で設定されているスワップデバイスが
      使われるでしょう。
      スワップに使えないデバイスへのダンプ、
      例えばテープへのダンプは現在サポートさ
      れていません。カーネルのコンフィグは
      <command>config <option>-g</option></command> によって行ってください。
      <link linkend="kernelconfig"> FreeBSD
	カーネルのコンフィグレーション </link>
      には FreeBSD のカーネルの設定の詳細がありますので
      参照してください。</para>

    <para>&man.dumpon.8; コマンドを使ってどこへダンプするか
      カーネルに伝えてください
      (&man.swapon.8; によってそのパーティションが
      スワップとして設定された
      後でなければならないことに注意してください)。これは普通は
      <filename>/etc/rc.conf</filename> や <filename>/etc/rc</filename>
      で設定されます。あるいは
      別の方法としてカーネルコンフィグレーションファイルの
      <literal>config</literal> 行の <literal>dump</literal> 節で
      ダンプデバイスをハードコードすることができます。
      この方法はあまりよくは
      ありません。カーネルがブート時に crash
      する場合のクラッシュダンプを取り
      たい時だけ使うべきです。</para>

    <note>
      <para>以下では <command>gdb</command>という用語は
	<command>gdb</command>
        を<quote>カーネルデバッグモード</quote>で動かしていることを意味します。
        <command>gdb</command> を
	<option>-k</option>オプションをつけて起動することで、
        このモードになります。
        カーネルデバッグモードでは、プロンプトが
        <prompt>(kgdb)</prompt> に変わります。</para>
    </note>

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

      <screen>&prompt.root; <userinput>cp kernel kernel.debug</userinput>
&prompt.root; <userinput>strip -g kernel</userinput></screen>

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

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

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

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

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

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

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

    <screen><userinput>symbol-file kernel.debug</userinput>
<userinput>exec-file /var/crash/kernel.0</userinput>
<userinput>core-file /var/crash/vmcore.0</userinput></screen>

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

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

<screen> 1:Script started on Fri Dec 30 23:15:22 1994
 2:&prompt.root; <userinput>cd /sys/compile/URIAH</userinput>
 3:&prompt.root; <userinput>gdb -k kernel /var/crash/vmcore.1 </userinput>
 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:<prompt>(kgdb)</prompt> <userinput>where</userinput>
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:<prompt>(kgdb)</prompt> <userinput>up 10</userinput>
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:<prompt>(kgdb)</prompt> <userinput>frame frame-&gt;tf_ebp frame-&gt;tf_eip</userinput>
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-&gt;t_line].l_open)(dev, tp));
41:<prompt>(kgdb)</prompt> <userinput>list</userinput>
42:398        
43:399             tp-&gt;t_state |= TS_CARR_ON;
44:400             tp-&gt;t_cflag |= CLOCAL;  /* cannot be a modem (:-) */
45:401     
46:402     #if PCVT_NETBSD || (PCVT_FREEBSD &gt;= 200)
47:403             return ((*linesw[tp-&gt;t_line].l_open)(dev, tp));
48:404     #else
49:405             return ((*linesw[tp-&gt;t_line].l_open)(dev, tp, flag));
50:406     #endif /* PCVT_NETBSD || (PCVT_FREEBSD &gt;= 200) */
51:407     }
52:<prompt>(kgdb)</prompt> <userinput>print tp</userinput>
53:Reading in symbols for ../../i386/i386/cons.c...done.
54:$1 = (struct tty *) 0x1bae
55:<prompt>(kgdb)</prompt> <userinput>print tp-&gt;t_line</userinput>
56:$2 = 1767990816
57:<prompt>(kgdb)</prompt> <userinput>up</userinput>
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:<prompt>(kgdb)</prompt> <userinput>up</userinput>
62:#2  0xf0132c34 in spec_open ()
63:<prompt>(kgdb)</prompt> <userinput>up</userinput>
64:#3  0xf012d014 in vn_open ()
65:<prompt>(kgdb)</prompt> <userinput>up</userinput>
66:#4  0xf012a183 in open ()
67:<prompt>(kgdb)</prompt> <userinput>up</userinput>
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-&gt;sy_call)(p, args, rval);
74:<prompt>(kgdb)</prompt> <userinput>up</userinput>
75:Initial frame selected; you cannot go up.
76:<prompt>(kgdb)</prompt> <userinput>quit</userinput>
77:&prompt.root; <userinput>exit</userinput>
78:exit
79:
80:Script done on Fri Dec 30 23:18:04 1994</screen>

    <para>上の出力についてのコメントをします。</para>

    <variablelist>
      <varlistentry><term>line 6:</term>
	<listitem>
	  <para>これは DDB (後述) からのダンプです。このため
	    <quote>because you said to!</quote> という
	    panicコメントがつき、ページフォルトのトラップによって
	    DDBに入ったことが原因の、やや長いスタックトレー
	    スがあります。</para>
	</listitem>
      </varlistentry>

      <varlistentry><term>line 20:</term>
	<listitem>
	  <para>スタックトレースでのこれは
	    <function>trap()</function>関数の位置です。</para>
	</listitem>
      </varlistentry>

      <varlistentry><term>line 36:</term>
	<listitem>
	  <para>新しいスタックフレームを使用するように指定しています。
            ただし、ここでこれを指定する必要ありません。
            trap の場合、スタックフレームは正しい場所を指していると考えられるからです。
            ソースコードの 403 行を見ると、<quote>tp</quote>
            ポインタのアクセスが失敗しているか、
            配列のアクセスが範囲外である可能性が高いことがわかります。</para>
	</listitem>
      </varlistentry>

      <varlistentry><term>line 52:</term>
	<listitem>
	  <para>怪しいポインタですが、
	    アクセスは正常におこなえました。</para>
	</listitem>
      </varlistentry>

      <varlistentry><term>line 56:</term>
	<listitem>
	  <para>ところが、明らかにポインタはゴミを指しています。これで
	    エラーを見つけました! (ここのコードの部分からはよくわかり
	    ませんが、
	    <literal>tp-&gt;t_line</literal>はコンソールデバイスの規定
	    する行を参照しているので、
	    もっと小さな整数でなければなりません。)</para>
	</listitem>
      </varlistentry>
    </variablelist>
  </sect1>

  <sect1>
    <title>DDD によるクラッシュダンプのデバッグ</title>

    <para>カーネルのクラッシュダンプは <command>ddd</command>
      のようなグラフィカルなデバッガで調べることもできます。
      通常はコマンドラインで <option>-k</option> オプションをつけて
      <command>ddd</command> を起動します。たとえば:</para>

    <screen>&prompt.root; <userinput>ddd -k /var/crash/kernel.0 /var/crash/vmcore.0</userinput></screen>

    <para>クラッシュダンプを <command>ddd</command>
      のグラフィカルなインタフェースを使って
      見ることができます。</para>
  </sect1>

  <sect1>
    <title>突然ダンプした場合の解析</title>

    <para>カーネルが予想もしない時にコアダンプして <command>config
	-g</command>
      を行ってコンパイルされていなかった場合にはどうしたら
      よいでしょう。すべてが失われるわけではありません。
      パニックを起こさないでください。</para>

    <para>もちろん、クラッシュダンプを使えるようにする必要があります。
      使い方は前述の部分を見てください。</para>

    <para>カーネルのコンパイルディレクトリ
      (<filename>/usr/src/sys/<replaceable>arch</replaceable>/conf</filename>)
      で、設定ファイルを編集します。以下の行のコメントを外します
      (行が存在しなければ追加します):</para>

    <programlisting>makeoptions    DEBUG=-g                #Build kernel with gdb(1) debug symbols</programlisting>

    <para>カーネルを再構築しましょう。
      Makefileのタイムスタンプの変更により、例えば  <!-- kuriyama -
      should be filename --> <emphasis remap=tt> trap.o
      </emphasis>などのいくつかの他のオブジェクトファイルも作り直さ
      れます。少しの幸運があれば、
      <option>-g</option>オプションが追加されても作ら
      れるコードは変更されず、いくらかのデバッグシンボル以外には
      問題を
      起こしたコードとそっくりな新しいカーネルを手に入れることが
      できます。少なくとも &man.size.1;
      コマンドで古い方と新しい方のサイズを比較すべきです。
      これが食い違っていれば、
      多分あきらめなければならないでしょう。</para>

    <para>ダンプを使って前述のように動かして調べます。
      デバッグシンボルは 必ずしも十分ではありません。
      上の例ではスタックトレースでいくつかの関
      数の行番号や引数リストが表示されないかもしれません。
      もしより多くのデバッグシンボルが必要であれば、十分になるまで
      適切なオブジェクトファイ ルを消して (makeして)
      <command>gdb <option>-k</option></command> セッションを繰り返してください。</para>

    <para>これは必ずしもうまく動くと保証はできません。
      しかしほとんどの場合でうまくいくでしょう。</para>
  </sect1>

  <sect1>
    <title>DDBを使ったオンラインカーネルデバッグ</title>

    <para><command>gdb <option>-k</option></command>
      は非常に高レベルのユーザインタフェースを提
      供するオフラインデバッガですが、いくつかのことはできません。
      (できないことの中で)
      極めて重要なことはカーネルコードへのブレークポイ
      ントの設定とシングルステップ実行です。</para>

    <para>カーネルの低レベルデバッグが必要であれば、DDBと呼ばれる
      on-lineデバッガが使えます。ブレークポイントの設定、
      シングルステップのカーネルの実行、
      変数の検査と変更などができます。
      ただし、これはカーネルのソースファ
      イルにアクセスすることはできません。
      <command>gdb</command>のようにすべてのデ
      バッグ情報にはアクセスできず、global と
      static のシンボルにアクセスすることができるだけです。</para>

    <para>カーネルに DDB
      を含めるためにはコンフィグファイルに次のようなオプショ
      ンを加えて、</para>

    <programlisting>options DDB</programlisting>

    <para>再構築をおこないます。(
      FreeBSD のカーネルの設定の詳細については <link
	linkend="kernelconfig">FreeBSD
	カーネルのコンフィグレーション</link>を参照してくださ
      い。</para>

    <note>
      <para>古いバージョンの起動ブロックを使っている場合、ですと、
	デバッガのシンボルが完全にはロードされないかもしれません。
        その時は起動ブロックを最新のものに更新してください。
        新しい起動ブロックは、DDB シンボルを自動的にロードします。</para>
    </note>

    <para>DDB カーネルの実行において、
      DDB に入るいくつかの方法があります。最初の、
      最も早い方法はブートプロンプトが出ている時に
      <option>-d</option>のブートフラグをタイプすることです。
      カーネルはデバッグモードで起動し、デバイスのプローブ以前に
      DDB に入ります。したがって、デバイスのプローブ/初期
      設定ファンクションのデバッグができます。</para>

    <para>2 つ目のシナリオはキーボードのホットキーで、通常は
      Ctrl-Alt-ESC です。syscons ではホットキーは再設定することができ、
      配付されているいくつかのキーマッピングでは別のキーに
      再設定されていますので確認しておいてください。シリアルラインの
      BREAK を使ってシリアルコンソールから DDB へ入ることを可
      能にするオプションもあります
      (カーネルコンフィグレーションファイルの  <literal>options
	BREAK_TO_DEBUGGER</literal>)。これは多くのつまらないシリ
      アルアダプタが、例えばケーブルを引き抜いた時に
      BREAK 状態を意味もなく
      作り出してしまうのでデフォルトでは無効になっています。</para>

    <para>3つ目は、DDB
      を使うようになっているカーネルがパニック状態になると DDB
      へ入るというものです。このため、
      無人運転するマシンのカーネルに DDB を
      入れるのは賢明ではありません。</para>

    <para>DDB のコマンドはおおまかには <command>gdb</command>
      のいくつかのコマンドと似て
      います。おそらく最初にブレークポイントを
      設定する必要があるでしょう。</para>

    <screen><userinput>b function-name</userinput>
<userinput>b address</userinput></screen>

    <para>数値はデフォルトでは 16 進数で、
      シンボル名とはまったく異なります。16 進数で <literal>a-f</literal>
      の文字で始まる場合は、先頭に <literal>0x</literal>
      をつける必要があります(それ以外の数字の場合はどちらでもか
      まいません)。<literal>function-name +
	0x103</literal>のような単純な式を使うことができます。</para>

    <para>割り込みされたカーネルから処理を続行するためには、</para>

    <screen><userinput>c</userinput></screen>

    <para>とタイプするだけです。
      スタックのトレースには</para>

    <screen><userinput>trace</userinput></screen>

    <para>とします。</para>

    <note>
      <para>DDB にホットキーで入った場合は、カーネルはその
	(ホットキーの) 割り込み
	の処理を行っていますのでスタックトレースは
	あまり役にたたないことに注意してください。</para>
    </note>

    <para>ブレークポイントを削除したい場合は、</para>

    <screen><userinput>del</userinput>
<userinput>del address-expression</userinput></screen>

    <para>とします。
      最初の形式はブレークポイントにヒットしたすぐ後で使うことができ、
      現在のブレークポイントを削除します。2 番目の形式では任意のブレー
      クポイントを削除することができますが、
      次の形式で得られるような正確な
      アドレスを与えることが必要です。</para>

    <screen><userinput>show b</userinput></screen>

    <para>カーネルをシングルステップ実行させるには</para>

    <screen><userinput>s</userinput></screen>

    <para>としてみてください。これは関数呼出し先までステップ実行 (step
      into function) するでしょう。
      次のステートメントが終了するまでの DDBトレースは</para>

    <screen><userinput>n</userinput></screen>

    <para>によっておこなうことができます。</para>

    <note>
      <para>これは <command>gdb</command> の <command>next</command>
	命令とは異ります。<command>gdb</command>の
	<command>finish</command> 命令と似ています。</para>
    </note>

    <para>メモリ上のデータを調べるには (例として) 次のようにします。

      <screen><userinput>x/wx 0xf0133fe0,40</userinput>
<userinput>x/hd db_symtab_space</userinput>
<userinput>x/bc termbuf,10</userinput>
<userinput>x/s stringbuf</userinput></screen>

      word/halfword/byte 単位でアクセスをおこない、hex (16進)
      /dec (10進) /
      char (文字) /string (文字列) で表示します。
      カンマの後ろの数字はオブジェク
      トカウントです。次の 0x10 個の要素を表示するには、単純に</para>

    <screen><userinput>x ,10</userinput></screen>

    <para>とします。同様に次のように使うことができます。

      <screen><userinput>x/ia foofunc,10</userinput></screen>

      <function>foofunc</function>
      の最初の 0x10 個の命令語をディスアセンブルし、
      <function>foofunc</function>
      の先頭からのオフセットとともに表示します。</para>

    <para>メモリの内容を変更するには write コマンドを使います。</para>

    <screen><userinput>w/b termbuf 0xa 0xb 0</userinput>
<userinput>w/w 0xf0010030 0 0</userinput></screen>

    <para>コマンドモディファイアの
      (<literal>b</literal>/<literal>h</literal>/<literal>w</literal>)
      はデータを 書くサイズを定義し、
      これに続く最初の式は書き込むアドレス、残りがこれ
      に続く連続するメモリアドレスに書き込まれるデータになります。
    </para>

    <para>現在のレジスタ群の内容を知りたい場合は</para>

    <screen><userinput>show reg</userinput></screen>

    <para>とします。また、単一のレジスタの値を表示するには、例えば

      <screen><userinput>p $eax</userinput></screen>

      とします。また値の変更は</para>

    <screen><userinput>set $eax new-value</userinput></screen>

    <para>とします。</para>

    <para>DDBからカーネルの関数を呼び出す必要がある場合は、単に</para>

    <screen><userinput>call func(arg1, arg2, ...)</userinput></screen>

    <para>とします。return 値が出力されます。</para>

    <para>動いているプロセスの &man.ps.1; スタイルの概要は</para>

    <screen><userinput>ps</userinput></screen>

    <para>です。</para>

    <para>カーネルの失敗の原因の調査が終わったら、ここで再起動すべきです。
      それまでの不具合により、カーネルのすべての部分が期待するような
      動作をしているわけではないということを忘れないでください。
      以下のうちいずれかの方法でシステムのシャットダウンおよび再起動を行ってください。</para>

    <screen><userinput>panic</userinput></screen>

    <para>カーネルをコアダンプしてリブートしますので、後で
      <command>gdb</command>によってコアの高レベル解析をすることができます。
      このコマンドは通常、一度
      <command>continue</command>命令を使った後に
      使うことになるでしょう。</para>

    <screen><userinput>call boot(0)</userinput></screen>

    <para> は動いているシステムを `clean' に shut
      down するよい方法です。すべてのディスクを
      <function>sync()</function> して最後にリブートします。
      ディスクとカー
      ネルのファイルシステムインタフェースが破損していない限り、
      ほぼ完全に `clean' にシャットダウンするよい方法でしょう。</para>
    <!-- kuriyama - ldquo? -->

    <screen><userinput>call cpu_reset()</userinput></screen>

    <para>は大惨事を防ぐための最後の手段で 「赤い大きなボタン」
      を押すのとほとんど同じです (訳注:
      リセットボタンを押すのとほぼ同じであるという意味です)。</para>

    <para>短いコマンドの要約は</para>

    <screen><userinput>help</userinput></screen>

    <para>をタイプします。ただし、デバッグセッションのために
	&man.ddb.4; の
      マニュアルページのプリントアウトを用意しておくことを
      強くお奨めします。
      カーネルのシングルステップ中にオンラインマニュアルを
      読むことは難しいということを覚えておいてください。</para>
  </sect1>

  <sect1>
    <title>リモート GDB を使ったオンラインカーネルデバッグ</title>

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

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

    <para>この場合、カーネルのコンフィグレーションは <command>config
	-g</command> で行な い、<option>DDB</option>
      を含めなくてはなりません。そうして通常通りコンパイルし ます。
      こうして作ったバイナリファイルはデバッグ情報のために非常に大き
      くなります。このカーネルをターゲットマシンにコピーして
      <command>strip -x</command> でデバッグシンボルを取り除きます。
      そして <option>-d</option>  ブートオプションを使いブートします。
      sio デバイスにフラグ 0x80 が設定されているターゲットマシンの
      シリアル回線を、デバッグホストのいずれかのシリアル回線に
      つないでください。
      それからデバッグ (訳注:ホスト) マシン上で、ターゲットとなって
      いるカーネルのコンパイルディレクトリで <command>gdb</command> を起動します:</para>
	
    <screen>&prompt.user; <userinput>gdb -k kernel</userinput>
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...
<prompt>(kgdb)</prompt> </screen>

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

    <screen><prompt>(kgdb)</prompt> <userinput>target remote /dev/cuaa0</userinput></screen>

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

    <screen>Debugger("Boot flags requested debugger")
Stopped at	Debugger+0x35: movb	$0, edata+0x51bc
<prompt>db&gt;</prompt> <userinput>gdb</userinput></screen>

    <para>DDB は次のような出力を返すでしょう。</para>

    <screen>Next trap will enter GDB remote protocol mode</screen>

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

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

    <para>このセッションではソースコードへのフルアクセスや Emacs の
      window 上 の gud-mode (これは別の Emacs window
      に自動的にソースコードを表示します) で動かすなど、通常の GDB
      セッションでできることのほとんどのことができます。</para>
  </sect1>
  
  <sect1>
    <title>GDB を使ったローダブルモジュールのデバッグ</title>

    <para>モジュール内部で発生する panic のデバッグや、
      動的モジュールを使っているマシンを GDB
      でリモートデバッグしている場合、
      モジュールのシンボル情報を得る方法を
      GDB に伝える必要があります。</para>

    <para>まず、モジュールをデバッグ情報を含めて構築する必要があります。</para>

    <screen>&prompt.root; <userinput>cd /sys/modules/linux</userinput>
&prompt.root; <userinput>make clean; make COPTS=-g</userinput></screen>
     
    <para>リモート GDB を使っている場合は、
      ターゲットマシンで <command>kldstat</command> を実行することで
      モジュールがどこにロードされたか調べることが可能です。</para>

    <screen>&prompt.root; <userinput>kldstat</userinput>
Id Refs Address    Size     Name
 1    4 0xc0100000 1c1678   kernel
 2    1 0xc0a9e000 6000     linprocfs.ko
 3    1 0xc0ad7000 2000     warp_saver.ko
 4    1 0xc0adc000 11000    linux.ko</screen>

    <para>クラッシュダンプをデバッグしている場合、
      <literal>linker_files->tqh_first</literal> から始まる
      <literal>linker_files</literal> リストを調べ、
      探している <literal>filename</literal> が見つかるまで
      <literal>link.tqe_next</literal> ポインタをたどる必要があります。
      エントリ中の <literal>address</literal> メンバが、
      モジュールのロードアドレスです。</para>

    <para>次に、モジュール内の text セクションのオフセットを調べます。</para>

    <screen>&prompt.root; <userinput>objdump --section-headers /sys/modules/linux/linux.ko | grep text</userinput>
  3 .rel.text     000016e0  000038e0  000038e0  000038e0  2**2
 10 .text         00007f34  000062d0  000062d0  000062d0  2**2</screen>

    <para>必要なのは <literal>.text</literal> セクションで、
      上の例では 10 にあたります。その 4 番目の 16 進数フィールド
      (全部で 6 フィールドあります) が、ファイル中の text
      セクションのオフセットになります。
      そして、このオフセットをモジュールのロードアドレスに加算すると
      モジュールのコードの再配置アドレスを求めることができます。
      この例では 0xc0adc000 + 0x62d0 = 0xc0ae22d0 です。
      GDB コマンド <command>add-symbol-file</command> を使い、
      得られたモジュールの情報をデバッガに伝えるには、次のようにします。</para>

    <screen><prompt>(kgdb)</prompt> <userinput>add-symbol-file /sys/modules/linux/linux.ko 0xc0ae22d0</userinput>
add symbol table from file "/sys/modules/linux/linux.ko" at text_addr = 0xc0ae22d0?
(y or n) <userinput>y</userinput>
Reading symbols from /sys/modules/linux/linux.ko...done.
<prompt>(kgdb)</prompt></screen>

    <para>これで、モジュール内のすべてのシンボルにアクセスできるようになります。</para>
  </sect1>

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

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

<!--
     Local Variables:
     mode: sgml
     sgml-declaration: "../chapter.decl"
     sgml-indent-data: t
     sgml-omittag: nil
     sgml-always-quote-attributes: t
     sgml-parent-document: ("../book.sgml" "part" "chapter")
     End:
-->
