[メモリリーク #1] [HOWTO] UMDH を用いたメモリリーク調査技法

Last Update: feedback 共有

(※ 2013 年 8 月 18 日に JAPAN Platform SDK(Windows SDK) Support Team Blog に公開した情報の再編集・再掲です。)

こんにちは、Japan Developer Support Core チーム 平田 a.k.a ぴろとです。
本日は、メモリ リークに焦点を当てて調査を行う方法についてご紹介いたします。

概要説明

多くの場合、アプリケーションのメモリ リーク箇所の特定は非常に難しいトラブルシューティングとなります。しかしながら、本日ご紹介させていただく User-Mode Dump Heap (以下、UMDH) というツールを用いることで、メモリ リークの発生箇所や、リークの大きさを特定することができます。私たちもお客様からのお問い合わせを調査する際に利用しているツールですので、ひょっとしたらメモリ リークしてるかも?という場合にぜひともご利用いただきたいツールです。

UMDH ツールは、Windows SDK に含まれる Debugging Tools for Windows の一部として提供されており、簡単に利用できるのですが、VirtualAlloc() や VirtualAllocEx() で確保されたメモリのメモリ リークは残念ながら検出することができません。その点だけお気を付けいただければ便利に使えるツールです。

自分のプログラムにメモリ リークが疑われたら、まず、パフォーマンス モニターでメモリに関する情報を採取してみましょう。これには、パフォーマンス モニターの Process カテゴリの情報を採取し、 Private Bytes の値を監視します。この値が右肩上がりの傾向であれば、メモリ リークが発生している可能性あるため、より詳しく調査をするために UMDH の出番となります。

ツールのインストール

  1. UMDH のインストール方法
    以下の SDK をダウンロードしてインストールします。この時、[Debugging Tools for Windows] をインストールするようにチェックボックスにチェックを入れます。
    Windows 10 SDK
    https://developer.microsoft.com/ja-jp/windows/downloads/windows-10-sdk/
  2. インストールが成功すると、以下のフォルダーに Debugger Tools for Windows がインストールされます。
    C:\Program Files (x86)\Windows Kits\10\Debuggers\x64
    C:\Program Files (x86)\Windows Kits\10\Debuggers\x86

使ってみよう

サンプル作成

今回は、以下のようなサンプル アプリケーションを使って UMDH の使い方を簡単にご説明します。まずはメモリリークするサンプルプログラムを作成します。
このサンプルコードを LeakSample.cpp として保存します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "stdafx.h"
#include "Leak.h"
#include <conio.h>
#include <stdlib.h>

void heapleak()
{
CLeak *p;
p = new CLeak();
}

int _tmain(int argc, _TCHAR* argv[])
{
_getch();
for(int i=0;i<100;i++)
{
heapleak();
}
_getch();
return 0;
}

また、以下のヘッダファイルを Leak.h として保存してプロジェクトに入れてください。

1
2
3
4
5
6
7
8
9
10
11
#pragma once
class CLeak
{
public:
int a;
int b;
int c;
int d;
CLeak() {};
~CLeak() {};
};

LeakSample.exeとしてコンパイルしましょう。コンパイル時に生成されたシンボルファイル (拡張子が .pdb のファイル) を、C:\symbols.cache_pubにコピーしてください。

使用方法

  1. 環境変数の設定
    UMDH による調査では、最低 2 回 UMDH を実行して、実行した時点で確保されているメモリの差分よりメモリ リークを調査します。UMDH が実行されて、次に実行される間に確保、解放されたメモリは適切に解放されたメモリですので UMDH では検出することができません。
    また、UMDH は管理者権限で実行する必要があります。そのため、以下の手順はすべて管理者権限をもつユーザーとして実行してください。
    コマンドプロンプトを起動して、環境変数 _NT_SYMBOL_PATH にシンボル ファイルの参照元を設定します。
    >set _NT_SYMBOL_PATH=srv*C:\symbols.cache_pub*http://msdl.microsoft.com/download/symbols

  2. flags コマンドを使い、スタックトレースの取得を有効にします。
    「Leaksample.exe」がメモリ リークの調査対象なので、以下のように gflags コマンドを実行します。
    > gflags -i Leaksample.exe +ust

  3. メモリ リークの調査対象となるアプリケーションを実行します。

  4. タスクマネージャーや tlist コマンドなどを使い、調査対象アプリケーションのプロセス ID を特定します。
    tlist コマンドを使って Leaksample.exe のプロセス PID を確認した例がこちらになります。この場合、プロセス ID は 6248 であることがわかります。

     >tlist
     0 System Process
     4 System
     <... 省略 ...>
     6248 LeakSample.exe    LeakSample.exe
     2704 conhost.exe
  5. UMDH による 1 回目の情報採取をします。
    プロセス ID 6248 のアプリケーションに対する情報を C:\TEMP\log1.log として保存するために次のコマンドを実行します。
    C:\Program Files (x86)\Windows Kits\10\Debuggers\x86>umdh -p:6248 -f:C:\TEMP\log1.log

  6. アプリケーションをしばらく実行し続けて、メモリ リークの現象を発生させます。

  7. 2 回目の情報採取をします。
    C:\Program Files (x86)\Windows Kits\10\Debuggers\x86>umdh -p:6248 -f:C:\TEMP\log2.log

解析してみよう

1 回目と 2 回目での間にて確保されたメモリの差分がリークしたメモリになります。この差分の検出するときも UMDH を使います。

  1. UMDH で情報採取結果を取得するために、以下のコマンドにて結果を取得します
    C:\Program Files (x86)\Windows Kits\10\Debuggers\x86>umdh C:\TEMP\log1.log C:\TEMP\log2.log > C:\TEMP\diff12.txt
  2. diff12.txt をテキストエディタで開きます
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//                                                                         
// Each log entry has the following syntax:
//
// + BYTES_DELTA (NEW_BYTES - OLD_BYTES) NEW_COUNT allocs BackTrace TRACEID
// + COUNT_DELTA (NEW_COUNT - OLD_COUNT) BackTrace TRACEID allocations
// ... stack trace ...
//
// where:
//
// BYTES_DELTA - increase in bytes between before and after log
// NEW_BYTES - bytes in after log
// OLD_BYTES - bytes in before log
// COUNT_DELTA - increase in allocations between before and after log
// NEW_COUNT - number of allocations in after log
// OLD_COUNT - number of allocations in before log
// TRACEID - decimal index of the stack trace in the trace database
// (can be used to search for allocation instances in the original
// UMDH logs).
//
\+ 1450 ( 1450 - 0) 64 allocs BackTraceA51584
\+ 64 ( 64 - 0) BackTraceA51584 allocations
ntdll!RtlAllocateHeap+1CC
MSVCR110D!_heap_alloc_base+51
MSVCR110D!_free_dbg_nolock+6FF
MSVCR110D!_nh_malloc_dbg+7D
MSVCR110D!_nh_malloc_dbg+2A
MSVCR110D!malloc+19
MSVCR110D!operator new+F
LeakSample!heapleak+44 (C:\leaksample.cpp, 13)
LeakSample!wmain+4A (c:\leaksample.cpp, 22)
LeakSample!__tmainCRTStartup+199
LeakSample!wmainCRTStartup+D
KERNEL32!BaseThreadInitThunk+E
ntdll!__RtlUserThreadStart+72
ntdll!_RtlUserThreadStart+1B
Total increase == 1450 requested + af0 overhead = 1f40
  1. 上記ログから、LeakSample!heapleak+44 (c:\leaksample.cpp, 13) にてリークしていることがわかります。実際にリークするサンプルコードを確認してみると、p = new CLeak(); となっておりますが、該当ポインタに対して、delete がありません。これにより、メモリリークのコード箇所を確認することができました!


本ブログの内容は弊社の公式見解として保証されるものではなく、開発・運用時の参考情報としてご活用いただくことを目的としています。もし公式な見解が必要な場合は、弊社ドキュメント (https://docs.microsoft.comhttps://support.microsoft.com) をご参照いただくか、もしくは私共サポートまでお問い合わせください。