Solaris モジューラデバッガ

第 6 章 カーネルメモリーアロケータを使用するデバッギング

Solaris カーネルメモリー (kmem) アロケータでは、カーネルクラッシュダンプの分析を容易にする強力なデバッギング機能のセットを用意しています。この章では、これらのデバッギング機能について説明し、またこのアロケータ用に特に設計された MDB dcmd と walker について説明します。Bonwick (「関連マニュアルと論文」を参照) に、このアロケータ自体の原理の概要が説明されています。アロケータデータ構造体の定義については、ヘッダーファイル <sys/kmem_impl.h> を参照してください。システム上で kmem デバッギング機能を有効にして問題の分析能力を向上させたり、あるいは開発システム上で kmem デバッギング機能を有効にしてカーネルソフトウェアやデバイスドライバのデバッギングを支援したりすることができます。


注 -

このマニュアルは、Solaris 8 での実装を反映しています。つまり、現在のカーネルの実装を反映しているので、過去または将来のリリースに対しては関連せず、適切でない、あるいは適用できない場合があります。これはどんな種類の公開インタフェースを定義するものでもありません。このカーネルメモリーアロケータに関して提供されている情報は、将来の Solaris リリースでは変更される場合があります。


はじめに − サンプルクラッシュダンプの作成

この節では、サンプルクラッシュダンプの作成方法およびそれを調べるために MDB を起動する方法について説明します。

kmem_flags の設定

カーネルメモリーアロケータには多くの高度なデバッギング機能が含まれていますが、それらは性能の低下をもたらす可能性があるので、デフォルトでは有効になっていません。このマニュアルの例を実行するには、これらの機能を有効にする必要があります。性能の低下やほかの問題を引き起こす可能性があるため、これらの機能を有効にするのは、テストシステムに対してだけにしておくべきです。

アロケータのデバッギング機能は、調整可能な kmem_flags によって制御されます。この機能を使用する前に、kmem_flagskmem_flags が次のように正しく設定されていることを確認します。

# mdb -k
> kmem_flags/X
kmem_flags:
kmem_flags:     f

kmem_flags が 'f' に設定されていない場合には、次の行を /etc/system に追加して、システムを再起動する必要があります。

set kmem_flags=0xf

システムを再起動し、kmem_flags が 'f' に設定されていることを確認します。システムを稼動状態に戻す前に、この /etc/system の変更を元に戻すことを忘れないようにしてください。

クラッシュダンプの保存

次に、クラッシュダンプが正しく設定されていることを確認します。最初に、dumpadm が、カーネルクラッシュダンプを保存し、savecore が有効であるように設定されていることを確認します。クラッシュダンプパラメタの詳細については、dumpadm(1M) のマニュアルページを参照してください。

# dumpadm
		      Dump content: kernel pages
		       Dump device: /dev/dsk/c0t0d0s1 (swap)
		Savecore directory: /var/crash/testsystem
		  Savecore enabled: yes

次に、reboot(1M) に '-d' フラグを設定してシステムを再起動します。これによってカーネルが強制的にパニック状態になり、クラッシュダンプが保存されます。


# reboot -d
Sep 28 17:51:18 testsystem reboot: rebooted by root

panic[cpu0]/thread=70aacde0: forced crash dump initiated at user request

401fbb10 genunix:uadmin+55c (1, 1, 0, 6d700000, 5, 0)
		%l0-7: 00000000 00000000 00000000 00000000 00000000 00000000 00000000
		00000000
...

システムが再起動されたら、クラッシュダンプが成功したことを確認します。

$ cd /var/crash/testsystem
$ ls
bounds    unix.0    unix.1    vmcore.0  vmcore.1

ダンプディレクトリにダンプが見当たらない場合には、このパーティションが容量不足である可能性があります。スペースを解放し、root として手動で savecore(1M) を実行し、続いてダンプを保存することができます。ダンプディレクトリに複数のクラッシュダンプが含まれている場合には、今作成したクラッシュダンプは、変更時刻が最新である unix.[n]vmcore.[n] のペアになります。

MDB の起動

次に、作成したクラッシュダンプに対して mdb を実行し、その状態をチェックします。

$ mdb unix.1 vmcore.1
Loading modules: [ unix krtld genunix ip nfs ipc ]
> ::status
debugging crash dump vmcore.1 (32-bit) from testsystem
operating system: 5.8 Generic (sun4u)
panic message: forced crash dump initiated at user request

このマニュアルに示す例では、32 ビットカーネルからのクラッシュダンプを使用します。ここに示す手法はすべて 64 ビットカーネルにも適用可能であり、ポインタ (32 ビットシステムと 64 ビットシステムではサイズが異なる) を固定サイズ量 (カーネルデータモデルに関して不変) と区別するよう注意が払われています。

ここに示す例の生成には、Sun Ultra-1 ワークステーションを使用しました。結果は、使用するアーキテクチャとシステムのモデルによって異なる場合があります。

アロケータの基礎

カーネルメモリーアロケータの仕事は、仮想記憶領域を他のカーネルサブシステムに区分けすることです。これらのカーネルサブシステムは、通常、クライアントと呼ばれます。この節では、アロケータの操作の基礎を説明し、また、このマニュアルで後で使用するいくつかの用語を説明します。

バッファの状態

カーネルメモリーアロケータが作用する領域は、カーネルヒープを構成する仮想記憶のバッファの集まりです。これらのバッファは、キャッシュと呼ばれる一様なサイズと目的を持ったセットにグループ化されます。各キャッシュには、バッファのセットが含まれています。これらのバッファの一部は現在未使用です。つまり、これらはまだアロケータのクライアントに割り当てられていません。残りのバッファは割り当て済みです。つまり、そのバッファへのポインタが、アロケータのクライアントに提供されています。アロケータのクライアントが、割り当てられているバッファへのポインタを保持していない場合には、そのバッファは解放することができないので、リークしていると言われます。リークしているバッファは、カーネル資源を無駄使いしている正しくないコードを示しています。

トランザクション

kmem トランザクションとは、バッファの割り当て済み状態と未使用状態の間の移行のことです。アロケータは、各トランザクションの一部として、バッファの状態が有効であることを確認することができます。さらに、アロケータには、事後分析のためにトランザクションを記録しておく機能があります。

スリーピング割り当てと非スリーピング割り当て

標準 C ライブラリの malloc(3C) 関数とは異なり、カーネルメモリーアロケータは、ブロックする (またはスリープする) ことができ、クライアントの要求を満たすのに十分な仮想記憶が使用可能になるまで待機することができます。これは、kmem_alloc(9F) の「flag」パラメタによって制御されます。KM_SLEEP フラグが設定されている kmem_alloc(9F) への呼び出しは、決して失敗することはありません。この呼び出しは、資源が使用可能になるまでいつまでも待機します。

カーネルメモリーキャッシュ

カーネルメモリーアロケータは、管理しているメモリーをキャッシュのセットに分割します。すべての割り当てはこれらのキャッシュから供給され、これらのキャッシュは kmem_cache_t データ構造体によって表されます。各キャッシュは固定のバッファサイズを持っており、これはそのキャッシュが満たす最大割り当てサイズを表します。各キャッシュは、管理するデータのタイプを示す文字列による名前を持っています。

一部のカーネルメモリーキャッシュは特殊な目的用で、特定の種類のデータ構造体だけを割り当てるために初期化されます。この一例を挙げると、"thread_cache" があります。これは、kthread_t タイプの構造体だけを割り当てます。このキャッシュのメモリーは、kmem_cache_alloc() 関数によってクライアントに割り当てられ、kmem_cache_free() 関数によって解放されます。


注 -

kmem_cache_alloc()kmem_cache_free() は公開 DDI インタフェースではありません。これらは Solaris の将来のリリースでは変更または削除される場合があるので、これらに依存したコードを作成しないでください。


"kmem_alloc_" で始まる名前を持つキャッシュは、カーネルの汎用メモリー割り当てスキーマを実装します。これらのキャッシュは、kmem_alloc(9F)kmem_zalloc(9F) のクライアントにメモリーを提供します。これらの各キャッシュは、そのサイズが、そのキャッシュのバッファサイズと次に小さいキャッシュのバッファサイズの間にあるという要求を満たします。たとえば、このカーネルは kmem_alloc_8kmem_alloc_16 キャッシュを持っています。この場合、kmem_alloc_16 キャッシュは、9 〜 16 バイトのメモリーを要求するすべてのクライアント要求を処理します。クライアント要求のサイズに関係なく、kmem_alloc_16 キャッシュの各バッファのサイズは 16 バイトです。14 バイト要求の場合、要求は kmem_alloc_16 キャッシュによって満たされるので、バッファの残りの 2 バイトは使用されません。

キャッシュの最後のセットは、カーネルメモリーアロケータ自体が記述するために内部的に使用するキャッシュです。これには、名前が "kmem_magazine_" または "kmem_va_" で始まるキャッシュ、kmem_slab_cachekmem_bufctl_cache などがあります。

カーネルメモリーキャッシュ

この節では、カーネルメモリーキャッシュを検索し調べる方法について説明します。::kmastat コマンドを実行することによって、システムの種々の kmem キャッシュについて調べることができます。


> ::kmastat
cache                        buf    buf    buf    memory     alloc alloc
name                        size in use  total    in use   succeed  fail
------------------------- ------ ------ ------ --------- --------- -----
kmem_magazine_1                8     24   1020      8192        24     0
kmem_magazine_3               16    141    510      8192       141     0
kmem_magazine_7               32     96    255      8192        96     0
...
kmem_alloc_8                   8   3614   3751     90112   9834113     0
kmem_alloc_16                 16   2781   3072     98304   8278603     0
kmem_alloc_24                 24    517    612     24576    680537     0
kmem_alloc_32                 32    398    510     24576    903214     0
kmem_alloc_40                 40    482    584     32768    672089     0
...
thread_cache                 368    107    126     49152    669881     0
lwp_cache                    576    107    117     73728       182     0
turnstile_cache               36    149    292     16384    670506     0
cred_cache                    96      6     73      8192   2677787     0
...

::kmastat を実行すれば、「正常な」システムの感じをつかむことができます。これは、システムのメモリーをリークしている過度に大きなキャッシュを見つけるのに役立ちます。::kmastat を実行した結果は、それを実行しているシステム、実行しているプロセスの数などによって異なります。

種々の kmem キャッシュのリストを表示するもう 1 つの方法は、::kmem_cache コマンドを使用することです。


> ::kmem_cache
ADDR     NAME                      FLAG  CFLAG  BUFSIZE  BUFTOTL
70036028 kmem_magazine_1           0020 0e0000        8     1020
700362a8 kmem_magazine_3           0020 0e0000       16      510
70036528 kmem_magazine_7           0020 0e0000       32      255
...
70039428 kmem_alloc_8              020f 000000        8     3751
700396a8 kmem_alloc_16             020f 000000       16     3072
70039928 kmem_alloc_24             020f 000000       24      612
70039ba8 kmem_alloc_32             020f 000000       32      510
7003a028 kmem_alloc_40             020f 000000       40      584
...

このコマンドは、キャッシュ名をアドレスに対応づける点で有用です。FLAG 欄には各キャッシュのデバッギングフラグが示されます。重要なことは、アロケータのデバッギング機能の選択がキャッシュごとにこのフラグのセットに基づいて行われていることを理解することです。これらは、キャッシュの作成時に、大域 kmem_flags 変数とともに設定されます。システムの実行中に kmem_flags を設定しても、その後に作成されたキャッシュを除いて (起動後にキャッシュが作成されることはまれです)、デバッギング動作には影響を与えません。

次に、MDB の kmem_cache walker を使用して、直接 kmem キャッシュのリストを調べます。


> ::walk kmem_cache
70036028
700362a8
70036528
700367a8
...

これによって、カーネルの各 kmem キャッシュに対応するポインタのリストが作成されます。特定のキャッシュを見つけるには、kmem_cache マクロを適用します。


> 0x70039928$<kmem_cache
0x70039928:     lock
0x70039928:     owner/waiters
                0
0x70039930:     flags           freelist        offset
                20f             707c86a0        24
0x7003993c:     global_alloc    global_free     alloc_fail
                523             0               0
0x70039948:     hash_shift      hash_mask       hash_table
                5               1ff             70444858
0x70039954:     nullslab
0x70039954:     cache           base            next
                70039928        0               702d5de0
0x70039960:     prev            head            tail
                707c86a0        0               0
0x7003996c:     refcnt          chunks
                -1              0
0x70039974:     constructor     destructor      reclaim
                0               0               0
0x70039980:     private         arena           cflags
                0               104444f8        0
0x70039994:     bufsize         align           chunksize
                24              8               40
0x700399a0:     slabsize        color           maxcolor
                8192            24              32
0x700399ac:     slab_create     slab_destroy    buftotal
                3               0               612
0x700399b8:     bufmax          rescale         lookup_depth
                612             1               0
0x700399c4:     kstat           next            prev
                702c8608        70039ba8        700396a8
0x700399d0:     name    kmem_alloc_24
0x700399f0:     bufctl_cache    magazine_cache  magazine_size
                70037ba8        700367a8        15
...

デバッギングにとって重要なフィールドは、'bufsize'、'flags'、および 'name' です。kmem_cache の名前 (この場合は "kmem_alloc_24" ) は、このシステムでの目的を示しています。bufsize は、このキャッシュの各バッファのサイズです。この場合、このキャッシュは、サイズ 24 以下の割り当てに使用されます。'flags' は、このキャッシュに対してどのデバッギング機能が有効になっているかを示しています。これは <sys/kmem_impl.h> の中に定義されているデバッギングフラグです。この場合には 'flags' は 0x20f ですが、これは KMF_AUDIT | KMF_DEADBEEF | KMF_REDZONE | KMF_CONTENTS | KMF_HASH です。各デバッギング機能については、この後の各節で説明します。

特定のキャッシュのバッファを調べたい場合には、そのキャッシュの割り当て済みおよび未使用バッファを直接調べることができます。


> 0x70039928::walk kmem
704ba010
702ba008
704ba038
702ba030
...

> 0x70039928::walk freemem
70a9ae50
70a9ae28
704bb730
704bb2f8
...

MDB は、kmem walker にキャッシュアドレスを供給するショートカットを用意しています。kmem キャッシュごとに特定の walker が提供され、その walker の名前はキャッシュの名前と同じです。たとえば、次のようになります。


> ::walk kmem_alloc_24
704ba010
702ba008
704ba038
702ba030
...

> ::walk thread_cache
70b38080
70aac060
705c4020
70aac1e0
...

これで、カーネルメモリーアロケータの内部データ構造体に対して繰り返す方法や、kmem_cache データ構造体の最も重要なメンバーを調べる方法がわかります。

メモリー破壊の検出

アロケータの主要デバッギング機能の 1 つは、データの損傷をすばやく認識するアルゴリズムです。破壊が検出されると、アロケータによりただちにシステムでパニックが発生します。

この節では、アロケータがどのようにしてデータの損傷を認識するかを説明します。これらの問題をデバッグするには、この点を理解しておく必要があります。メモリーの誤用は、一般的に次のいずれかの原因によるものです。

この後の 3 つの節を読む際には、これらの問題を覚えておいてください。アロケータの設計を理解する上で役立ち、問題を効率的に診断できます。

未使用バッファの検査 (0xdeadbeef)

kmem_cache の flags フィールドの KMF_DEADBEEF (0x2) ビットが設定されている場合、アロケータは、すべての未使用バッファに特殊なパターンを書き込むためメモリー破壊を簡単に検出できます。このパターンは 0xdeadbeef です。一般的なメモリーの領域は、割り当て済みメモリーと未使用メモリーの両方を含んでいるので、各種のブロックのセクションが混在します。"kmem_alloc_24" キャッシュの一例を以下に示します。

0x70a9add8:     deadbeef        deadbeef
0x70a9ade0:     deadbeef        deadbeef
0x70a9ade8:     deadbeef        deadbeef
0x70a9adf0:     feedface        feedface
0x70a9adf8:     70ae3260        8440c68e
0x70a9ae00:     5               4ef83
0x70a9ae08:     0               0
0x70a9ae10:     1               bbddcafe
0x70a9ae18:     feedface        4fffed
0x70a9ae20:     70ae3200        d1befaed
0x70a9ae28:     deadbeef        deadbeef
0x70a9ae30:     deadbeef        deadbeef
0x70a9ae38:     deadbeef        deadbeef
0x70a9ae40:     feedface        feedface
0x70a9ae48:     70ae31a0        8440c54e

0x70a9add8 で始まるバッファは、0xdeadbeef のパターンが使用されています。このパターンによって、そのバッファが現在未使用であることがただちにわかります。0x70a9ae28 から次の未使用バッファが始まっています。それらの間の 0x70a9ae00 で始まる領域に割り当て済みバッファがあります。


注 -

この図にはいくつかの穴があいていて、ここに示された 120 バイトのうち、3 つの 24 バイト領域によって 72 バイトのメモリーしか占有されていません。この不一致については、「レッドゾーン (0xfeedface)」で説明します。


レッドゾーン (0xfeedface)

上記のバッファには、0xfeedface のパターンが頻繁に現れています。このパターンは、レッドゾーンインジケータと呼ばれるものです。これによって、アロケータ (および問題のデバッギングを行なっているプログラマ) は、「バグのある」コードがバッファの境界を超えているかどうかを判断することができます。レッドゾーンの後に追加の情報があります。このデータの内容は他の要因によって異なります (「メモリー割り当てログ」を参照)。レッドゾーンとそのあとのデータ領域は、まとめて buftag 領域と呼ばれます。 図 6-1 に、この情報の要約を示します。

図 6-1 レッドゾーン

Graphic

バッファのキャッシュに KMF_AUDITKMF_DEADBEEFKMF_REDZONE、またはKMF_CONTENTS フラグが設定されている場合には、そのキャッシュの各バッファに buftag が付加されます。buftag の内容は、KMF_AUDIT が設定されているかどうかにより異なります。

前述のメモリー領域を個別のバッファに分解すると、次のように簡単になります。

0x70a9add8:     deadbeef        deadbeef  ¥
0x70a9ade0:     deadbeef        deadbeef   +- ユーザーデータ (未使用)
0x70a9ade8:     deadbeef        deadbeef  /
0x70a9adf0:     feedface        feedface  -- レッドゾーン
0x70a9adf8:     70ae3260        8440c68e  -- デバッギングデータ

0x70a9ae00:     5               4ef83     ¥
0x70a9ae08:     0               0          +- ユーザーデータ (割り当て済み)
0x70a9ae10:     1               bbddcafe  /
0x70a9ae18:     feedface        4fffed    -- レッドゾーン
0x70a9ae20:     70ae3200        d1befaed  -- デバッギングデータ

0x70a9ae28:     deadbeef        deadbeef  ¥
0x70a9ae30:     deadbeef        deadbeef   +- ユーザーデータ (未使用)
0x70a9ae38:     deadbeef        deadbeef  /
0x70a9ae40:     feedface        feedface  -- レッドゾーン
0x70a9ae48:     70ae31a0        8440c54e  -- デバッギングデータ

0x70a9add80x70a9ae28 の未使用バッファでは、レッドゾーンには 0xfeedfacefeedface が使用されています。これは、バッファが未使用であることを判断する便利な方法です。

0x70a9ae00 で始まる割り当て済みバッファでは、状況は異なります。「アロケータの基礎」 で説明したことを思い出してください。割り当てには、次の 2 つのタイプがあります。

1) クライアントが、kmem_cache_alloc() を使用してメモリーを要求した場合。この場合には、要求されたバッファのサイズは、キャッシュの bufsize と等しくなります。

2) クライアントが、kmem_alloc(9F) を使用してメモリーを要求した場合。この場合には、要求されたバッファのサイズは、キャッシュの bufsize 以下になります。たとえば、20 バイトの要求は、kmem_alloc_24 キャッシュによって満たされます。アロケータは、クライアントデータのすぐ後にマーカーとしてレッドゾーンバイトを調整して強制的にバッファ境界を合わせます。

0x70a9ae00:     5               4ef83     ¥
0x70a9ae08:     0               0          +- ユーザーデータ (割り当て済み)
0x70a9ae10:     1               bbddcafe  /
0x70a9ae18:     feedface        4fffed    -- レッドゾーン
0x70a9ae20:     70ae3200        d1befaed  -- デバッギングデータ

0x70a9ae18 にある 0xfeedface の後には、ランダムな値のように見える 32 ビットのワードがあります。この数字は、実際にはバッファサイズの符号化された表現です。この数字を復号化して割り当て済みバッファのサイズを知るには、次の公式を使用します。

size = redzone_value / (UINT_MAX / KMEM_MAXBUF)

KMEM_MAXBUF の値は 16384 であり、UINT_MAX の値は 4294967295 です。したがってこの例では、次のようになります。

size = 0x4fffed / (4294967295 / 16384) = 20 bytes.

これは、要求されたバッファのサイズが 20 バイトであることを示しています。アロケータはこの復号化操作を行なって、レッドゾーンバイトがオフセット 20 であることを知ります。レッドゾーンバイトは 16 進パターン 0xbb です。これは予想通り、0x729084e4 (0x729084d0 + 0t20) に存在しています。

図 6-2 kmem_alloc(9F) バッファの例

Graphic

図 6-3 に、メモリー配置の一般的形式を示します。

図 6-3 レッドゾーンバイト

Graphic

割り当てサイズがキャッシュの bufsize と同じである場合には、図 6-4 に示すように、レッドゾーン自体の最初のバイトにレッドゾーンバイトが上書きされます。

図 6-4 レッドゾーンの先頭にあるレッドゾーンバイト

Graphic

この上書きの結果、レッドゾーンの最初の 32 ビットワードは 0xbbedface または 0xfeedfabb になります。このどちらになるかは、システムを実行しているハードウェアのエンディアンによります。


注 -

割り当てサイズがこのような方法で符号化されるのはなぜでしょうか。サイズを符号化するために、アロケータは公式 ((UINT_MAX / KMEM_MAXBUF) * size + 1) を使用します。サイズを復号化する際には、整数の割り算を行い、余りの '+1' は捨てられます。しかし、この追加された 1 は貴重な役割を果たします。なぜなら、アロケータは (size % (UINT_MAX / KMEM_MAXBUF) == 1) になるかどうかをテストすることにより、サイズが有効かどうかをチェックできるからです。このようにして、アロケータはレッドゾーンバイトインデックスの破壊に対処します。


初期化されていないデータ (0xbaddcafe)

アドレス 0x729084d40xbbddcafe は、ワードの最初のバイトにレッドゾーンバイトが上書きされる前には何と書いてあったのでしょうか。0xbaddcafe だったのです。キャッシュに KMF_DEADBEEF フラグが設定されると、割り当てられたけれども初期化されていないメモリーには、パターン 0xbaddcafe が使用されます。アロケータが割り当てを行う際には、バッファの各ワードをループし、各ワードに 0xdeadbeef が含まれていることを検証し、次にそのワードに 0xbaddcafe を使用します。

システムが次のようなメッセージを出してパニックを引き起こす場合があります。

panic[cpu1]/thread=e1979420: BAD TRAP: type=e (Page Fault)
rp=ef641e88 addr=baddcafe occurred in module "unix" due to an
illegal access to a user address

この場合、障害の原因になったアドレスは 0xbaddcafe です。スレッドがパニックを起こしたのは、初期化されていないデータにアクセスしたためです。

パニックメッセージと障害の関係

カーネルメモリーアロケータは、前述した障害モードに対応してパニックメッセージを出します。たとえば、システムが次のようなメッセージを出してパニックを引き起こす場合があります。


kernel memory allocator: buffer modified after being freed
modification occurred at offset 0x30

アロケータは、問題のバッファに 0xdeadbeef が使用されていることを確認するので、この場合を検出することができます。オフセット 0x30 ではこの条件が満たされていませんでした。この状態はメモリー破壊を示しているので、アロケータによりシステムにパニックが発生しました。

障害メッセージのもう 1 つの例を次に示します。

kernel memory allocator: redzone violation: write past end of buffer

アロケータは、レッドゾーンサイズの符号化から判定した場所にレッドゾーンバイト (0xbb) が存在することを確認するので、この問題を検出することができます。しかし、アロケータは正しい場所にこのシグニチャーバイトを見つけることができませんでした。これはメモリー破壊を示しているので、アロケータによりシステムにパニックが発生しました。その他のアロケータパニックメッセージについては、後で説明します。

メモリー割り当てログ

この節では、カーネルメモリーアロケータのログ機能と、この機能を使用してシステムクラッシュのデバッギングを行う方法について説明します。

buftag データの完全性

前述のように、各 buftag の後半には、対応するバッファに関する追加情報が含まれています。この情報の一部はデバッギング情報であり、また、アロケータの内部データも含まれています。この補助的データは種々の形式をとりますが、まとめて「バッファ制御」データあるいは bufctl データと呼ばれます。

しかし、誤ったコードによってこの bufctl ポインタも破壊される場合があるので、アロケータはバッファの bufctl ポインタが有効であるかどうかを知る必要があります。アロケータは、このポインタとその符号化されたバージョンを格納し、2 つのバージョンのクロスチェックを行うことにより、この補助ポインタの完全性を確認します。

図 6-5 に示すように、ポインタの 2 つのバージョンは、bcp (buffer control pointer) と bxstat (buffer control XOR status) です。アロケータは、式 bcp XOR bxstat がわかりやすい既知の値に等しくなるように bcp と bxstat を調整します。

図 6-5 buftag の追加のデバッギングデータ

Graphic

これらのポインタの一方または両方が壊れている場合には、アロケータは容易に破壊を検出し、システムにパニックを発生させます。バッファが割り当て済みの場合には、bcp XOR bxstat = 0xa110c8ed ( "allocated" ) になります。バッファが未使用の場合には、bcp XOR bxstat = 0xf4eef4ee ( "freefree" ) になります。


注 -

「未使用バッファの検査 (0xdeadbeef)」に示されている例をもう一度調べて、例に示されている buftag ポインタがこの説明どおりであることを確認してください。


アロケータは、buftag が壊れていることを発見した場合には、システムにパニックを発生させ、次のようなメッセージを出します。


kernel memory allocator: boundary tag corrupted
    bcp ^ bxstat = 0xffeef4ee, should be f4eef4ee

bcp が壊れていても、そのバッファが割り当て済みか未使用かによって、それぞれ bxstat XOR 0xf4eef4ee または bxstat XOR 0xa110c8ed の値からその値を取り出すことが可能です。

bufctl ポインタ

buftag 領域に含まれているバッファ制御 (bufctl) ポインタは、そのキャッシュの kmem_flags に応じて種々の意味を持ちます。KMF_AUDIT フラグによって切り替えられる動作は、特に興味深いものです。KMF_AUDIT フラグが設定されていない場合には、カーネルメモリーアロケータは、各バッファの kmem_bufctl_t 構造体を割り当てます。この構造体には、各バッファに関する最小限のアカウンティング情報が含まれています。KMF_AUDIT フラグが設定されている場合には、アロケータはこの代わりに、kmem_bufctl_t の拡張バージョンである kmem_bufctl_audit_t を割り当てます。

この節では、KMF_AUDIT フラグが設定されていることを前提とします。このビットが設定されていないキャッシュは、使用可能なデバッギング情報の量が少なくなります。

kmem_bufctl_audit_t (略称は bufctl_audit) には、このバッファに対して発生した最後のトランザクションに関する追加情報が含まれています。次の例で、bufctl_audit マクロを適用して監査レコードを調べる方法を示します。ここに示したバッファは、「メモリー破壊の検出」で使用したサンプルバッファです。

> 0x70a9ae00,5/KKn
0x70a9ae00:     5               4ef83
                0               0
                1               bbddcafe
                feedface        4fffed
                70ae3200        d1befaed

上記の手法を使用すると、0x70ae3200bufctl_audit レコードを指していることが容易にわかります。これはレッドゾーンの後の最初のポインタです。bufctl_audit レコードを調べるには、bufctl_audit マクロを適用します。

> 0x70ae3200$<bufctl_audit
0x70ae3200:     next            addr            slab
                70378000        70a9ae00        707c86a0
0x70ae320c:     cache           timestamp       thread
                70039928        e1bd0e26afe     70aac4e0
0x70ae321c:     lastlog         contents        stackdepth
                7011c7c0        7018a0b0        4
0x70ae3228:
                kmem_zalloc+0x30
                pid_assign+8
                getproc+0x68
                cfork+0x60

'addr' フィールドは、この bufctl_audit レコードに対応するバッファのアドレスです。これはオリジナルアドレス 0x70a9ae00 です。'cache' フィールドは、このバッファが割り当てられている kmem_cache を指します。::kmem_cache dcmd を使用して、次のようにしてこのキャッシュを調べることができます。

> 0x70039928::kmem_cache
ADDR     NAME                      FLAG  CFLAG  BUFSIZE  BUFTOTL
70039928 kmem_alloc_24             020f 000000       24      612

'timestamp' フィールドは、このトランザクションが発生した時刻を表します。この時刻は gethrtime(3C) と同じ形式で表されます。

'thread' は、このバッファに対して最後のトランザクションを行なったスレッドへのポインタです。'lastlog' および 'contents' ポインタは、アロケータのトランザクションログの中の位置を指します。これらのログについては、「アロケータのログ機能」で詳しく説明します。

一般的に、bufctl_audit が提供する最も有用な情報は、トランザクションが発生した時点で記録されるスタックトレースです。この場合、このトランザクションは fork(2) の実行の一部として呼び出された割り当てです。

拡張メモリー解析

この節では、メモリーリークとデータ破壊の原因などの拡張メモリーの解析について説明します。

メモリーリークの発見

::findleaks dcmd を使用して、フルセットの kmem デバッギング機能が有効になっている場合に、カーネルクラッシュダンプの際に効率的にメモリーリークの検出を行うことができます。::findleaks の最初の実行では、ダンプを処理してメモリーリークを探します。この処理には数分かかる場合があります。次に、割り当てスタックトレース別にリークがまとめられます。findleaks レポートには、識別されたメモリーリークごとに bufctl アドレスと先頭のスタックフレームが示されます。

> ::findleaks
CACHE     LEAKED   BUFCTL CALLER
70039ba8       1 703746c0 pm_autoconfig+0x708
70039ba8       1 703748a0 pm_autoconfig+0x708
7003a028       1 70d3b1a0 sigaddq+0x108
7003c7a8       1 70515200 pm_ioctl+0x187c
----------------------------------------------------------------------
   Total       4 buffers, 376 bytes

bufctl ポインタを使用し、bufctl_audit マクロを適用して、その割り当ての完全なスタックバックトレースを得ることができます。

> 70d3b1a0$<bufctl_audit
0x70d3b1a0:     next            addr            slab
                70a049c0        70d03b28        70bb7480
0x70d3b1ac:     cache           timestamp       thread
                7003a028        13f7cf63b3      70b38380
0x70d3b1bc:     lastlog         contents        stackdepth
                700d6e60        0               5
0x70d3b1c8:
                kmem_alloc+0x30
                sigaddq+0x108
                sigsendproc+0x210
                sigqkill+0x90
                kill+0x28

プログラマは、通常、bufctl_audit 情報と割り当てスタックトレースの割り当てを使用して、そのバッファのリークの原因となったコードパスをすばやく突き止めることができます。

データへの参照の発見

メモリー破壊の診断を行う際は、他のどのカーネルエンティティが特定のポインタのコピーを保持しているかを知る必要があります。これは重要なことです。なぜならデータ構造体が解放された後どのスレッドがこれにアクセスしたかを明らかにできるからです。また、特定の (有効な) データ項目の知識をどのカーネルエンティティが共有しているかを知ることが容易になります。このためには ::whatis dcmd と ::kgrep dcmd を使用します。次のようにして、問題の値に対して ::whatis を適用します。


> 0x705d8640::whatis
705d8640 is 705d8000+640, allocated from kmem_va_8192
705d8640 is 705d8640+0, allocated from streams_mblk

この場合は、0x705d8640 が STREAMS mblk 構造体へのポインタであることが明らかになりました。この割り当ては、kmem_va 仮想記憶領域の前の段階の kmem キャッシュである kmem_va_8192 キャッシュにも見られます。::kmastat dcmd を使用すれば、kmem キャッシュと vmem 領域のリストが表示されます。::kgrep を使用して、この mblk へのポインタを含む他のカーネルアドレスを突き止めることができます。これによって、システムのメモリー割り当ての階層的特徴が明らかになります。一般的に、特殊な kmem キャッシュの名前から、そのアドレスによって参照されるオブジェクトのタイプを判断することができます。

> 0x705d8640::kgrep
400a3720
70580d24
7069d7f0
706a37ec
706add34

再び ::whatis を適用します。

> 400a3720::whatis
400a3720 is in thread 7095b240's stack

> 706add34::whatis
706add34 is 706ac000+1d34, allocated from kmem_va_8192
706add34 is 706add20+14, allocated from streams_dblk_120

1 つのポインタは既知のカーネルスレッドのスタック上にあり、もう 1 つのポインタは対応する STREAMS dblk 構造体の内部の mblk ポインタであることがわかりました。

::kmem_verify を使用したバッファの障害の発見

MDB の ::kmem_verify dcmd を使用すると、kmem アロケータが実行時に行う検査とほぼ同じ検査を行います。::kmem_verify を起動して、該当する kmem_flags が設定されている場合にすべての kmem キャッシュを走査し、あるいは特定のキャッシュを調べることができます。

::kmem_verify を使用して問題を突き止める例を、以下に示します。


> ::kmem_verify
Cache Name                      Addr     Cache Integrity
kmem_alloc_8                    70039428 clean
kmem_alloc_16                   700396a8 clean
kmem_alloc_24                   70039928 1 corrupt buffer
kmem_alloc_32                   70039ba8 clean
kmem_alloc_40                   7003a028 clean
kmem_alloc_48                   7003a2a8 clean
...

::kmem_verify によれば、明らかに kmem_alloc_24 キャッシュには問題が存在します。明示的なキャッシュ引数を指定すると、::kmem_verify dcmd はこの問題に関するより詳細な情報を提供します。

> 70039928::kmem_verify
Summary for cache 'kmem_alloc_24'
  buffer 702babc0 (free) seems corrupted, at 702babc0

次に、::kmem_verify によって障害があると認識されたバッファを調べます。

> 0x702babc0,5/KKn
0x702babc0:     0               deadbeef
                deadbeef        deadbeef
                deadbeef        deadbeef
                feedface        feedface
                703785a0        84d9714e

::kmem_verify がこのバッファにフラグを立てた理由が明らかになりました。バッファの最初のワード (0x702babc0 で始まる) には、0xdeadbeef のパターンが使用されるはずであったのに、0 が使用されています。この時点で、このバッファの bufctl_audit を調べることによって、このバッファにどのコードが最近書き込みを行なったか、どこでいつ解放されたかについての手がかりが得られます。

この状況でのもう 1 つの有用な手法は、::kgrep を使用してアドレス空間を調べてアドレス 0x702babc0 への参照を検索し、この解放されたデータへの参照を依然として保持しているスレッドまたはデータ構造体を発見することです。

アロケータのログ機能

キャッシュの KMF_AUDIT が設定されている場合、カーネルメモリーのアロケータは、アクティビティの最近の履歴を記録するログを維持します。このトランザクションログには、bufctl_audit レコードが記録されます。KMF_AUDITKMF_CONTENTS の両方のフラグが設定されている場合には、アロケータは、割り当て済みバッファと解放されたバッファの実際の内容の一部を記録したログを生成します。このログの構造と使用法については、このマニュアルでは記載していません。この節では、トランザクションログについて説明します。

MDB は、トランザクションログを表示するための複数の機能を備えています。最も簡単な方法は、::walk kmem_log です。これは、このログに記録されているトランザクションを一連の bufctl_audit_t ポインタの形で出力します。


> ::walk kmem_log
70128340
701282e0
70128280
70128220
701281c0
...
> 70128340$<bufctl_audit
0x70128340:     next            addr            slab
                70ac1d40        70bc4ea8        70bb7c00
0x7012834c:     cache           timestamp       thread
                70039428        e1bd7abe721     70aacde0
0x7012835c:     lastlog         contents        stackdepth
                701282e0        7018f340        4
0x70128368:
                kmem_cache_free+0x24
                nfs3_sync+0x3c
                vfs_sync+0x84
                syssync+4

トランザクションログ全体を表示するもっと簡潔な方法は、::kmem_log コマンドを使用することです。


> ::kmem_log
CPU ADDR     BUFADDR         TIMESTAMP THREAD
  0 70128340 70bc4ea8      e1bd7abe721 70aacde0
  0 701282e0 70bc4ea8      e1bd7aa86fa 70aacde0
  0 70128280 70bc4ea8      e1bd7aa27dd 70aacde0
  0 70128220 70bc4ea8      e1bd7a98a6e 70aacde0
  0 701281c0 70d03738      e1bd7a8e3e0 70aacde0
  ...
  0 70127140 70cf78a0      e1bd78035ad 70aacde0
  0 701270e0 709cf6c0      e1bd6d2573a 40033e60
  0 70127080 70cedf20      e1bd6d1e984 40033e60
  0 70127020 70b09578      e1bd5fc1791 40033e60
  0 70126fc0 70cf78a0      e1bd5fb6b5a 40033e60
  0 70126f60 705ed388      e1bd5fb080d 40033e60
  0 70126f00 705ed388      e1bd551ff73 70aacde0
  ...

::kmem_log の出力は、時刻表示の降順にソートされます。ADDR 欄は、このトランザクションに対応する bufctl_audit 構造体です。BUFADDR は、実際のバッファを指しています。

これらの数字は、バッファに対するトランザクション (割り当てと解放) を表しています。特定のバッファが壊れた場合、トランザクションログの中でそのバッファを突き止め、そのトランザクションを行なったスレッドが他のどのトランザクションにかかわっていたかを判断することは有用です。このことは、バッファの割り当て (または解放) の前後に発生したイベントのシーケンスの全体像を理解するのに役立ちます。

::bufctl コマンドを使用して、トランザクションログの調査の出力をフィルタリングすることができます。::bufctl -a コマンドは、トランザクションログの中のバッファをバッファアドレスによってフィルタリングします。次の例は、バッファ 0x70b09578 のフィルタリングの結果です。


> ::walk kmem_log | ::bufctl -a 0x70b09578
ADDR     BUFADDR   TIMESTAMP   THREAD   CALLER
70127020 70b09578  e1bd5fc1791 40033e60 biodone+0x108
70126e40 70b09578  e1bd55062da 70aacde0 pageio_setup+0x268
70126de0 70b09578  e1bd52b2317 40033e60 biodone+0x108
70126c00 70b09578  e1bd497ee8e 70aacde0 pageio_setup+0x268
70120480 70b09578  e1bd21c5e2a 70aacde0 elfexec+0x9f0
70120060 70b09578  e1bd20f5ab5 70aacde0 getelfhead+0x100
7011ef20 70b09578  e1bd1e9a1dd 70aacde0 ufs_getpage_miss+0x354
7011d720 70b09578  e1bd1170dc4 70aacde0 pageio_setup+0x268
70117d80 70b09578  e1bcff6ff27 70bc2480 elfexec+0x9f0
70117960 70b09578  e1bcfea4a9f 70bc2480 getelfhead+0x100
...

この例は、特定のバッファが多くのトランザクションに使用される場合があることを示しています。


注 -

kmem トランザクションログは、カーネルメモリーアロケータが行なったトランザクションのすべての記録ではないことを忘れないでください。ログのサイズを一定に保つために、ログの中の古い記録は消去されます。


::allocdby dcmd と ::freedby dcmd を使用して、特定のスレッドに関連するトランザクションの要約を示すことができます。次の例では、スレッド 0x70aacde0 によって行われた最近の割り当てのリストが示されています。


> 0x70aacde0::allocdby
BUFCTL      TIMESTAMP CALLER
70d4d8c0  e1edb14511a allocb+0x88
70d4e8a0  e1edb142472 dblk_constructor+0xc
70d4a240  e1edb13dd4f allocb+0x88
70d4e840  e1edb13aeec dblk_constructor+0xc
70d4d860  e1ed8344071 allocb+0x88
70d4e7e0  e1ed8342536 dblk_constructor+0xc
70d4a1e0  e1ed82b3a3c allocb+0x88
70a53f80  e1ed82b0b91 dblk_constructor+0xc
70d4d800  e1e9b663b92 allocb+0x88

bufctl_audit レコードを調べることにより、特定のスレッドの最近のアクティビティを理解することができます。