UMDH を使用したユーザーモード メモリ リークの検出

ユーザー モード ダンプ ヒープ (UMDH) ユーティリティは、オペレーティング システムと連携して、特定のプロセスの Windows ヒープ割り当てを分析します。 UMDH は、特定のプロセスでメモリリークしているルーチンを特定します。

UMDH は、Windows 用デバッグ ツールに含まれています。 詳細については、「 UMDH」を参照してください。

UMDH を使用する準備

どのプロセスがメモリをリークするかをまだ特定していない場合は、まずそれを行います。 詳細については、「 パフォーマンス モニターを使用したUser-Modeメモリ リークの検出」を参照してください。

UMDH ログの最も重要なデータは、ヒープ割り当てのスタック トレースです。 プロセスがヒープ メモリをリークしているかどうかを判断するには、これらのスタック トレースを分析します。

UMDH を使用してスタック トレース データを表示する前に、 GFlags を使用してシステムを適切に構成する必要があります。 GFlags は、Windows 用デバッグ ツールに含まれています。

次の GFlags 設定では、UMDH スタック トレースが有効になります。

  • GFlags グラフィカル インターフェイスで、[イメージ ファイル] タブを選択し、プロセス名 (ファイル名拡張子を含む) を入力して Tab キーを押し、[ ユーザー モード スタック トレース データベースの作成] を選択し、[ 適用] を選択します。

    または、同様に、次の GFlags コマンド ラインを使用します。 ここで、ImageName はプロセス名 (ファイル名拡張子を含む) です。

    gflags /i ImageName +ust 
    

    完了したら、このコマンドを使用して GFlag 設定をクリアします。 詳細については、「 GFlags コマンド」を参照してください。

    gflags /i ImageName -ust 
    
  • 既定では、Windows が収集するスタック トレース データの量は、x86 プロセッサでは 32 MB、x64 プロセッサでは 64 MB に制限されます。 このデータベースのサイズを大きくする必要がある場合は、GFlags グラフィカル インターフェイスで [イメージ ファイル] タブを選択し、プロセス名を入力して Tab キーを押し、[スタック バックトレース (Megs) チェック ボックスチェックし、関連付けられているテキスト ボックスに値 (MB 単位) を入力して、[適用] を選択します。 必要な場合にのみ、このデータベースを増やしてください。これは、Windows リソースが限られている可能性があるためです。 サイズを大きくする必要がなくなったら、この設定を元の値に戻します。

  • [ システム レジストリ ] タブでフラグを変更した場合は、これらの変更を有効にするために Windows を再起動する必要があります。 [ イメージ ファイル ] タブでフラグを変更した場合は、変更を有効にするためにプロセスを再起動する必要があります。 [ カーネル フラグ ] タブの変更はすぐに有効になりますが、次回 Windows が再起動すると失われます。

UMDH を使用する前に、アプリケーションの適切なシンボルにアクセスできる必要があります。 UMDH は、環境変数_NT_SYMBOL_PATHで指定されたシンボル パスを使用します。 この変数は、アプリケーションのシンボルを含むパスに設定します。 Windows シンボルへのパスも含める場合は、分析が完了している可能性があります。 このシンボル パスの構文は、デバッガーで使用される構文と同じです。詳細については、「 シンボル パス」を参照してください。

たとえば、アプリケーションのシンボルが C:\MySymbols にあり、C:\MyCache をダウンストリーム ストアとして使用して Windows シンボルにパブリック Microsoft シンボル ストアを使用する場合は、次のコマンドを使用してシンボル パスを設定します。

set _NT_SYMBOL_PATH=c:\mysymbols;srv*c:\mycache*https://msdl.microsoft.com/download/symbols 

さらに、正確な結果を得るには、BSTR キャッシュを無効にする必要があります。 これを行うには、OANOCACHE 環境変数を 1 に設定します。 割り当てをトレースするアプリケーションを起動する前に、この設定を行います。

サービスによって行われた割り当てをトレースする必要がある場合は、OANOCACHE をシステム環境変数として設定し、この設定を有効にするために Windows を再起動する必要があります。

UMDH を使用したヒープ割り当ての増加の検出

これらの準備を行った後、UMDH を使用して、プロセスのヒープ割り当てに関する情報をキャプチャできます。 これを行うには、次の手順に従います。

  1. 調査する プロセスのプロセス ID (PID) を決定します。

  2. UMDH を使用して、このプロセスのヒープ メモリ割り当てを分析し、ログ ファイルに保存します。 PID で -p スイッチを使用し、ログ ファイルの名前を指定して -f スイッチを使用します。 たとえば、PID が 124 で、ログ ファイルにLog1.txt名前を付ける場合は、次のコマンドを使用します。

    umdh -p:124 -f:log1.txt 
    
  3. メモ帳または別のプログラムを使用してログ ファイルを開きます。 このファイルには、各ヒープ割り当ての呼び出し履歴、その呼び出し履歴を介して行われた割り当ての数、およびその呼び出し履歴で使用されたバイト数が含まれます。

  4. メモリ リークを探しているため、1 つのログ ファイルの内容だけでは十分ではありません。 増加している割り当てを決定するには、異なる時間に記録されたログ ファイルを比較する必要があります。

    UMDH では、2 つの異なるログ ファイルを比較し、それぞれの割り当てサイズの変更を表示できます。 大なり記号 (>) を使用して、結果を 3 番目のテキスト ファイルにリダイレクトできます。 バイト数と割り当て数を 16 進数から 10 進数に変換する -d オプションを含めることもできます。 たとえば、Log1.txtとLog2.txtを比較し、ファイル LogCompare.txtとの比較結果を保存するには、次のコマンドを使用します。

    umdh log1.txt log2.txt > logcompare.txt 
    
  5. LogCompare.txt ファイルを開きます。 その内容は次のようになります。

    + 5320 ( f110 - 9df0) 3a allocs BackTrace00B53 
    Total increase == 5320 
    

    UMDH ログ ファイル内の呼び出し履歴 ("BackTrace" というラベルが付いている) ごとに、2 つのログ ファイル間で比較が行われます。 この例では、最初のログ ファイル (Log1.txt) は BackTrace00B53 に割り当てられた0x9DF0バイトを記録し、2 番目のログ ファイルには 0xF110 バイトが記録されています。つまり、2 つのログがキャプチャされた時間の間に0x5320追加のバイトが割り当てられました。 バイトは、BackTrace00B53 によって識別される呼び出し履歴から取得されました。

  6. そのバックトレースの内容を確認するには、元のログ ファイル (たとえば、Log2.txt) のいずれかを開き、"BackTrace00B53" を検索します。結果は次のデータのようになります。

    00005320 bytes in 0x14 allocations (@ 0x00000428) by: BackTrace00B53
    ntdll!RtlDebugAllocateHeap+0x000000FD
    ntdll!RtlAllocateHeapSlowly+0x0000005A
    ntdll!RtlAllocateHeap+0x00000808
    MyApp!_heap_alloc_base+0x00000069
    MyApp!_heap_alloc_dbg+0x000001A2
    MyApp!_nh_malloc_dbg+0x00000023
    MyApp!_nh_malloc+0x00000016
    MyApp!operator new+0x0000000E
    MyApp!DisplayMyGraphics+0x0000001E
    MyApp!main+0x0000002C
    MyApp!mainCRTStartup+0x000000FC
    KERNEL32!BaseProcessStart+0x0000003D 
    

    この UMDH 出力は、呼び出し履歴から割り当てられた0x5320 (10 進数 21280) の合計バイト数があることを示しています。 これらのバイトは、それぞれ0x428 (1064) バイトの0x14 (10 進数 20) の個別の割り当てから割り当てられました。

    呼び出し履歴には "BackTrace00B53" という識別子が与えられ、このスタック内の呼び出しが表示されます。 呼び出し履歴を確認すると、 DisplayMyGraphics ルーチンが 新しい 演算子を介してメモリを割り当てていることがわかります。この演算子は、Visual C++ ランタイム ライブラリを使用してヒープからメモリを取得するルーチン malloc を呼び出します。

    これらの呼び出しのうち、ソース コードに明示的に表示する最後の呼び出しを決定します。 この場合は、malloc の呼び出しが別の割り当てとしてではなく、new の実装の一部として発生したため、おそらくしい演算子です。 したがって、DisplayMyGraphics ルーチンの新しい演算子のこのインスタンスは、解放されていないメモリを繰り返し割り当てしています。