IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  Linux  >

Linuxのデバッグ手法をマスターする

Linuxのバグを検出し、鎮圧するための主な戦略

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

原文はこちら

原文はこちら


レベル: 中級

Steve Best (sbest@us.ibm.com), JFS core team member, IBM

2002年 8月 01日

実行中のユーザー・スペース・プログラムは、いろいろな方法で監視することができます。プログラムをデバッガーにかけ、ステップ実行し、〔実行状況を示す〕文を表示させるという方法もありますし、プログラムを分析するためのツールを追加するという手もあります。本稿では、Linux上で実行されるプログラムをデバッグするための手法をいろいろと紹介します。ここでは、セグメンテーション障害、メモリーのオーバーランとリーク、およびハングアップなどの問題をデバッグするための4つのシナリオについて説明します。

本稿では、Linuxプログラムをデバッグするための4通りのシナリオを紹介します。シナリオ1では、メモリー割り当ての問題を扱うサンプル・プログラムを2つ使用します。MEMWATCHとYet Another Malloc Debugger (YAMD) というツールを使ってデバッグを行います。シナリオ2では、Linuxのstraceユーティリティーを使用します。このユーティリティーでは、システム・コールとシグナルをトレースすることで、プログラムの問題箇所を検出することができます。シナリオ3では、LinuxカーネルのOops機能を使って、セグメンテーション障害を解決するとともに、同じ問題をGNUデバッガー (gdb) で解決するために、カーネル・ソース・レベル・デバッガー (kgdb) をセットアップする方法を紹介します。kgdbプログラムは、シリアル接続経由でのLinuxカーネル・リモートgdbです。シナリオ4では、Linuxで利用できるmagic key sequenceを使って、ハングアップを起こしているコンポーネントについての情報を表示します。

一般的なデバッグ方針

プログラムにバグがあるというのは、コードのなかのどこかに、自分では正しいと思っていたものが実際には間違っていたということでしょう。バグを見つけ出す作業は、どこか間違っている箇所を発見するまで、自分で正しいと思っていることを確認する過程だと言えます。

自分では正しいと思っていることの例としては、以下のような類のことがあります。

  • ソース・コードのある箇所で、変数に何らかの値が割り当てられている。
  • ある構造体が、ある箇所で、すでに正しくセットアップされている。
  • ある if-then-else 文で、if 以下が実際に実行された個所である。
  • サブルーチンが呼び出される際、そのルーチンはパラメーターを正しく受けとっている。

バグの発見は、これらのことをすべて確認する作業からなります。あるサブルーチンが呼び出されるときに、ある変数は、ある値をとっているはずだと考えるのであれば、そのことを確認する必要があります。ある if 構文が実行されるものと考えるのであれば、そのことを確認する必要があります。通常、プログラマーは自分の想定していたことを確認していくうちに、ついには、その考えが間違っている箇所を発見することになります。その結果、バグの在処〔ありか〕がわかることになります。

デバッグ作業は、決して避けて通ることはできません。デバッグには、いろいろな方法があります。画面にメッセージを表示させるとか、デバッガーを使うとか、あるいはプログラムの実行を頭の中で想像し、経験的に原因を推測するといった方法もあります。

バグを修正するには、まず、その在処〔ありか〕を発見する必要があります。たとえば、セグメンテーション障害の場合、コード中の何行目がセグメンテーション障害の原因となっているのかを知る必要があります。何行目が問題なのかがわかれば、次に、そのメソッド中の変数の値、そのメソッドの呼び出し方、および、なぜエラーが発生したのかを突き止めます。デバッガーを使えば、これらすべての情報を簡単に調べることができます。デバッガーがない場合、他のツールを使う手もあります。(実稼働環境にデバッガーが用意されていない場合もあるでしょう。Linuxカーネルには、デバッガーは組み込まれていません。)

メモリーとカーネルに関する便利なデバッグ・ツール
Linuxでデバッグ・ツールを使ってユーザー・スペースやカーネルの問題を追跡するには、いろいろな方法があります。以下に列挙したツールや手法を使ってソース・コードをビルドし、デバッグします。

ユーザー・スペース用のツール:

  • メモリー・ツール: MEMWATCH、YAMD
  • strace
  • GNUデバッガー (gdb)
  • Magic key sequence

カーネル用のツール:

  • カーネル・ソース・レベル・デバッガー (kgdb)
  • 内蔵のカーネル・デバッガー (kdb)
  • Oops

本稿では、コードを目で見て検査することによっては発見の難しい問題を扱うことにします。この種の問題は、まれな状況でしか起こらないかもしれません。多くの場合、メモリー・エラーは、いくつかの状況が重なった場合にのみ起こります。また、プログラムを配備してからでないとメモリー関係のバグを発見できない場合もあります。

シナリオ1: メモリー・デバッグ・ツール

C言語は、Linuxシステムでの標準的なプログラミング言語として、動的メモリー割り当てを、さまざまな形で制御することができます。しかし、このように自由自在に制御できるということは、メモリー管理に重大な問題をもたらし得るということでもあり、そうした問題が、プログラムをクラッシュさせたり、時間の経過とともにプログラムの性能を低下させたりすることがあります。

よく起こるのは、メモリー・リーク (malloc() で獲得されたメモリーが、対応する free() 呼び出しで解放されないという問題) やバッファー・オーバーラン (たとえば、配列として割り当てられたメモリー領域を越えて書き込みを行うという問題) で、これらの問題は、検出し難い面があります。以下では、メモリー関係の問題を非常に簡単に検出、分離するためのデバッグ・ツールをいくつか取り上げたいと思います。




上に戻る


MEMWATCH

MEMWATCHは、Johan Lindhが作成したC用のオープン・ソースのメモリー・エラー検出ツールで、ダウンロードが可能です (稿末の参考文献参照)。コードにヘッダー・ファイルを追加し、gccのコマンド・ラインでMEMWATCHを定義するだけで、プログラム中のメモリー・リークやメモリーのデータ破壊 (corruptions) を追跡することができます。MEMWATCHは、ANSI Cをサポートし、結果をログとして残します。二重解放、間違った解放、未解放メモリー、オーバーフロー、アンダーフローなどを検出することができます。


リスト1. メモリー・サンプル (test1.c)
                
#include <stdlib.h>
#include <stdio.h>
#include "memwatch.h"

int main(void)
{
  char *ptr1;
  char *ptr2;

  ptr1 = malloc(512);
  ptr2 = malloc(512);

  ptr2 = ptr1;
  free(ptr2);
  free(ptr1);
}

リスト1のコードでは、512バイトのメモリー・ブロックを2つ割り当てた後、2個目のブロックへのポインターに1個目のブロックへのポインターをセットしています。その結果、2個目のブロックのアドレスは失われてしまい、メモリー・リークが発生しています。

そこで、リスト1をmemwatch.cといっしょにコンパイルしてみます。MAKEファイルは以下のようなものになります。


test1
                
gcc -DMEMWATCH -DMW_STDIO test1.c memwatch
c -o test1

test1のプログラムを実行すると、メモリー・リークの報告が作成されます。出力ファイルmemwatch.logは、リスト2のようなものになります。


リスト2. test1のmemwatch.logファイル
                
MEMWATCH 2.67 Copyright (C) 1992-1999 Johan Lindh

...
double-free: <4> test1.c(15), 0x80517b4 was freed from test1.c(14)
...
unfreed: <2> test1.c(11), 512 bytes at 0x80519e4
{FE FE FE FE FE FE FE FE FE FE FE FE ..............}

Memory usage statistics (global):
  N)umber of allocations made: 	2
  L)argest memory usage : 	1024
  T)otal of all alloc() calls: 	1024
  U)nfreed bytes totals : 	512

MEMWATCHは、問題のある具体的な行を示しています。解放済みのポインターを解放すると、MEMWATCHは、そのことを指摘します。同様に、未解放のメモリーについても指摘します。ログの最後の部分には、メモリー・リークの量、メモリーの使用量、割り当ての総量など、統計が示されます。




上に戻る


YAMD

Nate Eldredge作のYAMDパッケージは、CおよびC++ での動的なメモリー割り当てに関係する問題を検出します。本稿執筆時点でのYAMDの最新バージョンは0.32で、yamd-0.32.tar.gzがダウンロード可能です (参考文献参照)。YAMDを使用するには、make コマンドを実行してプログラムをビルドし、さらに make install コマンドを実行して、プログラムをインストールして、ツールのセットアップを行います。

YAMDをダウンロードしたら、test1.cでYAMDを試してみてください。#include memwatch.h を削除し、MAKEファイルも、以下のように、少し変更します。


test1をYAMDといっしょにコンパイルする
                
gcc -g test1.c -o test1

リスト3は、test1に対するYAMDの出力結果を示したものです。


リスト3. test1に対するYAMDの出力
                
YAMD version 0.32
Executable: /usr/src/test/yamd-0.32/test1
...
INFO: Normal allocation of this block
Address 0x40025e00, size 512
...
INFO: Normal allocation of this block
Address 0x40028e00, size 512
...
INFO: Normal deallocation of this block
Address 0x40025e00, size 512
...
ERROR: Multiple freeing At
free of pointer already freed
Address 0x40025e00, size 512
...
WARNING: Memory leak
Address 0x40028e00, size 512
WARNING: Total memory leaks:
1 unfreed allocations totaling 512 bytes

*** Finished at Tue ... 10:07:15 2002
Allocated a grand total of 1024 bytes 2 allocations
Average of 512 bytes per allocation
Max bytes allocated at one time: 1024
24 K alloced internally / 12 K mapped now / 8 K max
Virtual program size is 1416 K
End.

YAMDは、メモリーが解放済みであること、およびメモリー・リークがあることを示しています。次に、リスト4の別のサンプル・プログラムでYAMDを試してみることにします。


リスト4. メモリー関係のコード (test2.c)
                
#include <stdlib.h>
#include <stdio.h>

int main(void)
{
  char *ptr1;
  char *ptr2;
  char *chptr;
  int i = 1;
  ptr1 = malloc(512);
  ptr2 = malloc(512);
  chptr = (char *)malloc(512);
  for (i; i <= 512; i++) {
    chptr[i] = 'S';
  }	
  ptr2 = ptr1;
  free(ptr2);
  free(ptr1);
  free(chptr);
}

次のコマンドでYAMDを始動することができます。

./run-yamd /usr/src/test/test2/test2

リスト5は、サンプル・プログラムtest2に対してYAMDを使用したときの出力結果を示しています。YAMDは、for ループで境界を越えてのアクセスがあることを示しています。


リスト5. test2に対するYAMDの出力
                
Running /usr/src/test/test2/test2
Temp output to /tmp/yamd-out.1243
*********
./run-yamd: line 101: 1248 Segmentation fault (core dumped)
YAMD version 0.32
Starting run: /usr/src/test/test2/test2
Executable: /usr/src/test/test2/test2
Virtual program size is 1380 K
...
INFO: Normal allocation of this block
Address 0x40025e00, size 512
...
INFO: Normal allocation of this block
Address 0x40028e00, size 512
...
INFO: Normal allocation of this block
Address 0x4002be00, size 512
ERROR: Crash
...
Tried to write address 0x4002c000
Seems to be part of this block:
Address 0x4002be00, size 512
...
Address in question is at offset 512 (out of bounds)
Will dump core after checking heap.
Done.

MEMWATCHとYAMDは、いずれも、便利なデバッグ・ツールですが、それぞれ別々の使い方をする必要があります。MEMWATCHの場合、インクルード・ファイルmemwatch.hを付加し、コンパイル時のフラグを2つオンにする必要があります。YAMDは、リンク・コマンドに -g オプションを指定するだけで済みます。




上に戻る


Electric Fence

Electric Fenceのパッケージは、Linuxのほとんどのディストリビューションに含まれていますが、ダウンロードすることもできます。Electric Fenceは、Bruce Perens作の malloc() デバッグ用ライブラリーです。Electric Fenceは、プログラムで割り当てたメモリーの直後にプロテクト・メモリーを割り当てます。保護柵 (fencepost) エラー (配列境界を越えてのアクセス) が検出されると、そのプログラムは、保護エラーとして、即座に終了させられることになります。Electric Fenceとgdbを組み合わせて利用すると、プロテクト・メモリーにアクセスしようとした行を正確に検出することができます。Electric Fenceは、メモリー・リークを検出することもできます。




上に戻る


シナリオ2: straceの利用

Strace コマンドは、ユーザー・スペース・プログラムが発行したすべてのシステム・コールを表示してくれる強力なツールです。Straceは、システム・コールへの引数と戻り値をシンボリック形式で表示します。Straceは、カーネルから情報を受け取りますが、カーネルを特別な方法でビルドしなければならないわけではありません。トレース情報は、アプリケーション開発者にとってもカーネル開発者にとっても有益な情報として利用することができます。リスト6は、パーティションのフォーマットに失敗している例で、このリストは、make file system (mkfs) の呼び出しに対するstraceの最初の部分を示したものです。Straceは、どの呼び出しが問題を起こしているのかを示しています。


リスト6. mkfsに対するstraceの最初の部分
                
execve("/sbin/mkfs.jfs", ["mkfs.jfs", "-f", "/dev/test1"], &
 ...
 open("/dev/test1", O_RDWR|O_LARGEFILE) = 4
 stat64("/dev/test1", {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0
 ioctl(4, 0x40041271, 0xbfffe128) = -1 EINVAL (Invalid argument)
 write(2, "mkfs.jfs: warning - cannot setb" ..., 98mkfs.jfs: warning -
 cannot set blocksize on block device /dev/test1: Invalid argument )
  = 98
 stat64("/dev/test1", {st_mode=&, st_rdev=makedev(63, 255), ...}) = 0
 open("/dev/test1", O_RDONLY|O_LARGEFILE) = 5
 ioctl(5, 0x80041272, 0xbfffe124) = -1 EINVAL (Invalid argument)
 write(2, "mkfs.jfs: can\'t determine device"..., ..._exit(1)
  = ?

リスト6は、パーティションをフォーマットするための mkfs プログラムが、ioctl 呼び出しでエラーになったことを示しています。ioctl BLKGETSIZE64 がエラーを起こしています。(BLKGET-SIZE64 は、ioctl を呼び出しているソース・コードの中で定義されています。) BLKGETSIZE64 ioctl は、Linuxのすべてのデバイスに追加されつつありますが、上の例では、論理ボリューム・マネージャーが、これをまだサポートしていないというわけです。したがって、mkfsのコードは、BLKGETSIZE64 ioctl 呼び出しがエラーになった場合、代わりに、古い ioctl 呼び出しを呼び出すようにします。それによって、mkfs は、この論理ボリューム・マネージャーを利用できることになります。





上に戻る


シナリオ3: gdbおよびOopsの利用

Free Software Foundation作のデバッガーであるgdbプログラムを、コマンド・ラインまたはData Display Debugger (DDD) などのグラフィカル・ツールから実行することで、エラーを探り出すこともできます。gdbは、ユーザー・スペース・プログラムのデバッグにもLinuxカーネルのデバッグにも使用することができます。ここでは、gdbをコマンド・ラインから利用する方法のみを紹介します。

<gdbプログラム名> というコマンドでgdbを始動します。すると、gdbは、その実行ファイルのシンボルをロードした後、入力プロンプトを表示して、デバッガーを使用できる状態にします。gdbでプロセスを表示させるには、以下の3通りの方法があります。

  • attachコマンドを使って、すでに実行中のプロセスの表示を開始する。 attachは、そのプロセスを停止します。
  • runコマンドを使ってプログラムを実行し、そのプログラムを最初からデバッグする。
  • 既存のコア・ファイルで、プロセスが終了したときのそのプロセスの状態を調べる。コア・ファイルを参照するには、以下のようにしてgdbを始動します。

    gdb programname corefilename

    コア・ファイルを使ってデバッグを行うには、コア・ファイルの他に、プログラムの実行ファイルとソース・ファイルが必要です。コア・ファイルを添えてgdbを始動するには、-cオプションを指定します。

    gdb -c core programname

    gdbは、コード中の何行目がコア・ダンプを起こさせたのかを表示します。

プログラムを実行する場合、あるいは、すでに実行中のプログラムにattachを行う場合、先にソース・コード中のバグの潜んでいそうな部分を表示させ、ブレークポイントを設定しておいてから、プログラムのデバッグを開始します。gdbには、幅広いオンライン・ヘルプおよび詳細なチュートリアルが用意されており、help コマンドを使って表示させることができます。




上に戻る


kgdb

kgdbプログラム (gdb経由のリモート・ホストLinuxカーネル・デバッガー) は、gdbを使ってLinuxのカーネルをデバッグする仕掛けを提供します。kgdbプログラムはカーネルの拡張で、拡張されたカーネルが稼動しているマシンにリモートホストで実行しているgdbから接続出来るようにします。kgdbでカーネルを拡張することで、カーネルにブレークをかけたり、ブレーク・ポイントを設定したり、データを確認したりすることができます (アプリケーション・プログラムに対してgdbを使用するのと同様のことができます)。このパッチの主な機能の1つは、gdbを実行しているリモート・ホストが、(デバッグすべきカーネルを実行している) ターゲット・マシンに、ブート処理中に接続することです。このため、できるだけ早い段階でデバッグを開始できます。ちなみに、このパッチはLinuxのカーネルに機能を追加しますので、gdbを使ってLinuxのカーネルをデバッグすることもできます。

kgdbを使うにはマシンが2台必要です。1台は開発用のマシン、もう1台はテスト・マシンです。それぞれのシリアル・ポートを使って、シリアル・ケーブル (ヌル・モデム・ケーブル) で2台のマシンを接続します。デバッグしたいカーネルはテスト・マシンで実行し、gdbは開発マシンで実行します。gdbは、シリアル・ケーブルを介して、デバッグ対象のカーネルと通信を行います。

kgdbのデバッグ環境は、以下の手順でセットアップします。

  1. Linuxカーネルのバージョンに合わせて、適当なパッチをダウンロードする。
  2. そのコンポーネントをカーネルに組み込む形でビルドする。 kgdbを使う場合、これが最も簡単な方法です。(カーネルのほとんどのコンポーネントは、ビルドの方法に2通りがあります。モジュールとしてビルドするか、直接カーネルにビルドするかです。たとえば、Journaled File System (JFS) は、モジュールとしてビルドすることもできれば、カーネルに直接ビルドすることもできます。gdbのパッチを使う場合、JFSを直接カーネルにビルドすることも可能です。)
  3. カーネルにパッチを当て、カーネルを再ビルドする。
  4. .gdbinitというファイルを作成し、それをカーネル・ソースのサブディレクトリー (/usr/src/linux) に入れる。.gdbinitファイルには、以下の4行を書き込んでおきます。
    • set remotebaud 115200
    • symbol-file vmlinux
    • target remote /dev/ttyS0
    • set output-radix 16

  5. カーネルのブート時にどのカーネルを使うかを選択するためのブート・ロードであるliloに、append=gdbの行を追加する。
    • image=/boot/bzImage-2.4.17
    • label=gdb2417
    • read-only
    • root=/dev/sda8
    • append="gdb gdbttyS=1 gdb-baud=115200 nmi_watchdog=0"

リスト7は、開発マシン上にビルドしたカーネルやモジュールを、テスト・マシンに引き出してくるためのスクリプトの例です。以下の項目は、変更する必要があります。

  • best@sfb: ユーザーIDとマシン名。
  • /usr/src/linux-2.4.17: カーネル・ソース・ツリーのディレクトリー。
  • bzImage-2.4.17: テスト・マシンでブートしたいカーネルの名前。
  • rcprsync: カーネルがビルドされたマシンで、実行できるようにする必要がある。

リスト7. カーネルと各種モジュールをテスト・マシンに引き出してくるためのスクリプト
                
set -x
rcp best@sfb: /usr/src/linux-2.4.17/arch/i386/boot/bzImage /boot/bzImage-2.4.17
rcp best@sfb:/usr/src/linux-2.4.17/System.map /boot/System.map-2.4.17
rm -rf /lib/modules/2.4.17
rsync -a best@sfb:/lib/modules/2.4.17 /lib/modules
chown -R root /lib/modules/2.4.17
lilo

これで、開発マシンのディレクトリーをカーネル・ソース・ツリーの開始ディレクトリーに変更すれば、gdbプログラムを始動できるようになりました。上の例では、カーネル・ソース・ツリーは /usr/src/linux-2.4.17にあります。gdb とタイプしてプログラムを始動します。

すべて問題なく進めば、テスト・マシンはブート処理で停止するはずです。そこで、gdb コマンドの cont を入力して、ブート処理を続行します。このとき、ヌル・モデム・ケーブルを間違ったシリアル・ポートに接続してしまうという問題がよく起こります。gdbが始動しない場合は、ポートを2番目のシリアルに切り換えると、gdbが始動するようになるはずです。




上に戻る


kgdbを使ってカーネルの問題をデバッグする

リスト8は、109行目でヌル・ポインター例外を起こさせ、セグメンテーション障害を発生させるために、jfs_mount.cファイルのソースに手を加えたコードです。


リスト8. jfs_mount.cに手を加えたコード
                
int jfs_mount(struct super_block *sb)
{
...
int ptr;                        /* line 1 added */
jFYI(1, ("\nMount JFS\n"));
/ *
* read/validate superblock
* (initialize mount inode from the superblock)
* /
if ((rc = chkSuper(sb))) {
                goto errout20;
        }
108     ptr=0;                  /* line 2 added */
109     printk("%d\n",*ptr);    /* line 3 added */

リスト9は、ファイル・システムに対してマウント・コマンドを発行したときのgdbの例外を示したものです。kgdbで利用できるコマンドには、データ構造や変数の値を表示したり、システム中のすべてのタスクの状態や、タスクがどこでスリープし、どこでCPUを消費しているのかを確認するためのコマンドなど、いろいろなものがあります。リスト9には、上の問題に対してバック・トレースで得られた情報が示されています。where コマンドは、バック・トレースを行うためのもので、コード中の停止位置に達するまでに実行された一連の呼び出しを教えてくれます。


リスト9. gdbの例外とバック・トレース
                
mount -t jfs /dev/sdb /jfs

Program received signal SIGSEGV, Segmentation fault.
jfs_mount (sb=0xf78a3800) at jfs_mount.c:109
109             printk("%d\n",*ptr);
(gdb)where
#0 jfs_mount (sb=0xf78a3800) at jfs_mount.c:109
#1 0xc01a0dbb in jfs_read_super ... at super.c:280
#2 0xc0149ff5 in get_sb_bdev ... at super.c:620
#3 0xc014a89f in do_kern_mount ... at super.c:849
#4 0xc0160e66 in do_add_mount ... at namespace.c:569
#5 0xc01610f4 in do_mount ... at namespace.c:683
#6 0xc01611ea in sys_mount ... at namespace.c:716
#7 0xc01074a7 in system_call () at af_packet.c:1891
#8 0x0 in ?? ()
(gdb)

今度は、デバッガーのセットアップなしで、同じJFSのセグメンテーション障害の問題を分析することにします。kgdb拡張していないカーネル環境でリスト8のコードを実行したときにカーネルが生成するOopsを使用します。




上に戻る


Oops分析

Oops (つまり、パニック) メッセージには、CPUレジスターの内容など、システム障害の詳細が示されます。従来Linuxでシステム・クラッシュをデバッグするには、クラッシュ時にシステム・コンソールに表示されるOopsメッセージの詳細を分析するという手法がとられてきました。そうした詳細が得られれば、そのメッセージをksymoopsユーティリティーに渡してやることができます。そうすればksymoopsがコードを命令に変換し、スタックの値をカーネルのシンボルに対応付けてくれます。多くの場合、これで、障害の原因を調べるのに充分な情報が得られます。ちなみに、Oopsメッセージにコア・ファイルは含まれません。

いま、システムがOopsメッセージを1個作成したとします。みなさんがそのコードの開発者なら、問題を解決したいと考え、そのOopsメッセージの原因を突き止めたいでしょうし、開発した人が別の人なら、そのOopsメッセージを表示したコードを開発した人に、その問題に関するほとんどの情報を提供し、折よく問題を解決してもらいたいと思うでしょう。Oopsメッセージは、方程式の一部ではありますが、ksymoopsプログラムにかけなければ、それは役には立ちません。以下の図は、Oopsメッセージのフォーマット処理の過程を示したものです。


Oopsメッセージのフォーマット処理
図1

ksymoopsは、いろいろな情報を必要とします。Oopsメッセージ出力、実行中のカーネルのSystem.mapファイル、/proc/ksyms、vmlinux、/proc/modulesといった情報です。カーネル・ソースの /usr/src/linux/Documentation/oops-tracing.txtあるいはksymoopsのmanページには、ksymoopsの使い方についての詳細な説明があります。Ksymoopsは、コード部を逆アセンブルし、エラーを起こしている命令を指示するとともに、そのコードがどのように呼び出されたのかを示すトレース・セクションを表示します。

まずは、Oopsメッセージをファイルに取り込み、ksymoopsユーティリティーで実行できるようにします。リスト10は、JFSのマウント・コードにリスト8の3行を追加したことで問題を起こすようになっているJFSファイル・システムに対してマウントを行うことで作成されたOopsを示しています。


リスト10. ksymoopsで処理されたOops
                
ksymoops 2.4.0 on i686 2.4.17. Options used
... 15:59:37 sfb1 kernel: Unable to handle kernel NULL pointer dereference at
virtual address 0000000
... 15:59:37 sfb1 kernel: c01588fc
... 15:59:37 sfb1 kernel: *pde = 0000000
... 15:59:37 sfb1 kernel: Oops: 0000
... 15:59:37 sfb1 kernel: CPU:    0
... 15:59:37 sfb1 kernel: EIP:    0010:[jfs_mount+60/704]

... 15:59:37 sfb1 kernel: Call Trace: [jfs_read_super+287/688] 
[get_sb_bdev+563/736] [do_kern_mount+189/336] [do_add_mount+35/208]
[do_page_fault+0/1264]
... 15:59:37 sfb1 kernel: Call Trace: [<c0155d4f>]...
... 15:59:37 sfb1 kernel: [<c0106e04 ...
... 15:59:37 sfb1 kernel: Code: 8b 2d 00 00 00 00 55 ...

>>EIP; c01588fc <jfs_mount+3c/2c0> <=====
...
Trace; c0106cf3 <system_call+33/40>
Code; c01588fc <jfs_mount+3c/2c0>
00000000 <_EIP>:
Code; c01588fc <jfs_mount+3c/2c0>  <=====
   0: 8b 2d 00 00 00 00 	mov 	0x0,%ebp    <=====
Code; c0158902 <jfs_mount+42/2c0>
   6:  55 			push 	%ebp

次に、jfs_mountで問題を起こしている行がどこなのかを調べる必要があります。Oopsメッセージから、オフセット3cの命令が問題を起こしていることがわかります。1つのやり方として、jfs_mount.oファイルをobjdumpユーティリティーにかけ、オフセット3cを調べることで、このことがわかります。Objdumpは、モジュール関数を逆アセンブルし、Cのソース・コードからどんなアセンブラー命令が作成されたのかを調べるために使用されます。リスト11は、objdumpの出力を示したもので、そこでjfs_mountのCのコードを調べれば、(3cにおける)ヌルは109行目で発生していることがわかります。オフセット3cは、そこが問題の原因であるとOopsメッセージが指示している箇所であり、重要な箇所です。


リスト11. jfs_mountのアセンブラー・リスト
                
109     printk("%d\n",*ptr);

objdump jfs_mount.o

jfs_mount.o:    file format elf32-i386

Disassembly of section .text:

00000000 <jfs_mount>:
   0:55                         push %ebp
  ...
  2c:   e8 cf 03 00 00     call    400 <chkSuper>
  31:   89 c3                   mov     %eax,%ebx
  33:   58                      pop     %eax
  34:   85 db                   test    %ebx,%ebx
  36:   0f 85 55 02 00 00 jne   291 <jfs_mount+0x291>
  3c:   8b 2d 00 00 00 00 mov   0x0,%ebp << problem line above
  42:   55                      push    %ebp




上に戻る


kdb

Linuxカーネル・デバッガー (kdb) は、Linuxカーネルに対するパッチであり、システムが動作しているときのカーネルのメモリーやデータ構造を確認するのに利用できます。kdbは、マシンを2台必要としませんが、kgdbのようなソース・レベルのデバッグを行うことはできません。kdbでは、システムの基本的なデータ構造の名前かアドレスを指定することで、そうしたデータ構造をフォーマットしたり表示したりするためのコマンドを追加することができます。現在のコマンド・セットでは、カーネルの動作を、以下のような方法で制御することができます。

  • プロセッサーのシングル・ステップ動作
  • 指定した命令を実行する時点での停止
  • 指定した仮想メモリー・アドレスのアクセス (または書き換え) の時点での停止
  • 入出力アドレス空間のレジスターへのアクセス時点での停止
  • 現在アクティブなタスクおよび他のすべてのタスク (プロセスID単位) に対するスタックのバック・トレース
  • 命令の逆アセンブル



上に戻る


シナリオ4: magic key sequenceを使ってバック・トレースを行う

メモリー・オーバーランの検出

何千回か呼び出しを行うと割り当てオーバーランが起こるというのは、嫌なバグです。

私のチームも、ある奇妙なメモリーのデータ化けの原因を究明するのに、すごく長い時間を費やしたことがありました。そのアプリケーションは、われわれの開発ワークステーションでは問題なく動いていたのですが、新しい製品版のワークステーションで malloc() を2百万回呼び出すとエラーが発生しました。原因は、呼び出しを百万回行ったときのオーバーランの回帰でした。新しいシステムでは、malloc() 用の予約領域の配置が異なり、エラーを起こすメモリーが異なる場所に位置しており、オーバーランを起こしたときに違うところを破壊することで、問題が発生しているのでした。

われわれは、この問題を解決するために、デバッガーを使ったり、ソース・コードにトレースをかけたするなど、いろいろな手法を用いました。私の経歴の中では、この頃から、メモリー・デバッグ・ツールを使ってこの種の問題を速くかつ効率よく解決することを考えるようになりました。新しいプロジェクトを開始するときに私が最初に行うことの1つは、MEMWATCHとYAMDの両方を実行してみて、メモリー管理の問題を検出できるかどうかを確かめることです。

メモリー・リークは、アプリケーションによく起こる問題ですが、本稿で紹介したツールを使えば、そうした問題も解決することができます。

キーボードの機能は生きているが、Linuxはハングアップしているという場合は、以下の方法が、ハングアップの解決に役立ちます。以下の手順に従うことで、magic key sequenceを利用して、現在実行中のプロセスの他すべてのプロセスのバック・トレースを表示することができます。

  1. 実行中のカーネルは、CONFIG_MAGIC_SYS-REQ を有効にしてビルドしたものでなければなりません。また、テキスト・モードにしておく必要もあります。CLTR+ALT+F1でテキスト・モードにすることができ、CLTR+ALT+F7でX Windowsに戻すことができます。
  2. テキスト・モードで、<ALT+ScrollLock> を押し、さらに <Ctrl+ScrollLock> を押します。すると、magic keystrokesは、現在実行中のプロセス他すべてのプロセスのそれぞれのスタック・トレースを表示します。
  3. /var/log/messagesの内容を確認します。すべてが正しくセットアップされていれば、システムがシンボリックなカーネル・アドレスに変換してくれるはずです。バック・トレースが /var/log/messagesファイルに書き出されているはずです。

結論

Linuxでプログラムをデバッグする場合、いろいろなツールが利用できます。本稿で紹介したツールは、さまざまなコーディングの問題を解決するのに役立てることができます。メモリー・リーク、オーバーランなどのの発生箇所を検出するツールを利用すれば、メモリー管理の問題を解決することができます。そのようなツールとして、MEMWATCHとYAMDは役に立ちます。

カーネルにパッチを当ててgdbで扱えるようにすることで、私が開発に携わっているLinuxのファイル・システムのいろいろな問題を解決することができました。また、straceユーティリティーは、ファイル・システムがどのシステム・コールでエラーを起こすのかを調べるのに役に立ちました。みなさんも、Linux内のバグ潰しを行うときには、これらのツールを試してみてはいかがでしょうか。



参考文献



著者について

Steve Bestは、テキサス州オースチンにあるIBM Linuxテクノロジー・センターに勤務しています。現在、LinuxプロジェクトのJournaled File System (JFS) の開発に携わっています。Steveは、オペレーティング・システム開発のさまざまな側面に関ってきており、ファイル・システム、国際化対応、セキュリティーの分野を専門としてます。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


    日本IBMについて プライバシー お問い合わせ