helpサブコマンド

今回より、crashコマンドを使用して、ダンプの解析方法を解説していきます。本講座は、ダンプの解析方法を解説するのが目的であり、crashコマンドの解説をすることが目的ではありませんので、crashコマンドの機能について網羅的に解説することはしません。crashコマンドの機能については、helpサブコマンド(以下、単にコマンド)を実行すると一覧が出てくるので参考にしてください。

helpコマンドの実行例(1)

crash> help
* files mod runq union
alias foreach mount search vm
ascii fuser net set vtop
bt gdb p sig waitq
btop help ps struct whatis
dev irq pte swap wr
dis kmem ptob sym q
eval list ptov sys
exit log rd task
extend mach repeat timer
crash version: 4.0-4.7 gdb version: 6.1
For help on any command above, enter “help “.
For help on input options, enter “help input”.
For help on output options, enter “help output”.

helpコマンドでは、各コマンドの詳細な説明や使用例を見ることができます。本講座で出てきたコマンドについても、適宜、helpコマンドで詳細を確認するようにしてください。

helpコマンドの実行例(2)

crash> help rd
NAME
rd – read memory
SYNOPSIS
rd [-dDsupxmf][-8|-16|-32|-64][-o offs][-e addr] [address|symbol] [count]
DESCRIPTION
This command displays the contents of memory, with the output formatted
in several different manners. The starting address may be entered either
symbolically or by address. The default output size is the size of a long
data type, and the default output format is hexadecimal. When hexadecimal
output is used, the output will be accompanied by an ASCII translation.

カーネルのデータの内容確認

それでは、いよいよお待ちかねのダンプ解析に入ります。まず最初は、カーネルのデータの内容を確認する方法について見ていきます。
メモリの内容を見るには、rdコマンドを使用します。一例として、変数num_physpagesの値を確かめて見ましょう。変数のアドレスは、symコマンドで分かります。rdコマンドの引数には、仮想アドレスを指定します。-pオプションを使用して、物理アドレスの指定をすることも可能ですが、実際の解析において、物理アドレスの参照が必要なケースはあまり多くないと思います。rdコマンドの引数には、シンボル名を直接指定することもできます。

変数の値の参照例

crash> sym num_physpages
c0793684 (B) num_physpages
crash> rd c0793684
c0793684: 0003fe70 p…
crash> rd -p 0x793684
793684: 0003fe70 p…
crash> rd num_physpages
c0793684: 0003fe70 p…
crash>

構造体の内容

構造体の内容も見てみましょう。一例として、変数mem_mapからポイントされるpage構造体(配列の先頭要素)を見てみます。rdコマンドでワード数を指定すれば、メモリの内容をいくらでも見れます。ソースコードと照らし合わせればメンバの値が確認できます。実は、構造体の形は、わざわざソースコードを見なくてもstructコマンドで分かります。これだけでもずいぶんありがたいことですが、なんと、structコマンドでアドレスを指定すれば、メンバの値を自動的に整形して出してくれます。

構造体の内容の表示例(1)

crash> rd mem_map
c0793690:  c9000000                              ....
crash> rd c9000000 8
c9000000:  00000400 00000001 ffffffff 00000000   ................
c9000010:  00000000 00000000 c9000018 c9000018   ................
crash> struct -o page
struct page {
   [0] long unsigned int flags;
   [4] atomic_t _count;
   [8] atomic_t _mapcount;
       union {
           struct {...};
       };
[20] long unsigned int index;
[24] struct list_head lru;
}
SIZE: 32
crash> struct page c9000000
struct page {
  flags = 1024,
  _count = {
    counter = 1
  },
  _mapcount = {
    counter = -1
  },
  {
    {
      private = 0,
      mapping = 0x0
    }
  },
  index = 0,
  lru = {
    next = 0xc9000018,
    prev = 0xc9000018
  }
}
crash>

筆者のように、ノートに一生懸命【図1】のような絵を描いて、16進ダンプで解析を行っていた世代の人間にとっては、ありがたすぎて涙が出てくるくらい便利です。今では、task_struct構造体のように殺人的に巨大な構造体もありますから、このような機能がなければ生きていけません。余談ですが、筆者はソースコードの解析を行う際もcrashコマンドを実行して、structコマンドを使うことがよくあります。ヘッダファイルを確認するよりもその方が楽ですから。

カーネル仮想領域

カーネルの仮想アドレスを指定して、データの内容を見てきましたが、これまでのところ、ストレートマップ領域にあるデータだけでした(すなわち、0xc0000000を引けば物理アドレスに変換できます)。カーネル仮想領域(vmalloc領域)にあるデータについても見てみましょう。下の例は、scsi_modカーネルモジュールに対するmodule構造体の内容を見てみたものです。このように問題なく見れました。

構造体の内容の表示例(2)

crash> struct module f891fa00
struct module {
  state = MODULE_STATE_LIVE,
  list = {
    next = 0xf8942e84,
    prev = 0xf883a784
  },
  name = "scsi_mod0000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000",
 
  mkobj = {
    kobj = {
      k_name = 0xf891fa4c "scsi_mod",
      name = "scsi_mod0000000000000000000000",
      kref = {
        refcount = {
          counter = 2
        }
      },
      entry = {
        next = 0xf883a7e4,
        prev = 0xf8942ee4
      },

第3回の講座で解説したように、ダンプの中のデータは物理アドレスでアクセスします。つまり、crashコマンドの中で仮想アドレスから物理アドレスへの変換が行われていることを意味します。仮想アドレスから物理アドレスへの変換は、mm_struct構造体init_mmのpgdメンバが示すページテーブルを追うことによって行うことができます。init_mmは、ストレートマップ領域にあり、ページテーブルの内容は物理アドレスですから、init_mmのアドレスさえ分かっていばいいわけです。仮想アドレスから物理アドレスへの変換の様子は、vtopコマンドの出力により伺い知ることができます。

 

vtopコマンドの表示例

crash> struct mm_struct.pgd init_mm
pgd = 0xc0712000,
crash> vtop f891fa00
VIRTUAL PHYSICAL
f891fa00 3fa54a00 PAGE DIRECTORY: c0712000
PGD: c0712f88 => 98f8067
PMD: c0712f88 => 98f8067
PTE: 98f847c => 3fa54163
PAGE: 3fa54000 PTE PHYSICAL FLAGS
3fa54163 3fa54000 (PRESENT|RW|ACCESSED|DIRTY|GLOBAL) PAGE PHYSICAL MAPPING INDEX CNT FLAGS
c97f4a80 3fa54000 ——- —– 1 c0000000
crash>

カーネルモジュールの情報

さて、structコマンドで、構造体の内容を自動的に表示してくれることが分かりましたが、構造体の形式についてはどこから情報を得ているのでしょうか。それは、crashコマンドの引数で指定したvmlinuxの中に入っているデバッグ情報から得ています。vmlinuxはカーネル本体ですが、カーネルモジュールの情報は入っていません。カーネルモジュール内で使用されている構造体については、きちんと表示できるのでしょうか。試しにscsiドライバ(scsi_modカーネルモジュール)で使用されている構造体について見てみましょう。

crash> struct scsi_cmnd
struct: invalid data structure reference: scsi_cmnd
crash>

残念ながら、失敗しました。これは、scsi_modカーネルモジュールのデバッグ情報がないためで、当然と言えば当然の結果です。ダンプ採取時点でロードされていたカーネルモジュールの一覧は、modコマンドで得ることができます。

modコマンドの表示例(読み込み前)

crash> mod
MODULE NAME SIZE OBJECT FILE
f8820980 pcspkr 7105 (not loaded) [CONFIG_KALLSYMS]
f8824780 serio_raw 10693 (not loaded) [CONFIG_KALLSYMS]
f882c100 uhci_hcd 25421 (not loaded) [CONFIG_KALLSYMS]
f8833880 ohci_hcd 23261 (not loaded) [CONFIG_KALLSYMS]
f883a780 sd_mod 22977 (not loaded) [CONFIG_KALLSYMS]
f883ea80 i2c_i801 11469 (not loaded) [CONFIG_KALLSYMS]
f8847e00 ehci_hcd 32845 (not loaded) [CONFIG_KALLSYMS]
f884e280 ata_piix 17609 (not loaded) [CONFIG_KALLSYMS]
f8853300 snd_page_alloc 13641 (not loaded) [CONFIG_KALLSYMS]
f885ac80 snd_seq_dummy 7877 (not loaded) [CONFIG_KALLSYMS]
f886aa80 jbd 56553 (not loaded) [CONFIG_KALLSYMS]
f8875000 parport 37513 (not loaded) [CONFIG_KALLSYMS]
f887ca80 i2c_core 23745 (not loaded) [CONFIG_KALLSYMS]
f891fa00 scsi_mod 130637 (not loaded) [CONFIG_KALLSYMS]

カーネルモジュールのデバッグ情報を得るためには、デバッグ情報付きのカーネルモジュール(すなわち、コンパイル時に-gオプション付きでビルドされたカーネルモジュール)をmodコマンドの-sオプションを使用して読み込みます。

mod -s コマンド実行例

crash> mod -s scsi_mod /usr/lib/debug/lib/modules/2.6.18-8.el5/kernel/drivers/scsi/scsi_mod.ko.debug
MODULE NAME SIZE OBJECT FILE
f891fa00 scsi_mod 130637 /usr/lib/debug/lib/modules/2.6.18-8.el5/kernel/drivers/scsi/scsi_mod.ko.debug
crash>

上の「mod -s コマンド実行例」で使用しているscsi_mod.ko.debugは、カーネルのdegubinfoパッケージ(kernel-debuginfo-common-2.6.18-8.el5.i686.rpm、kernel-PAE-debuginfo-2.6.18-8.el5.i686.rpm)をインストールして得られたものです。実は、カーネルのdebuginfoパッケージがインストールされていれば、modコマンドの-Sオプションでロードされているすべてのモジュールに対して、一発で読み込みを行ってくれます。

mod -S コマンド実行例

crash> mod -S
MODULE NAME SIZE OBJECT FILE
f8820980 pcspkr 7105 /lib/modules/2.6.18-8.el5/kernel/drivers/input/misc/pcspkr.ko
f8824780 serio_raw 10693 /lib/modules/2.6.18-8.el5/kernel/drivers/input/serio/serio_raw.ko
f882c100 uhci_hcd 25421 /lib/modules/2.6.18-8.el5/kernel/drivers/usb/host/uhci-hcd.ko
f8833880 ohci_hcd 23261 /lib/modules/2.6.18-8.el5/kernel/drivers/usb/host/ohci-hcd.ko
f883a780 sd_mod 22977 /lib/modules/2.6.18-8.el5/kernel/drivers/scsi/sd_mod.ko
f883ea80 i2c_i801 11469 /lib/modules/2.6.18-8.el5/kernel/drivers/i2c/busses/i2c-i801.ko
f8847e00 ehci_hcd 32845 /lib/modules/2.6.18-8.el5/kernel/drivers/usb/host/ehci-hcd.ko
f884e280 ata_piix 17609 /lib/modules/2.6.18-8.el5/kernel/drivers/scsi/ata_piix.ko
f8853300 snd_page_alloc 13641 /lib/modules/2.6.18-8.el5/kernel/sound/core/snd-page-alloc.ko
f885ac80 snd_seq_dummy 7877 /lib/modules/2.6.18-8.el5/kernel/sound/core/seq/snd-seq-dummy.ko
f886aa80 jbd 56553 /lib/modules/2.6.18-8.el5/kernel/fs/jbd/jbd.ko
f8875000 parport 37513 /lib/modules/2.6.18-8.el5/kernel/drivers/parport/parport.ko
f887ca80 i2c_core 23745 /lib/modules/2.6.18-8.el5/kernel/drivers/i2c/i2c-core.ko
f891fa00 scsi_mod 130637 /lib/modules/2.6.18-8.el5/kernel/drivers/scsi/scsi_mod.ko

目的のカーネルモジュールのデバッグ情報が読み込まれれば、カーネルモジュール内の構造体についてもstructコマンドで表示できるようになります。先ほど失敗したscsi_cmnd構造体についても、以下のとおりです。

crash> struct scsi_cmnd
struct scsi_cmnd {
    struct scsi_device *device;
    struct list_head list;
    struct list_head eh_entry;
    int eh_eflags;
    void (*done)(struct scsi_cmnd *);
    long unsigned int serial_number;
    long unsigned int jiffies_at_alloc;
    int retries;
    int allowed;
    int timeout_per_command;
    unsigned char cmd_len;
    enum dma_data_direction sc_data_direction;
    unsigned char cmnd[16];
    unsigned int request_bufflen;
    struct timer_list eh_timeout;
    void *request_buffer;
    short unsigned int use_sg;
    short unsigned int sglist_len;
    unsigned int underflow;
    unsigned int transfersize;
    int resid;
    struct request *request;
    unsigned char sense_buffer[96];
    void (*scsi_done)(struct scsi_cmnd *);
    struct scsi_pointer SCp;
    unsigned char *host_scribble;
    int result;
    unsigned char tag;
    long unsigned int pid;
}
SIZE: 280
crash>

例えば、独自のカーネルモジュールを開発してロードしているようなケースでは、modコマンドの-sオプションで個別に読み込むことになります。crashでダンプ解析することを想定して、最初から-g付きでビルドしておくとよいでしょう。

今回は、ダンプに格納されたメモリの内容を見る方法について説明しました。基本的には、これだけ知っているだけで、ダンプを解析して障害原因の調査ができるはずです。もちろん、カーネルそのものが分かっていることは前提ではありますが。
次回は、解析作業における手間を軽減するための技を紹介していきたいと思っています。