Linuxカーネル・デバッガー内部

KDB入門ガイド

カーネルの問題点をデバッグする場合、カーネルの実行をトレースし、メモリやデータ構造を調査することができると非常に便利です。Linuxのビルドイン・カーネル・デバッガーであるKDBは、この機能を提供しています。この記事では、KDBによって提供される機能の使用方法、およびLinuxマシンにKDBをインストールしセットアップする方法を学習していきます。さらに、KDBで利用可能なコマンドおよびセットアップや表示オプションに精通することになるでしょう。

Hariprasad Nellitheertha, Software engineer, IBM 

Hariprasad Nellitheerthaは、インドのBangaloreにあるIBMのLinuxテクノロジーセンターに勤務しており、現在、カーネルおよびその他のLinuxのバグに対処しているLinux Chage Teamに属しています。彼はOS/2カーネルおよびファイルシステムに取り組んできました。興味のある分野としては、Linuxのカーネル内部、ファイルシステム、およびオートノミック・コンピューティングなどがあります。連絡先はnharipra@in.ibm.comです。



2003年 6月 05日

LinuxカーネルはLinuxカーネル・デバッガー(KDB)によって、デバックすることができます。このふさわしい名前を付けられたツールは、本来ハッカーがカーネル・メモリやデータ構造にアクセスしているカーネル・コードに対するパッチです。KDBの長所の1つは、デバッグのために追加のマシンを必要としないということです。つまり、実行しているカーネルのデバッグが可能なのです。

KDB用にマシンをセットアップすることは、カーネルにパッチをあて再コンパイルする必要があるので若干の作業が必要です。KDBの利用者は、Linuxカーネル(ある程度カーネル内部まで)のコンパイル作業に精通して必要がありますが、カーネルのコンパイル方法に手助けの必要があれば、この記事の最後にある参考文献セクションを参照してください。

この記事は、KDBパッチをダウンロード、パッチの適用、カーネルのコンパイル、KDBの起動に関する情報を提供します。その後、KDBコマンドや、よく使われるいくつかのコマンドを概説し、最後に、セットアップや表示オプションに関する詳細を見ていくこととします。

入門

KDBプロジェクトは、Silicon Graphics (リンクに関しては参考文献を参照してください)によって保守されていますので、Silicon GraphicsのFTPサイトからカーネルのバージョンに依存したパッチをダウンロードする必要があります。KDBの最新バージョン(この記事を執筆している時点で)は、4.2です。2つのパッチをダウンロードし適用する必要があります。1つは一般的なカーネル・コードの変更を含んだ「common」パッチで、もう1つはアーキテクチャーに固有なパッチです。パッチはbz2ファイルとして利用可能になっています。たとえば、2.4.20カーネルが起動しているx86マシンにおいては、kdb-v4.2-2.4.20-common-1.bz2およびkdb-v4.2-2.4.20-i386-1.bz2が必要です。

ここに挙げているすべての例は、i386アーキテクチャー、2.4.20カーネル用です。ご自身のマシンおよびカーネルのバージョンに基づいて、適切な変更をおこなってください。また、これらのオペレーションを実行するには、ルート権限が必要です。

/usr/src/linux ディレクトリにファイルをコピーし、bzippedファイルからパッチファイルを取り出してください。

#bzip2 -d kdb-v4.2-2.4.20-common-1.bz2 #bzip2 -d kdb-v4.2-2.4.20-i386-1.bz2

kdb-v4.2-2.4.20-common-1ファイル、およびkdb-v4.2-2.4-i386-1ファイルを取得できるでしょう。

では、パッチを当ててください。

#patch -p1 <kdb-v4.2-2.4.20-common-1 #patch -p1 <kdb-v4.2-2.4.20-i386-1

パッチの適用は何事もなく完了するでしょう。完了後.rej で終わるファイルを捜してみてください。このファイルはパッチが失敗したことを表します。カーネル・ツリーに何もなければ、パッチは問題なく適用されています。

次に、KDBが利用可能になったカーネルをビルドする必要があります。はじめのステップはCONFIG_KDBオプションをセットすることです。このためには、おみの設定手段(xconfig、menuconfigなど)を使用してください。最後にある「Kernel hacking」セクションで、「Built-in Kernel Debugger support」オプションを選択してください。

さらに、お好みでその他2、3のオプションを選択することができます。「Compile the kernel with frame pointers」オプション(もしあれば)を選択することで、CONFIG_FRAME_POINTER フラグをセットできます。これにより、フレームポインタ・レジスターが一般的なレジスターとしてではなくフレーム・ポインタとして使用されるので、スタック・トレースバックを最適に行ってくれるでしょう。また、「KDB off by default」オプションを選択することもできます。これは、CONFIG_KDB_OFF フラグをセットし、デフォルトでKDBを停止します。この内容については、後のセクションでより詳しく説明します。

設定を保存し、終了してください。カーネルを再コンパイルします。カーネルをビルドする前に、「make clean」を実行することを推奨します。普段の方法にてカーネルをインストールし、ブートしてください。


環境変数の初期化および設定

KDBの初期化中に実行されるKDBコマンドを定義することができます。これらのコマンドは、Linuxのソース・ツリー(もちろんパッチ対応済み)のKDBディレクトリーにあるkdb_cmdsと呼ばれるテキストファイルに定義する必要があります。このファイルは、表示や出力のオプションを設定する環境変数を定義するためにも使用されます。ファイルの先頭のコメントには、ファイル編集時に役立つ内容が記述されています。このファイルを使用するにあたってのデメリットは、ファイルを変更する度にカーネルを再コンパイルし再インストールする必要があるということです。


KDBを起動する

コンパイル時にCONFIG_KDB_OFFが選択されていないと、KDBはデフォルトでアクティブとなります。そうでなければ、明示的にアクティブにする必要があります。つまり、ブート中にカーネルにkdb=onフラグを渡すか、/procがマウントさてからアクティブにします。

#echo "1" >/proc/sys/kernel/kdb

上述の逆の処置はKDBを非アクティブにすることです。すなわち、KDBがデフォルトでオンの時に、カーネルへkdb=off フラグを渡すか、以下のように実行するとKDBは非アクティブになります。

#echo "0" >/proc/sys/kernel/kdb

他にもブート中にカーネルへ渡すことができるフラグがあります。kdb=early フラグは、ブートプロセスの初期にKDBへ渡されて制御できるようになります。これは、ブートプロセスの初期にデバッグする必要がある場合、役に立つでしょう。

KDBを起動する方法はたくさんあります。KDBがonの場合、カーネルにパニックが発生した時に自動的に起動されます。キーボード上でPAUSEキーを押すと手動でKDBが立ち上がります。また別の方法としては、シリアル・コンソールを使うこともできます。もちろん、この方法を行うには、シリアル・コンソールをセットアップする必要があり(参考文献を参照してください)、シリアル・コンソールから読むプログラムが必要です。Ctrl-Aがシリアル・コンソールからKDBを起動することになります。


KDBコマンド

KDBはメモリやレジスターの変更、ブレークポイントの設定、スタックトレースなどのいくつかのオペレーションを可能とする非常に強力なツールです。これらに基づいて、KDBコマンドはいくつかのカテゴリーに分類することができます。各カテゴリーの中で、最も一般的に使用されるコマンドの詳細を以下に列挙しておきます。

メモリの表示と修正

このカテゴリで最もよく使われるコマンドはmdmdrmmmmWです。

mdコマンドは、アドレス/シンボルおよび行数の指定により、指定されたアドレスから、指定行数のメモリ内容を表示します。行数が指定されない場合、環境変数で指定されたデフォルトが利用されます。アドレスが指定されない場合は、mdコマンドは表示された最後のアドレスから表示を続けます。アドレスは初めに表示され、文字変換は最後に表示されます。

mdrコマンドは、アドレス/シンボルおよびバイトカウントの指定により、指定したアドレスから始まるメモリの内容をバイト数分表示します。基本的にはmdコマンドと同じですが、最初のアドレスや最後の文字変換は表示しません。mdrコマンドはそれほど役立つものではありません。

mmコマンドはメモリ内容を変更します。パラメータにアドレス/シンボルおよび新しい内容を指定し、指定アドレスの中を置き換えます。

mmWコマンドは、アドレスから始まるWバイトを変更します。mmは機械語を変更することに注意してください。

0xc000000から始まるメモリを15行表示する:
[0]kdb> md 0xc000000 15
0xc000000アドレス/シンボルのメモリ内容を 0x10へ変更する:
[0]kdb> mm 0xc000000 0x10

レジスターの表示と修正

このカテゴリのコマンドはrdrmefです。

rdコマンド(引数なしで)はプロセッサ・レジスターの内容を表示します。これは状況に応じて3つの引数をとります。c引数が渡されると、rdはプロセッサのコントロール・レジスターを表示します。d引数では、デバッグ・レジスターを表示します。u引数の場合は、現在のタスク、カーネルを記録した最後の時間のレジスターセットが表示されます。

rmコマンドはレジスター内容を変更します。引数としてレジスター名および新内容をとり、新内容でレジスターを変更します。レジスター名は固有のアーキテクチャーによります。現在、コントロール・レジスターは修正することができません。

efコマンドは引数としてアドレスをとり、指定のアドレスでの例外フレームを表示します。

レジスター一覧を表示する:
[0]kdb> rd
レジスター名 ebxに 0x25をセットする:
[0]kdb> rm %ebx 0x25

ブレークポイント

一般的に使用されるブレークポイントのコマンドはbpbcbdbeblです。

bpコマンドは引数としてアドレス/シンボルをとり、アドレスにブレークポイントを設定します。このブレークポイントがヒットすると、実行は停止し、KDBへ制御が移ります。このコマンドにはいくつかの便利なバリエーションがあります。bpaコマンドはSMPシステム内の全てのプロセッサにブレークポイントを設定します。bphコマンドはそれをサポートするシステム上のハードウェアレジスターの利用を強制的に使用します。bphaコマンドはハードウェアレジスターの使用を強制するという点を除けば、bpaコマンドと似ています。

bdコマンドは、特定のブレークポイントを抑止します。引数はブレークポイント番号です。このコマンドはブレークポイントテーブルからブレークポイントを削除せず、それを抑止します。ブレークポイント番号は0から始まり、設定の順に割り当てられます。

beコマンドはブレークポイントを使用可能にします。このコマンドの引数もブレークポイント番号です。

blコマンドは現在のブレークポイント一覧を表示します。可能、抑止両方のブレークポイントを含んでいます。

bcコマンドは、ブレークポイントテーブルからブレークポイントを削除します。引数には、特定のブレークポイント番号か*を指定します。*は全てのブレークポイントを取り除く場合に使用します。

関数sys_write() にブレークポイントを設定する:
[0]kdb> bp sys_write
ブレークポイントテーブル内の全てのブレークポイントをリストする:
[0]kdb> bl
ブレークポイント番号1を削除する:
[0]kdb> bc 1

スタックトレース

スタックトレースの主なコマンドはbtbtpbtcbtaです。

btコマンドは現在のスレッドのスタック情報の取得を試みます。状況に応じて、引数としてスタック・フレームのアドレスを指定します。アドレスが指定されない場合は、スタックをトレースバックするために現在のレジスターを引数に渡します。そうでなければ、有効なスタック・フレームの先頭アドレスとして提供されたアドレスで、トレースバックを試みます。CONFIG_FRAME_POINTER オプションがカーネルコンパイル時にセットされていると、フレーム・ポインタ・レジスターがスタックを保持するために使用され、その結果、スタックのトレースバックは正しく実行されます。CONFIG_FRAME_POINTER がセットされていない場合は、btコマンドは正しい結果を生み出さないかもしれません。

btpコマンドは引数としてプロセスIDをとり、特定のプロセスのスタックをトレースバックします。

btcコマンドは実際に各CPU上で起動しているプロセスのスタックをトレースバックします。稼動している初めのCPUからスタートし、btコマンドを発行し、次の稼動しているCPUへスィッチしていきます。

btaコマンドは特定の状態内の全てのプロセスをトレースバックします。引数がなければ、全てのプロセスをトレースバックをします。状況により、さまざまな引数をこのコマンドに渡すことができます。引数により特定の状態のプロセスが処理されます。オプションおよび対応する状態は以下のとおりです。

  • D: 割込み不可能
  • R: 実行中
  • S: 割込み可能なsleep
  • T: トレース済または停止
  • Z: ゾンビ
  • U: 実行不可能

これらの各コマンドは非常に多くの情報を出力します。これらのフィールドに関する詳細なドキュメントについては下記の参考文献を参照してください。

現在アクティブなスレッドのスタックをトレースする:
[0]kdb> bt
プロセスID575のスタックをトレースする:
[0]kdb> btp 575

その他のコマンド

カーネル・デバッキングにおけるその他の役立つKDBコマンドに関していくつか列挙しておきます。

idコマンドは引数としてアドレス/シンボルをとり、そのアドレスから始まる命令を逆アセンブルします。環境変数IDCOUNTが、出力が何行表示されるかを決定します。

ssコマンドは命令をシングル・ステップし、KDBへ制御を戻します。この命令の種類としてssbがあり、現在の命令ポインタ・アドレス(スクリーンに命令を表示している)から分岐命令になるまで命令を実行します。分岐命令の代表的な例は、callreturnjumpです。

goコマンドはシステムに通常実行を継続させます。実行はブレークポイントにヒットする(もし設定されていれば)まで続きます。

rebootコマンドは直ちにシステムをリブートします。システムは正しく終了されないので、結果は予測不能です。

ll コマンドは引数にアドレス、オフセット、その他のKDBコマンドを取ります。これは、リンクによるリストの各要素分コマンドを繰り返します。実行されるコマンドは、引数としてリスト内の現在の要素アドレスをとります。

scheduleルーチンから始まる命令を逆アセンブルする. 表示される行数は環境変数IDCOUNTによります:
[0]kdb> id schedule
分岐条件になるまで命令を実行する (この場合,jne命令):
[0]kdb> ssb 0xc0105355 default_idle+0x25: cli
0xc0105356 default_idle+0x26: mov 0x14(%edx),%eax
0xc0105359 default_idle+0x29: test %eax, %eax
0xc010535b default_idle+0x2b: jne 0xc0105361 default_idle+0x31

ヒントと秘訣

問題をデバッグすることは、デバッガ(または他のツール)を利用し、問題の根本原因を追跡するためにソースコードを利用して、その問題の原因を突き止めることを含んでいます。問題を判別するのにソースコードのみを利用するのは非常に難しく、熟練したカーネル・ハッカーのみが可能かもしれません。反対に、初心者はバグを修正するためにデバッガに過度に頼りがちです。このようなアプローチでは、問題を正しく解決できないかもしれません。怖いのは、このアプローチが、実際の問題を完全に取り払えない方向へ導いていくことです。そのような誤りの典型的な例は、無効な参照という実際の原因を調査せずに、NULLポインタや不適切な参照に対処するエラーハンドリングのコードを追加してしまうことです。

コードを研究し、デバッキング・ツールを使用するという2重のアプローチが、問題を見極め解決する最善の方法です。

デバッガー使用の主目的は、バグの箇所を知り、症状(いくつかのケースでは原因も)を確認し、変数の値を判断し、どのようにプログラムがそこへ到達したのか(つまり、呼び出し関数を確認する)を判断することにあります。経験をつんだハッカーなら、ある特定の問題にはどのデバッガーを使えばよいのかを知っており、デバッグで必要な情報を即座に取得し、原因を特定するコードの分析をうまくやってのけるでしょう。

それではKDBを利用して、上記のような結果が即座に得られるようないくつかのヒントをご紹介します。もちろん、デバッキングのスピード、正確性は経験、実践、システムに対する知識 (ハードウェア、カーネル内部など)に付随することを心に留めておいてください。

ヒント 1

KDBでは、プロンプトでアドレスをタイプすると、最も近い一致したシンボルを返します。これは、スタックの解析およびグローバルデータや関数のアドレス/値を判断するのに非常に便利です。同様に、シンボルの名前をタイプすると仮想アドレスが返されます。

関数sys_readがアドレス0xc013db4cから始まることを表示する:
[0]kdb> 0xc013db4c 0xc013db4c = 0xc013db4c (sys_read)

同様に、

同様に、sys_writeはアドレス0xc013dcc8であることを表示する:
[0]kdb> sys_write sys_write = 0xc013dcc8 (sys_write)

これらはスタックを解析において、グローバルデータや関数のアドレスを探し出すのに役立ちます。

ヒント 2

KDB付きでカーネルをコンパイルする際には、CONFIG_FRAME_POINTERオプションをいつも利用してください。これを利用するには、カーネルを設定する間に、「Kernel hacking」セクションで「Compile the kernel with frame pointers」オプションを選択する必要があります。これはフレーム・ポインタ・レジスタが的確なトレースバックに導くフレームポインタとして使用されることを確実にします。実際に手動でフレーム・ポインタ・レジスターの内容をダンプし、全スタックをトレースすることができます。例えば、i386マシンで、%全スタックをトレースバックするために%ebpレジスターを使用することができます。

例えば、rmqueue()関数で最初の命令を実行したすると、スタックは以下のようになります:

[0]kdb> md %ebp 
0xc74c9f38 c74c9f60 c0136c40 000001f0 00000000
0xc74c9f48 08053328 c0425238 c04253a8 00000000
0xc74c9f58 000001f0 00000246 c74c9f6c c0136a25
0xc74c9f68 c74c8000 c74c9f74 c0136d6d c74c9fbc
0xc74c9f78 c014fe45 c74c8000 00000000 08053328 [0]kdb> 0xc0136c40 0xc0136c40 = 0xc0136c40 (__alloc_pages +0x44) [0]kdb> 0xc0136a25 0xc0136a25 = 0xc0136a25 (_alloc_pages +0x19) [0]kdb> 0xc0136d6d 0xc0136d6d = 0xc0136d6d (__get_free_pages +0xd)

rmqueue()関数は_alloc_pagesに呼ばれ、それは順に_alloc_pagesに呼ばれていることを確認できます。

全てのフレームポインタの初めのダブルワードは次のフレームを示し、呼び出し元関数のアドレスがこれに続きます。したがって、スタックのトレースは簡単な作業となっています。

ヒント 3

goコマンドは任意でパラメータにアドレスを指定します。特定のアドレスで実行を続けたい場合、パラメータにアドレスを指定します。他の選択肢として、rmコマンドでgoとタイプするだけで、命令ポインタ・レジスターを変更することもできます。これは問題の原因と思われる命令や命令のセットをスキップしたいときに便利です。しかしながら、注意せずにこの命令を使用すると、厳しい問題を引き起こすかもしれず、またシステムがクラッシュするかもしれません。

ヒント 4

defcmdと呼ばれる便利なコマンドで自分用のコマンドセットを定義することができます。例えば、ブレークポイントにヒットした時はいつも、特定の変数をチェックし、いくつかのレジスター内容を確認し、スタックをダンプする作業を同時に行いたいと思うかもしれません。普通は、これら全てを同時に行うために一連のコマンドをタイプしなければならないでしょう。defcmdは、事前に定義された1つまたはそれ以上のKDBコマンドからなる自分用のコマンドを定義することが可能です。そうすると、たった1つのコマンドで3つのジョブの全てを行うことができます。この文法は以下のとおりです。

[0]kdb> defcmd name "usage" "help" 
[0]kdb> [defcmd] type the commands here 
[0]kdb> [defcmd] endefcmd

例えば、アドレス0xc000000から始まるメモリを1行表示し、レジスターの内容を表示し、スタックをダンプする、hariという名前の新しい(平凡な)コマンドを定義します。

[0]kdb> defcmd hari "" "no arguments needed" 
[0]kdb> [defcmd] md 0xc000000 1 
[0]kdb> [defcmd] rd 
[0]kdb> [defcmd] md %ebp 1 
[0]kdb> [defcmd] endefcmd

このコマンドの出力は以下のようになります。

[0]kdb> hari 
[hari]kdb> md 0xc000000 1 
0xc000000 00000001 f000e816 f000e2c3 f000e816 
[hari]kdb> rd 
eax = 0x00000000 ebx = 0xc0105330 ecx = 0xc0466000 edx = 0xc0466000
....
... [hari]kdb> md %ebp 1 0xc0467fbc c0467fd0 c01053d2 00000002 000a0200 [0]kdb>

ヒント 5

bphおよびbphaコマンドは(アーキテクチャーがハードウェア・レジスターの使用をサポートする場合)、読み出しや書き込みブレークポイントを設定するために使用することができます。つまり、データが特定のアドレスから読み書きされるときはいつでも制御が可能ということです。これはデータ/メモリ破壊の問題をデバックする時や、破壊しているコード/プロセスを識別するために使用すると非常に便利です。

アドレス0xc0204060へ4バイト書きこまれるとカーネルデバッガを開始する:
[0]kdb> bph 0xc0204060 dataw 4
0xc000000で始まるデータが少なくとも2バイト読まれるとカーネルデバッガを開始する:
[0]kdb> bph 0xc000000 datar 2

結論

KDBはカーネル・デバッキングを行うための便利で強力なツールです。KDBはさまざまなオプションを提供しており、メモリ内容やデータ構造の解析を可能にしています。中でも最も素晴らしいことは、デバッキングにあたり他のマシンを必要としないことです。

参考文献

  • Documentation/kdbディレクトリのKDBのオンライン・マニュアルを参照してください。
  • ・シリアル・コンソールに関する詳細に関しては、Documentationディレクトリでserial-console.txtファイルを参照してください。
  • SGIのカーネル・デバッガ・プロジェクトのWebサイトよりKDBをダウンロードしてください。
  • Linuxにおけるいくつかのシナリオベースのデバッキング技術の概観に関しては、「Linuxのデバッグ手法をマスターする」(developerWorks,2002年8月)を参照してください。
  • OS/2のデバッキングに関する情報は、IBMレッドブックのVol.2を参照してください。Vol.2が、OS/2のデバッキング・ハンドブックです。
  • developerWorksのLinuxゾーンには、他にも Linux関係の記事が多数掲載されています。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=228561
ArticleTitle=Linuxカーネル・デバッガー内部
publish-date=06052003