UMDH を使用したユーザー モード メモリ リークの検出
ユーザー モード ダンプ ヒープ (UMDH) ユーティリティは、オペレーティング システムと連携して、特定のプロセスの Windows ヒープ割り当てを解析します。 UMDH は、特定のプロセス内のどのルーチンがメモリ リークの原因となっているかを特定します。
UMDH は Windows 用デバッグ ツールに含まれています。 詳細については、「UMDH」を参照してください。
UMDH の使用の準備
どのプロセスがメモリ リークの原因となっているかをまだ特定していない場合は、まずそれを行ってください。 詳細については、「パフォーマンス モニターを使用したユーザー モード メモリ リークの検出」を参照してください。
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 を再起動して、それらの変更を有効にする必要があります。 [カーネル フラグ] タブへの変更はすぐに有効になりますが、次回 Windows を再起動すると失われます。
UMDH を使用する前に、アプリケーションの適切なシンボルにアクセスできる必要があります。 UMDH は、環境変数 _NT_SYMBOL_PATH で指定されたシンボル パスを使用します。 この変数に、アプリケーションのシンボルを含むパスを設定します。 Windows シンボルへのパスも含めると、解析がより完全なものになります。 このシンボル パスの構文は、デバッガーで使用されるものと同じです。詳細については、「シンボル パス」を参照してください。
たとえば、アプリケーションのシンボルが C:\MySymbols にあり、Windows シンボルにパブリック Microsoft シンボル ストアを使用し、C:\MyCache をダウンストリーム ストアとして使用する場合は、次のコマンドを使用してシンボル パスを設定します。
set _NT_SYMBOL_PATH=c:\mysymbols;srv*c:\mycache*https://msdl.microsoft.com/download/symbols
さらに、正確な結果を得るには、BSTR キャッシュを無効にする必要があります。 そのためには、OANOCACHE 環境変数を 1 に設定します。 この設定は、割り当てを追跡するアプリケーションを起動する前に行ってください。
サービスによる割り当てを追跡する必要がある場合は、システム環境変数として OANOCACHE を設定し、Windows を再起動して、この設定を有効にする必要があります。
UMDH を使用したヒープ割り当ての増加の検出
これらの準備をした後、UMDH を使用してプロセスのヒープ割り当てに関する情報を取得できます。 そのためには、以下の手順に従います。
調査するプロセスのプロセス ID (PID) を特定します。
UMDH を使用して、このプロセスのヒープ メモリ割り当てを解析し、ログ ファイルに保存します。 p スイッチに PID を指定し、-f スイッチにログ ファイル名を指定します。 たとえば、PID が 124 で、ログ ファイルの名前を Log1.txt にする場合は、次のコマンドを使用します。
umdh -p:124 -f:log1.txt
メモ帳または別のプログラムを使用してログ ファイルを開きます。 このファイルには、各ヒープ割り当ての呼び出しスタック、その呼び出しスタックを通じて行われた割り当ての回数、その呼び出しスタックを通じて消費されたメモリのバイト数が含まれています。
メモリ リークを探しているため、1 つのログ ファイルの内容だけでは不十分です。 異なる時間に記録されたログ ファイルを比較して、どの割り当てが増加しているかを特定する必要があります。
UMDH は 2 つの異なるログ ファイルを比較し、それぞれの割り当てサイズの変化を表示できます。 大なり記号 (>) を使用して、結果を 3 番目のテキスト ファイルにリダイレクトできます。 また、バイト数と割り当て数を 16 進数から 10 進数に変換する -d オプションを含めることもできます。 たとえば、Log1.txt と Log2.txt を比較し、比較の結果を LogCompare.txt ファイルに保存するには、次のコマンドを使用します。
umdh log1.txt log2.txt > logcompare.txt
LogCompare.txt ファイルを開きます。 その内容は次のようになります。
+ 5320 ( f110 - 9df0) 3a allocs BackTrace00B53 Total increase == 5320
UMDH ログ ファイル内の呼び出しスタック (「BackTrace」というラベルが付いている) ごとに、2 つのログ ファイル間で比較を行います。 この例では、最初のログ ファイル (Log1.txt) では BackTrace00B53 に対して 0x9DF0 バイトが割り当てられたことが記録されていますが、2 番目のログ ファイルでは 0xF110 バイトが割り当てられたことが記録されています。これは、2 つのログがキャプチャされた時間の間に 0x5320 バイトが追加で割り当てられたことを意味します。 これらのバイトは、BackTrace00B53 によって識別される呼び出しスタックから取得されました。
そのバックトレースに何が含まれているかを特定するには、元のログ ファイルの 1 つ (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 (10 進数で 1064) バイトずつ 0x14 (10 進数で 20) 回の個別の割り当てから割り当てられました。
呼び出しスタックには「BackTrace00B53」という識別子が与えられ、このスタック内の呼び出しが表示されます。 呼び出し履歴を確認すると、DisplayMyGraphics ルーチンが new 演算子を介してメモリを割り当てており、この演算子は Visual C++ ランタイム ライブラリを使用してヒープからメモリを取得する malloc ルーチンを呼び出していることがわかります。
これらの呼び出しのうち、ソース コードに明示的に現れる最後の呼び出しを特定します。 この場合、おそらく new 演算子です。なぜなら、malloc への呼び出しは個別の割り当てとしてではなく、new の実装の一部として発生したためです。 つまり、DisplayMyGraphics ルーチン内の new 演算子のインスタンスは、解放されていないメモリを繰り返し割り当てていることになります。