Solaris モジューラデバッガ

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

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


注 –

MDB によってカーネルの実装の詳細が公開されますが、これらは随時変更されることがあります。このマニュアルで説明する Solaris カーネルの実装は、このマニュアルの発行時点のものです。このマニュアルでカーネルメモリーアロケータに関して提供されている情報は、過去または将来の Solaris リリースに対しては正しくない場合や適用できない場合があります。


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

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

kmem_flags の設定

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

アロケータのデバッギング機能は、調整可能な kmem_flags によって制御されます。この機能を使用する前に、kmem_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.10 Generic (sun4u)
panic message: forced crash dump initiated at user request

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

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

アロケータの基礎

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

バッファーの状態

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

トランザクション

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        139d
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 領域と呼ばれます。図 9–1 に、この情報の要約を示します。

図 9–1 レッドゾーン

この図はレッドゾーンを示しています。

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

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

0x70a9add8:     deadbeef        deadbeef  \
0x70a9ade0:     deadbeef        deadbeef   +- User Data (free)
0x70a9ade8:     deadbeef        deadbeef  /
0x70a9adf0:     feedface        feedface  -- REDZONE
0x70a9adf8:     70ae3260        8440c68e  -- Debugging Data

0x70a9ae00:     5               4ef83     \
0x70a9ae08:     0               0          +- User Data (allocated)
0x70a9ae10:     1               bbddcafe  /
0x70a9ae18:     feedface        139d    -- REDZONE
0x70a9ae20:     70ae3200        d1befaed  -- Debugging Data

0x70a9ae28:     deadbeef        deadbeef  \
0x70a9ae30:     deadbeef        deadbeef   +- User Data (free)
0x70a9ae38:     deadbeef        deadbeef  /
0x70a9ae40:     feedface        feedface  -- REDZONE
0x70a9ae48:     70ae31a0        8440c54e  -- Debugging Data

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

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

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

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

0x70a9ae00:     5               4ef83     \
0x70a9ae08:     0               0          +- User Data (allocated)
0x70a9ae10:     1               bbddcafe  /
0x70a9ae18:     feedface        139d    -- REDZONE
0x70a9ae20:     70ae3200        d1befaed  -- Debugging Data

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

size = redzone_value / 251

たとえば、この例では次のようになります。

size = 0x139d / 251 = 20 bytes.

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

図 9–2 kmem_alloc(9F) バッファーの例

この図は kmem_alloc バッファーの例を表しています。レッドゾーンバイト、初期化されていないデータ、デバッグデータに印が付いています。

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

図 9–3 レッドゾーンバイト

この図は、ユーザーデータ領域のあとに書き込まれるレッドゾーンバイトを示しています。レッドゾーンバイトは、インデックスを復号化することで判定されます。

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

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

この図はレッドゾーンを示しています。

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


注 –

割り当てサイズがこのような方法で符号化されるのはなぜでしょうか。サイズを符号化するとき、アロケータは公式 (251 * size + 1) を使用します。サイズを復号化する際には、整数の割り算を行い、余りの「+1」は捨てられます。しかし、アロケータは (size % 251 == 1) になるかをテストすることによって、サイズが有効かどうかをチェックできるので、この 1 の加算 (「+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 つのバージョンのクロスチェックを行うことにより、この補助ポインタの完全性を確認します。

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

図 9–5 buftag の追加のデバッギングデータ

この図は walker の例を示しています。

これらのポインタの一方または両方が壊れている場合には、アロケータは容易に破壊を検出し、システムにパニックを発生させます。バッファーが割り当て済みの場合には、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        139d
                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 です。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 705d8640+0, allocated from streams_mblk

この場合は、0x705d8640 が STREAMS mblk 構造体へのポインタであることが明らかになりました。割り当てツリー全体を見るには、::whatis -a を代わりに使用します。

> 0x705d8640::whatis -a
705d8640 is 705d8640+0, allocated from streams_mblk
705d8640 is 705d8000+640, allocated from kmem_va_8192
705d8640 is 705d8000+640 from kmem_default vmem arena
705d8640 is 705d2000+2640 from kmem_va vmem arena
705d8640 is 705d2000+2640 from heap vmem arena

この割り当ては、kmem_va vmem 領域の前の段階の kmem キャッシュである kmem_va_8192 キャッシュにも見られます。また、フルスタックの vmem 割り当ても表示できます。

kmem キャッシュと vmem 領域の完全なリストを表示するには、::kmastat dcmd を使用します。::kgrep を使用すると、この mblk へのポインタを含むほかのカーネルアドレスを突き止めることができます。これによって、システムのメモリー割り当ての階層的特徴が明らかになります。一般的に、特殊な kmem キャッシュの名前から、そのアドレスによって参照されるオブジェクトのタイプを判断できます。

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

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

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

> 706add34::whatis
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 レコードを調べることにより、特定のスレッドの最近のアクティビティーを知ることができます。