Solaris カーネルメモリー (kmem) アロケータでは、カーネルクラッシュダンプの分析を容易にする強力なデバッギング機能のセットを用意しています。この章では、これらのデバッギング機能について説明し、またこのアロケータ用に特に設計された MDB dcmd と walker について説明します。Bonwick (「関連マニュアルと論文」を参照) に、このアロケータ自体の原理の概要が説明されています。アロケータデータ構造体の定義については、ヘッダーファイル <sys/kmem_impl.h> を参照してください。システム上で kmem デバッギング機能を有効にして問題の分析能力を向上させたり、あるいは開発システム上で kmem デバッギング機能を有効にしてカーネルソフトウェアやデバイスドライバのデバッギングを支援したりすることができます。
このマニュアルは、Solaris 8 での実装を反映しています。つまり、現在のカーネルの実装を反映しているので、過去または将来のリリースに対しては関連せず、適切でない、あるいは適用できない場合があります。これはどんな種類の公開インタフェースを定義するものでもありません。このカーネルメモリーアロケータに関して提供されている情報は、将来の Solaris リリースでは変更される場合があります。
この節では、サンプルクラッシュダンプの作成方法およびそれを調べるために MDB を起動する方法について説明します。
カーネルメモリーアロケータには多くの高度なデバッギング機能が含まれていますが、それらは性能の低下をもたらす可能性があるので、デフォルトでは有効になっていません。このマニュアルの例を実行するには、これらの機能を有効にする必要があります。性能の低下やほかの問題を引き起こす可能性があるため、これらの機能を有効にするのは、テストシステムに対してだけにしておくべきです。
アロケータのデバッギング機能は、調整可能な 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 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_8 と kmem_alloc_16 キャッシュを持っています。この場合、kmem_alloc_16 キャッシュは、9 〜 16 バイトのメモリーを要求するすべてのクライアント要求を処理します。クライアント要求のサイズに関係なく、kmem_alloc_16 キャッシュの各バッファのサイズは 16 バイトです。14 バイト要求の場合、要求は kmem_alloc_16 キャッシュによって満たされるので、バッファの残りの 2 バイトは使用されません。
キャッシュの最後のセットは、カーネルメモリーアロケータ自体が記述するために内部的に使用するキャッシュです。これには、名前が "kmem_magazine_" または "kmem_va_" で始まるキャッシュ、kmem_slab_cache、kmem_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 つの節を読む際には、これらの問題を覚えておいてください。アロケータの設計を理解する上で役立ち、問題を効率的に診断できます。
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 のパターンが頻繁に現れています。このパターンは、レッドゾーンインジケータと呼ばれるものです。これによって、アロケータ (および問題のデバッギングを行なっているプログラマ) は、「バグのある」コードがバッファの境界を超えているかどうかを判断することができます。レッドゾーンの後に追加の情報があります。このデータの内容は他の要因によって異なります (「メモリー割り当てログ」を参照)。レッドゾーンとそのあとのデータ領域は、まとめて buftag 領域と呼ばれます。 図 6-1 に、この情報の要約を示します。
バッファのキャッシュに KMF_AUDIT、KMF_DEADBEEF、KMF_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 -- デバッギングデータ |
0x70a9add8 と 0x70a9ae28 の未使用バッファでは、レッドゾーンには 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-3 に、メモリー配置の一般的形式を示します。
割り当てサイズがキャッシュの bufsize と同じである場合には、図 6-4 に示すように、レッドゾーン自体の最初のバイトにレッドゾーンバイトが上書きされます。
この上書きの結果、レッドゾーンの最初の 32 ビットワードは 0xbbedface または 0xfeedfabb になります。このどちらになるかは、システムを実行しているハードウェアのエンディアンによります。
割り当てサイズがこのような方法で符号化されるのはなぜでしょうか。サイズを符号化するために、アロケータは公式 ((UINT_MAX / KMEM_MAXBUF) * size + 1) を使用します。サイズを復号化する際には、整数の割り算を行い、余りの '+1' は捨てられます。しかし、この追加された 1 は貴重な役割を果たします。なぜなら、アロケータは (size % (UINT_MAX / KMEM_MAXBUF) == 1) になるかどうかをテストすることにより、サイズが有効かどうかをチェックできるからです。このようにして、アロケータはレッドゾーンバイトインデックスの破壊に対処します。
アドレス 0x729084d4 の 0xbbddcafe は、ワードの最初のバイトにレッドゾーンバイトが上書きされる前には何と書いてあったのでしょうか。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 の後半には、対応するバッファに関する追加情報が含まれています。この情報の一部はデバッギング情報であり、また、アロケータの内部データも含まれています。この補助的データは種々の形式をとりますが、まとめて「バッファ制御」データあるいは bufctl データと呼ばれます。
しかし、誤ったコードによってこの bufctl ポインタも破壊される場合があるので、アロケータはバッファの bufctl ポインタが有効であるかどうかを知る必要があります。アロケータは、このポインタとその符号化されたバージョンを格納し、2 つのバージョンのクロスチェックを行うことにより、この補助ポインタの完全性を確認します。
図 6-5 に示すように、ポインタの 2 つのバージョンは、bcp (buffer control pointer) と bxstat (buffer control XOR status) です。アロケータは、式 bcp XOR bxstat がわかりやすい既知の値に等しくなるように bcp と bxstat を調整します。
これらのポインタの一方または両方が壊れている場合には、アロケータは容易に破壊を検出し、システムにパニックを発生させます。バッファが割り当て済みの場合には、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 |
上記の手法を使用すると、0x70ae3200 が bufctl_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 ポインタであることがわかりました。
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_AUDIT と KMF_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 レコードを調べることにより、特定のスレッドの最近のアクティビティを理解することができます。