アロケータの主要デバッギング機能の 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 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 のパターンが頻繁に現れています。このパターンは、レッドゾーンインジケータと呼ばれるものです。これによって、アロケータ (および問題のデバッギングを行なっているプログラマ) は、「バグのある」コードがバッファーの境界を超えているかどうかを判断することができます。レッドゾーンのあとに追加の情報があります。このデータの内容はほかの要因によって異なります (「メモリー割り当てログ」を参照)。レッドゾーンとそのあとのデータ領域は、まとめて buftag 領域と呼ばれます。図 9–1 に、この情報の要約を示します。
バッファーのキャッシュに KMF_AUDIT、KMF_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
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 +- 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–3 に、メモリー配置の一般的形式を示します。
割り当てサイズがキャッシュの bufsize と同じである場合には、図 9–4 に示すように、レッドゾーン自体の最初のバイトにレッドゾーンバイトが上書きされます。
この上書きの結果、レッドゾーンの最初の 32 ビットワードは 0xbbedface または 0xfeedfabb になります。このどちらになるかは、システムを実行しているハードウェアのエンディアンによります。
割り当てサイズがこのような方法で符号化されるのはなぜでしょうか。サイズを符号化するとき、アロケータは公式 (251 * size + 1) を使用します。サイズを復号化する際には、整数の割り算を行い、余りの「+1」は捨てられます。しかし、アロケータは (size % 251 == 1) になるかをテストすることによって、サイズが有効かどうかをチェックできるので、この 1 の加算 (「+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) が存在することを確認するので、この問題を検出することができます。しかし、アロケータは正しい場所にこのシグニチャーバイトを見つけることができませんでした。これはメモリー破壊を示しているので、アロケータによりシステムにパニックが発生しました。その他のアロケータパニックメッセージについては、後で説明します。