zoukankan      html  css  js  c++  java
  • 如何调试Memory Leak 和 Handle Leak

     

    1. Handle Leak:

    A handle leak is a type of software bug that occurs when a computer program

    asks for a handle to a resource but does not free the handle when it is no

    longer used. If this occurs frequently or repeatedly over an extended period

    of time, a large number of handles may be marked in-use and thus unavailable,

    causing performance problems or a crash. The leak of handle itself is not a

    big problem. The problem is that handle leak causes the leak of kernel paged

    pool.

    2. Memory Leak:

    A memory leak is a particular type of unintentional memory consumption by a

    computer program where the program fails to release memory when no longer

    needed. This condition is normally the result of a bug in a program that

    prevents it from freeing up memory that it no longer needs. Memory is

    allocated to a program, and that program subsequently loses the ability to

    access it due to program logic flaws.

     

    //---------------------------------------------------------------------------------------------

    Symptoms:

    A. Handle Leaks

    1. System running out of non-paged pool.

    2. Failing handle allocations.

    3. Degradation in system performance.

    4. Excessive paging.

    5. Tools like Task Manager and PerfMon show increasing handle count.

    B. Memory Leaks

    1. System running out of virtual memory.

    2. Failing memory allocations.

    3. Degradation in system performance.

    4. Excessive paging.

    5. Tools like Task Manager and PerfMon show increasing memory counters for

    Working Set Size, Commit Size and Virtual Memory Size.

     

     

    //---------------------------------------------------------------------------------------------

    Causes:

    A. Handle Leaks

    1. The programmer mistakenly forget to close the handles. The OPEN and CLOSE

    operations are not in pairs.

    2. Incorrect error handling.

    3. Other process inject (duplicate) the handle to the current one, who is not

    aware of the injection, and thus, does not close the injected handle.

    B. Memory Leaks

    1. Incorrect error handling.

    2. Synchronization issues.

    3. Heap block caching (like BSTR etc).

    C. Heap Fragmentation

    1. Mixing long-term with short-term allocations.

    2. Mixing small size with large size allocations.

    3. Using realloc to shrink blocks.

    4. Heap leaks.

     

    /////////////////////////////////////////////////////////////////////////////

    Detections and Debugging:

    A. Handle Leaks

    - Step1. Is It Even a Handle Leak?

    The first step of investigating a potential resource leak is to confirm that

    there really is one. Handle leaks can be easily detected by using Task

    Manager. You can display the number of handles for a given process by

    clicking the Process tab of Task Manager followed by selecting the View and

    Select Columns submenu. This brings up a dialog box that displays a host of

    options that Task Manager is capable of displaying. Check the Handle Count

    check box, and click OK. If the Handles column shows a big number or its

    value continues going up and does not go down after letting the application

    sit idle for a while, the application is likely leaking handles.

    In the example of LeakFileHandle, the handle count increases by 2 and doesn't

    go down.

    - Step2. Initial analysis

    If we could identify what type of object the handle is associated with, it

    might give us a better clue to the source of the leak. For example, if all

    the preceding handles are thread handles, we could focus our efforts in those

    parts of the code. Process Explorer is an excellent tool that has the

    capability to show different handles and associated types in a process.

    (http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx)

    The newly created handles are highlighted for a short period. This facilitates

    the detection of the leaky handles.

    In the example of LeakFileHandle, Process Explorer displays the addition of

    the following two handles after the execution.

    File C:\Users\Jialiang Ge\AppData\Local\Temp\HLeEF15.tmp

    File C:\Users\Jialiang Ge\AppData\Local\Temp\HLeEF15.tmp

    With the information of the leaky handles, we can try to spot the culprit by

    a simple code review. If the problem cannot be easily spotted, go on to the

    next step.

    - Step3. Using Handle Leak Detection Tools

    Several tools are available to help efficiently track down handle leaks. They

    are Application Verifier and !htrace.

    Application Verifier

    Application Verifier option "Disable invalid handle usage" or

    "appverif -enable Handles". This functionality is provided by appverif on

    Windows XP and later versions of OS. By activating Application Verifier,

    stack trace information is saved each time the process opens a handle,

    closes a handle, or references an invalid handle. It is this stack trace

    information that !htrace displays.

    Handle Leak Detection Tools (!htrace)

    !htrace, an extension command of windbg, can help you detect where the leak

    is occurring. Htrace stands for handle trace, and the basic idea behind the

    command is to enable the operating system to track all calls (with associated

    stack traces) that result in handles being opened and closed. When a leak has

    been identified, you can then use the !htrace extension command to display

    all the stack traces in the debugger. After all stack traces are shown, you

    can track down sporadic handle leaks in a much easier fashion.

    The general strategy for using !htrace is

    1. Prior to starting the actual reproducing of the leak, enable handle

    tracing (using !htrace -enable).

    2. Run the reproduction and let the process handle leaks.

    3. Use !htrace -diff to find the offending stacks.

    Repeating steps 1-3 will give you enough information to narrow the problem

    down in the code and find the leak by using code reviews.

    Before !htrace can be used, Application Verifier must be activated for the

    target process, and the Detect invalid handle usage option must be selected.

    By activating Application Verifier, stack trace information is saved each

    time the process opens a handle, closes a handle, or references an invalid

    handle. It is this stack trace information that !htrace displays.

    First, let's enable the handle trace:

    0:000> !htrace -enable

    Handle tracing enabled.

    Handle tracing information snapshot successfully taken.

    The -enable switch is a two-step operation. First, it enables stack tracing,

    and second, it takes a snapshot of the current state of the process with

    regard to handles (as indicated by the second line in the output). As soon as

    stack tracing has been enabled, Windows starts recording all calls that

    result in handle creation and deletion.

    0:000> g

    (19ec.153c): Break instruction exception - code 80000003 (first chance)

    eax=7ffde000 ebx=00000000 ecx=00000000 edx=7798b412 esi=00000000 edi=00000000

    eip=7794433c esp=0065fd0c ebp=0065fd38 iopl=0 nv up ei pl zr na pe nc

    cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246

    ntdll!DbgBreakPoint:

    7794433c cc int 3

    The next time you take a snapshot

    (using the -snapshot option), the !htrace extension command queries the

    operating system for all stack traces that result in handle creation and

    deletion and displays them.

    0:000> !htrace

    --------------------------------------

    Handle = 0x0000003c - OPEN

    Thread ID = 0x000017ec, Process ID = 0x000017e8

    0x77ca310c: ntdll!ZwDuplicateObject+0x0000000c

    0x75e68ad9: KERNELBASE!DuplicateHandle+0x00000069

    0x77be34e1: kernel32!DuplicateHandleStub+0x000000b3

    ** WARNING: Unable to verify checksum for CppResourceLeaks.exe

    0x00ab1627: CppResourceLeaks!LeakFileHandle+0x00000207

    0x00ab1843: CppResourceLeaks!wmain+0x00000023

    0x00ab1e48: CppResourceLeaks!__tmainCRTStartup+0x000001a8

    0x00ab1c8f: CppResourceLeaks!wmainCRTStartup+0x0000000f

    0x77be36d6: kernel32!BaseThreadInitThunk+0x0000000e

    0x77c9883c: ntdll!__RtlUserThreadStart+0x00000070

    0x77c9880f: ntdll!_RtlUserThreadStart+0x0000001b

    --------------------------------------

    Handle = 0x00000038 - OPEN

    Thread ID = 0x000017ec, Process ID = 0x000017e8

    0x77ca2e3c: ntdll!ZwCreateFile+0x0000000c

    0x75e9679f: KERNELBASE!CreateFileW+0x0000035e

    0x77be51b3: kernel32!CreateFileWStub+0x00000069

    0x00ab158f: CppResourceLeaks!LeakFileHandle+0x0000016f

    0x00ab1843: CppResourceLeaks!wmain+0x00000023

    0x00ab1e48: CppResourceLeaks!__tmainCRTStartup+0x000001a8

    0x00ab1c8f: CppResourceLeaks!wmainCRTStartup+0x0000000f

    0x77be36d6: kernel32!BaseThreadInitThunk+0x0000000e

    0x77c9883c: ntdll!__RtlUserThreadStart+0x00000070

    0x77c9880f: ntdll!_RtlUserThreadStart+0x0000001b

    --------------------------------------

    Handle = 0x00000034 - CLOSE

    Thread ID = 0x000017ec, Process ID = 0x000017e8

    0x77ca2d3c: ntdll!NtClose+0x0000000c

    0x75e87772: KERNELBASE!GetTempFileNameW+0x00000293

    0x00ab14fe: CppResourceLeaks!LeakFileHandle+0x000000de

    0x00ab1843: CppResourceLeaks!wmain+0x00000023

    0x00ab1e48: CppResourceLeaks!__tmainCRTStartup+0x000001a8

    0x00ab1c8f: CppResourceLeaks!wmainCRTStartup+0x0000000f

    0x77be36d6: kernel32!BaseThreadInitThunk+0x0000000e

    0x77c9883c: ntdll!__RtlUserThreadStart+0x00000070

    0x77c9880f: ntdll!_RtlUserThreadStart+0x0000001b

    --------------------------------------

    Handle = 0x00000034 - OPEN

    Thread ID = 0x000017ec, Process ID = 0x000017e8

    0x77ca2e3c: ntdll!ZwCreateFile+0x0000000c

    0x75e9679f: KERNELBASE!CreateFileW+0x0000035e

    0x75e87762: KERNELBASE!GetTempFileNameW+0x00000208

    0x00ab14fe: CppResourceLeaks!LeakFileHandle+0x000000de

    0x00ab1843: CppResourceLeaks!wmain+0x00000023

    0x00ab1e48: CppResourceLeaks!__tmainCRTStartup+0x000001a8

    0x00ab1c8f: CppResourceLeaks!wmainCRTStartup+0x0000000f

    0x77be36d6: kernel32!BaseThreadInitThunk+0x0000000e

    0x77c9883c: ntdll!__RtlUserThreadStart+0x00000070

    0x77c9880f: ntdll!_RtlUserThreadStart+0x0000001b

    --------------------------------------

    ...

    ...

    --------------------------------------

    Handle = 0x00000024 - OPEN

    Thread ID = 0x000017ec, Process ID = 0x000017e8

    ...

    --------------------------------------

    Handle = 0x00000020 - OPEN

    Thread ID = 0x000017ec, Process ID = 0x000017e8

    ...

    --------------------------------------

    Parsed 0xC stack traces.

    Dumped 0xC stack traces.

    The output of !htrace shows all stack traces recorded for openning and

    closing handles. Some entries are in pairs. For example,

    Handle = 0x00000034 - CLOSE

    Thread ID = 0x000017ec, Process ID = 0x000017e8

    ...

    --------------------------------------

    Handle = 0x00000034 - OPEN

    Thread ID = 0x000017ec, Process ID = 0x000017e8

    ...

    It means that the handle 0x00000034 was opened and closed properply and was

    not leaked. !htrace has a handy command option, -diff, that correlates all

    paths that resulted in creation and deletion (since the last snapshot) and

    reports only the stack traces that do not have a delete stack associated.

    0:000> !htrace -diff

    Handle tracing information snapshot successfully taken.

    0xd9 new stack traces since the previous snapshot.

    Ignoring handles that were already closed...

    Outstanding handles opened since the previous snapshot:

    --------------------------------------

    Handle = 0x0000003c - OPEN

    Thread ID = 0x000017ec, Process ID = 0x000017e8

    0x77ca310c: ntdll!ZwDuplicateObject+0x0000000c

    0x75e68ad9: KERNELBASE!DuplicateHandle+0x00000069

    0x77be34e1: kernel32!DuplicateHandleStub+0x000000b3

    0x00ab1627: CppResourceLeaks!LeakFileHandle+0x00000207

    0x00ab1843: CppResourceLeaks!wmain+0x00000023

    0x00ab1e48: CppResourceLeaks!__tmainCRTStartup+0x000001a8

    0x00ab1c8f: CppResourceLeaks!wmainCRTStartup+0x0000000f

    0x77be36d6: kernel32!BaseThreadInitThunk+0x0000000e

    0x77c9883c: ntdll!__RtlUserThreadStart+0x00000070

    0x77c9880f: ntdll!_RtlUserThreadStart+0x0000001b

    --------------------------------------

    Handle = 0x00000038 - OPEN

    Thread ID = 0x000017ec, Process ID = 0x000017e8

    0x77ca2e3c: ntdll!ZwCreateFile+0x0000000c

    0x75e9679f: KERNELBASE!CreateFileW+0x0000035e

    0x77be51b3: kernel32!CreateFileWStub+0x00000069

    0x00ab158f: CppResourceLeaks!LeakFileHandle+0x0000016f

    0x00ab1843: CppResourceLeaks!wmain+0x00000023

    0x00ab1e48: CppResourceLeaks!__tmainCRTStartup+0x000001a8

    0x00ab1c8f: CppResourceLeaks!wmainCRTStartup+0x0000000f

    0x77be36d6: kernel32!BaseThreadInitThunk+0x0000000e

    0x77c9883c: ntdll!__RtlUserThreadStart+0x00000070

    0x77c9880f: ntdll!_RtlUserThreadStart+0x0000001b

    --------------------------------------

    Handle = 0x00000024 - OPEN

    Thread ID = 0x000017ec, Process ID = 0x000017e8

    ...

    --------------------------------------

    Handle = 0x00000020 - OPEN

    Thread ID = 0x000017ec, Process ID = 0x000017e8

    ...

    --------------------------------------

    Displayed 0x4 stack traces for outstanding handles opened since the previous

    snapshot.

    Therefore, our focus should be on the handles 0x3c, 0x38, 0x24, 0x20.

    !handle <handle> tells the type of the handle. For example,

    0:000> !handle 0x3c

    Handle 3c

    Type File

    The call-stacks in the output of !htrace give the hint of the culprit

    functions that open the leaky handles. Take the

    CppResourceLeaks!LeakFileHandle function as an example:

    --------------------------------------

    Handle = 0x00000038 - OPEN

    Thread ID = 0x000017ec, Process ID = 0x000017e8

    0x77ca2e3c: ntdll!ZwCreateFile+0x0000000c

    0x75e9679f: KERNELBASE!CreateFileW+0x0000035e

    0x77be51b3: kernel32!CreateFileWStub+0x00000069

    0x00ab158f: CppResourceLeaks!LeakFileHandle+0x0000016f

    0x00ab1843: CppResourceLeaks!wmain+0x00000023

    0x00ab1e48: CppResourceLeaks!__tmainCRTStartup+0x000001a8

    0x00ab1c8f: CppResourceLeaks!wmainCRTStartup+0x0000000f

    0x77be36d6: kernel32!BaseThreadInitThunk+0x0000000e

    0x77c9883c: ntdll!__RtlUserThreadStart+0x00000070

    0x77c9880f: ntdll!_RtlUserThreadStart+0x0000001b

    One exception is that, if the call-stack looks very convoluted and does not

    make any sense for the target process, the handle is likedly to be injected

    (duplicated) from another process. Take the LeakInjectedHandle as an example:

    0:000> !htrace -diff

    Handle tracing information snapshot successfully taken.

    0x1 new stack traces since the previous snapshot.

    Ignoring handles that were already closed...

    Outstanding handles opened since the previous snapshot:

    --------------------------------------

    Handle = 0x00000114 - OPEN

    Thread ID = 0x00001700, Process ID = 0x00000d30

    0x77ca310c: ntdll!ZwDuplicateObject+0x0000000c

    0x75e68ad9: KERNELBASE!DuplicateHandle+0x00000069

    0x77be34e1: kernel32!DuplicateHandleStub+0x000000b3

    0x00101991: +0x00101991

    0x00101b78: +0x00101b78

    0x00102188: +0x00102188

    0x00101fcf: +0x00101fcf

    0x77be36d6: kernel32!BaseThreadInitThunk+0x0000000e

    0x77c9883c: ntdll!__RtlUserThreadStart+0x00000070

    0x77c9880f: ntdll!_RtlUserThreadStart+0x0000001b

    --------------------------------------

    Another example:

    --------------------------------------

    Handle = 0x000007D8 - OPEN

    Thread ID = 0x00001700, Process ID = 0x00000d30

    0x01001363: 09htarget!XcptFilter+0x00000009

    0x010014D3: 09htarget!_NULL_IMPORT_DESCRIPTOR+0x000000CB

    0x7C816FD7: kernel32!BaseProcessStart+0x00000023

    --------------------------------------

    The Process ID and Thread ID reported by !htrace -diff belong to the

    injecting process. We can therefore find out who duplicated the handle to

    the leaking proecss.

    B. Memory Leaks

    - Step1. Is It Even a Memory Leak?

    We use Task Manager or Process Explorer to identify the potential memory

    leak. First, bring up Task Manager and select the Memory Usage and Virtual

    Memory Size columns in Pre-Vista machines, and the Memory - Working Set and

    Memory - Commit Size columns in Post-Vista machines. Virtual memory (aka.

    Commit Size or Private Bytes) indicates how much memory the process is using

    overall (both in and out of physical memory), whereas the Mem Usage (aka.

    Working Set) column shows how much physical memory the process is consuming.

    Typically, the best indicator for memory leaks is an increase in virtual

    memory size (e.g. Commmit Size) and not fluctuations in working set size.

    Virtual Memmory

    Private Bytes (aka. Commit Size or Virtual Memory Size)

    Peak Private Bytes

    Virtual Size

    Page Faults

    Page Fault Delta

    Physical Memory

    Working Set (aka. Mem Usage)

    WS Private: physical memory owned by just this process

    WS Shareable: physical memory that could be shared with other processes

    WS Shared: shareable memory that is current shared with other processes

    (It is a subset of shareable WS. It tells the possible size of memory

    that will still be in use after the current process exits)

    Peak Working Set

    Note: Private Bytes may be bigger than Working Set Size when much virtual

    memory is paged out. Private Bytes may also be smaller than Working Set Size

    because the memory allocations are in pages (influencing working set size)

    while the virtual memory size is in bytes which must be smaller than or equal

    to one page of memory.

    In the example of LeakHeapMemory, we can observe in Task Manager the

    continous increase of both virtual memory size and memory usage.

    - Step2. Using Memory Leak Detection Tools

    Several tools are available to help efficiently track down memory leaks. They

    are UMDH, LeakDiag, !address, !heap, Pageheap, and CRTDBG.

    UMDH

    http://support.microsoft.com/kb/268343

    UMDH works by taking snapshots of the virtual memory usage of a process at

    different points in time and logging them to text files. UMDH can compare the

    log files taken at 2 instances of time and list the allocations that are

    leaking. It requires GFLAGS "User Stack Trace Database Option" (+ust) to be

    enabled in order to record the stack traces of the memory allocation requests

    made by the process. Allocations and assocated stack traces in the log file

    are tagged with "BackTracexxxxx". UMDH uses these tags as identifiers for

    reporting leaks.

    Note: UMDH tracks heap allocations only. In other words, it cannot track

    allocations that are originating from non-heap-related memory activity (such

    as calls to VirtualAlloc).

    First, we need to enable stack traces for memory allocations. To accomplish

    this, we use the gflags tool and enable "Create user mode stack trace

    database" for CppResourceLeaks.exe.

    gflags -i CppResourceLeaks.exe +ust

    The command needs to be run as administrator. It does not enable stack

    tracing for processes that are already running, but it enables stack tracing

    for all future executions of CppResourceLeaks.exe. Alternatively, you can set

    the flag through the GFLAGS user interface. Use the -ust option for gflags to

    disable the stack tracing when you are finished debugging.

    Second, we need to configure the debug symbols. One of the most important

    steps to using UMDH is to make sure that you have good symbol files (.dbg or

    .pdb file) to get a good stack trace. You can use the Microsoft Symbol Server

    to obtain debug symbol files (http://support.microsoft.com/kb/311503/). UMDH

    is capable of using dbghelp.dll to reading symbol files and resolving

    addresses to symbolic function names.

    Third, start CppResourceLeaks.exe and get its process ID from Task Manager.

    Fourth, use UMDH to get a heap dump before the apparent leak happens with the

    following command:

    umdh -p:PID -f:CppResourceLeaks1.log

    Now you have a complete heap dump of the CppResourceLeaks process in the

    CppResourceLeaks1.log file. This file shows all of the allocations that were

    made and the callstacks where the allocations were made. The log in this

    state is not readable as the symbols are not resolved. UMDH can be instructed

    to resolve the symbols by simulating a log comparison with an empty log,

    using the command:

     

    umdh -v CppResourceLeaks1.log > CppResourceLeaks1.txt

    The resulting log contains something like:

    + 2018 ( 2018 - 0) 1 allocs BackTrace73F28

    + 1 ( 1 - 0) BackTrace73F28 allocations

    ntdll!RtlAllocateHeap+00000274

    kernel32!ConsoleAllocateCaptureBuffer+0000006F

    kernel32!ReadConsoleInternal+0000007E

    kernel32!ReadConsoleA+00000040

    kernel32!ReadFileImplementation+00000075

    MSVCR90D!_read_nolock+0000062C

    MSVCR90D!_read+00000219

    MSVCR90D!_filbuf+00000113

    MSVCR90D!getc+00000208

    MSVCR90D!_fgetchar+00000010

    MSVCR90D!getchar+0000000A

    CppResourceLeaks!LeakHeapMemory+0000003D (...\cppresourceleaks.cpp, 221)

    CppResourceLeaks!wmain+00000023 (...\cppresourceleaks.cpp, 260)

    CppResourceLeaks!__tmainCRTStartup+000001A8 (crtexe.c, 583)

    CppResourceLeaks!wmainCRTStartup+0000000F (crtexe.c, 403)

    kernel32!BaseThreadInitThunk+0000000E

    ntdll!__RtlUserThreadStart+00000070

    ntdll!_RtlUserThreadStart+0000001B

    Fifth, while memory is leaking, take the second snapshot of the heap:

    umdh -p:PID -f:CppResourceLeaks2.log

    Last, use UMDH to compare the UMDH logs:

    UMDH CppResourceLeaks1.log CppResourceLeaks2.log > cmp12.txt

    In cmp12.txt, 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).

    For example (LeakHeapMemory()),

    + 2a98c90 ( 2a98c90 - 0) 15cf4 allocs BackTrace74050

    + 15cf4 ( 15cf4 - 0) BackTrace74050 allocations

    ntdll!RtlAllocateHeap+00000274

    CppResourceLeaks!LeakHeapMemory+00000083 (...\cppresourceleaks.cpp, 228)

    CppResourceLeaks!wmain+00000023 (...\cppresourceleaks.cpp, 260)

    CppResourceLeaks!__tmainCRTStartup+000001A8 (crtexe.c, 583)

    CppResourceLeaks!wmainCRTStartup+0000000F (crtexe.c, 403)

    kernel32!BaseThreadInitThunk+0000000E

    ntdll!__RtlUserThreadStart+00000070

    ntdll!_RtlUserThreadStart+0000001B

     

    - 2018 ( 0 - 2018) 0 allocs BackTrace73F28

    - 1 ( 0 - 1) BackTrace73F28 allocations

    ntdll!RtlAllocateHeap+00000274

    kernel32!ConsoleAllocateCaptureBuffer+0000006F

    kernel32!ReadConsoleInternal+0000007E

    kernel32!ReadConsoleA+00000040

    kernel32!ReadFileImplementation+00000075

    MSVCR90D!_read_nolock+0000062C

    MSVCR90D!_read+00000219

    MSVCR90D!_filbuf+00000113

    MSVCR90D!getc+00000208

    MSVCR90D!_fgetchar+00000010

    MSVCR90D!getchar+0000000A

    CppResourceLeaks!LeakHeapMemory+0000003D (...\cppresourceleaks.cpp, 221)

    CppResourceLeaks!wmain+00000023 (...\cppresourceleaks.cpp, 260)

    CppResourceLeaks!__tmainCRTStartup+000001A8 (crtexe.c, 583)

    CppResourceLeaks!wmainCRTStartup+0000000F (crtexe.c, 403)

    kernel32!BaseThreadInitThunk+0000000E

    ntdll!__RtlUserThreadStart+00000070

    ntdll!_RtlUserThreadStart+0000001B

    According to the comparison, the first call-stack appears for 15cf4 times,

    and 2a98c90 bytes were leaked because of the LeakHeapMemory function.

    !address

    The !address extension command comes in very handy when you want to get a

    quick overview of where the memory in your process is really located. The

    command gives statistics, such as memory region usage in heaps, stack, free,

    and so on.

    For example (LeakHeapMemory()),

    0:000> !address -summary

    ProcessParametrs 00381a18 in range 00380000 0039c000

    Environment 00380810 in range 00380000 0039c000

    -------------------- Usage SUMMARY --------------------------

    TotSize ( KB) Pct(Tots) Pct(Busy) Usage

    11c4000 ( 18192) : 00.87% 02.27% : RegionUsageIsVAD

    4f132000 ( 1295560) : 61.78% 00.00% : RegionUsageFree

    397000 ( 3676) : 00.18% 00.46% : RegionUsageImage

    200000 ( 2048) : 00.10% 00.26% : RegionUsageStack

    2000 ( 8) : 00.00% 00.00% : RegionUsageTeb

    2f760000 ( 777600) : 37.08% 97.01% : RegionUsageHeap

    0 ( 0) : 00.00% 00.00% : RegionUsagePageHeap

    1000 ( 4) : 00.00% 00.00% : RegionUsagePeb

    0 ( 0) : 00.00% 00.00% : RegionUsageProcessParametrs

    0 ( 0) : 00.00% 00.00% : RegionUsageEnvironmentBlock

    Tot: 7fff0000 (2097088 KB) Busy: 30ebe000 (801528 KB)

    -------------------- Type SUMMARY --------------------------

    TotSize ( KB) Pct(Tots) Usage

    4f132000 ( 1295560) : 61.78% : <free>

    398000 ( 3680) : 00.18% : MEM_IMAGE

    1be000 ( 1784) : 00.09% : MEM_MAPPED

    30968000 ( 796064) : 37.96% : MEM_PRIVATE

    -------------------- State SUMMARY --------------------------

    TotSize ( KB) Pct(Tots) Usage

    2f4cf000 ( 774972) : 36.95% : MEM_COMMIT

    4f132000 ( 1295560) : 61.78% : MEM_FREE

    19ef000 ( 26556) : 01.27% : MEM_RESERVE

    Largest free region: Base 30f00000 - Size 2a970000 (697792 KB)

    The column Pct(Tots) means the percentage of the entry in total virtual

    memory. The column Pct(Busy) means the percentage of the entry in busy

    virtual memory.

    RegionUsageIsVAD - memory allocated by VirtualAlloc in VMM

    RegionUsageHeap - memory allocated by heap manager

    From the output

    11c4000 ( 18192) : 00.87% 02.27% : RegionUsageIsVAD

    2f760000 ( 777600) : 37.08% 97.01% : RegionUsageHeap

    we see that most used memory is heap alloc, instead of virtual alloc, so it's

    a heap memory leak.

    !heap -s, !heap -a, and !heap -x -v

    The !heap -s command allows you to get a detailed look at the heap summary of

    the process and the suspicious heaps. Judging from the pattern of allocations

    in the !heap extension command output (e.g. there are tons of blocks

    allocated of same user size), chances are good that we can locate the heap

    blocks that are leaked. Furthermore, by looking around at the heap block

    contents (e.g. does it contain ASCII characters? does it correspond to the

    address of some function / symbol?) we may see how / why the block was

    allocated.

    Please note that because a lot of changes happened to the heap manager in

    Windows Vista and the later operating system, the allocation of heap entries

    may vary. For example, the allocated block may be bigger than requested, or

    the allocation granually grows in size.

    To prove that this is indeed a leak, you can search for references to the

    block in the process's memory space. If these potentially leaked blocks were

    being used (perhaps cached), there would need to be a reference somewhere in

    memory that points to that heap block. If there are no references, it means

    that we definitely have a leak. The !heap -x -v allows you to search the

    entire memory space of the process for the presence of a specified address.

    For example (LeakHeapMemory()),

    0:000> !heap -s

    Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast

    (k) (k) (k) (k) length blocks cont. heap

    -----------------------------------------------------------------------------

    00150000 00000002 16384 16352 16352 2 0 1 0 0 L

    00250000 00008000 64 12 12 10 1 1 0 0

    00380000 00001002 64 44 44 9 2 1 0 0 L

    -----------------------------------------------------------------------------

    The heap 00150000 occupies abnormally large memory.

    0:000> !heap -a 00120000

    ...

    00246240: 00200 . 00200 [01] - busy (1f4)

    00246440: 00200 . 00200 [01] - busy (1f4)

    00246640: 00200 . 00200 [01] - busy (1f4)

    00246840: 00200 . 00200 [01] - busy (1f4)

    00246a40: 00200 . 00200 [01] - busy (1f4)

    00246c40: 00200 . 00200 [01] - busy (1f4)

    00246e40: 00200 . 00200 [01] - busy (1f4)

    00247040: 00200 . 00200 [01] - busy (1f4)

    00247240: 00200 . 00200 [01] - busy (1f4)

    ...

    We find a large number of blocks with the same user allocation size (1f4).

    This is usually a good indicator that they are potentially leaked blocks.

    The next step is to find out what these blocks actually contain. If we were

    leaking memory, it would be reasonable to expect data related to our

    application contained within those blocks:

    0:000> db 00246c40+0x8

    00246c48 41 6c 6c 2d 49 6e 2d 4f-6e 65 20 43 6f 64 65 20 All-In-One Code

    00246c58 46 72 61 6d 65 77 6f 72-6b 00 00 00 00 00 00 00 Framework.......

    Before we come to the conclusion that this is in fact a leak, we should

    verify it by searching for references to the block in the process's memory

    space.

    0:000> !heap -x -v 00246c40+0x8

    Entry User Heap Segment Size PrevSize Unused Flags

    -----------------------------------------------------------------------------

    00246c40 00246c48 00150000 00150640 200 200 c busy

    Search VM for address range 00246c40 - 00246e3f :

    The search yielded zero results. As stated before, if a currently allocated

    heap block is not referenced anywhere in memory, we can safely say that we

    are leaking that block.

    !heap -l

    The !heap -l command causes debugger to look for leaked heap blocks. It

    automates the act of dumping out all heap blocks (!heap -s) and

    systematically searching for any potentially leaked blocks (!heap -x -v).

    Please note that !heap -l does not work if full page heap is enabled for the

    process.

    For example (LeakHeapMemory()),

    0:000> !heap -l

    Searching the memory for potential unreachable busy blocks.

    Heap 00150000

    Heap 00250000

    Heap 00380000

    Scanning VM ...

    Scanning references from 32822 busy blocks (16 MBytes) ....

    Entry User Heap Segment Size PrevSize Unused Flags

    -----------------------------------------------------------------------------

    00154640 00154648 00150000 00150000 200 200 c busy

    00154840 00154848 00150000 00150000 200 200 c busy

    00154a40 00154a48 00150000 00150000 200 200 c busy

    00154e40 00154e48 00150000 00150000 200 200 c busy

    00155040 00155048 00150000 00150000 200 200 c busy

    00155240 00155248 00150000 00150000 200 200 c busy

    00155640 00155648 00150000 00150000 200 200 c busy

    00155840 00155848 00150000 00150000 200 200 c busy

    ...

    29050 potential unreachable blocks were detected.

    Pageheap, and !heap -p -a

    After you have identified a potential leak culprit using the above !heap

    commands, it would be useful to see which stack trace made the allocation to

    begin with. If we had that, we could find out exactly what the code was doing

    and what it was allocating.

    First, we need to enable stack tracing using Application Verifier. Second,

    run !heap -p -a upon the address that we thought was leaking. Not only will

    we see general information about the leaked address (such as which heap it's

    in and the trace ID), but we also get the full stack trace of the code that

    made the allocation. From here, it is a trivial exercise to code review and

    find the culprit code.

    Note, while using page heap, !heap -s, !heap -a, !heap -x -v and !heap -l may

    not work at all! We should find the culprit memory block and run !heap -p -a

    upon it directly.

    For example (LeakHeapMemory()),

    0:000> !address 0b768e08

    Usage: PageHeap

    Base Address: 0b768000

    End Address: 0b769000

    Region Size: 00001000

    Type: 00020000 MEM_PRIVATE

    State: 00001000 MEM_COMMIT

    Protect: 00000004 PAGE_READWRITE

    More info: !heap -p 0x150000

    More info: !heap -p -a 0xb768e08

    0:000> !heap -p -a 0xb768e08

    address 0b768e08 found in

    _DPH_HEAP_ROOT @ 151000

    in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)

    b72e700: b768e08 1f4 - b768000 2000

    7c83d9aa ntdll!RtlAllocateHeap+0x00000e9f

    0039fd2c vfbasics!AVrfpRtlAllocateHeap+0x000000b1

    00401046 CppResourceLeaks!LeakHeapMemory+0x00000046

    Not only do we see general information about the leaked address (such as

    which heap it's in and the trace ID), but we also get the full stack trace of

    the code that made the allocation. From here, it is a trivial exercise to

    code review and find the culprit code.

    CRTDBG

    http://msdn.microsoft.com/en-us/library/x98tx3cf.aspx

    Debug version of C Run Time Library has facilities to debug C Run Time Heap

    related problems like leaks and corruption. It requires application to be

    rebuilt with debug CRT.

    When _DEBUG is defined the following functions call the respective _xxx_dbg()

    versions which provide extra debugging capabilities: malloc, realloc, calloc,

    expand, free, msize.

    When _CRTDBG_MAP_ALLOC is defined the _xxx_dbg() versions of the CRT heap

    functions are call directly instead of the standard versions. This enables

    the _xxx_dbg() funtions to record the location (source file path & line

    number) where the allocation function is being invoked. The information can

    be used to identify the location in the source code where the block was

    allocated.

    After enabling the above flags, debug CRT heap manager can perform various

    types of checking like heap corruptions and heap leaks in run-time or debug-

    time:

    _CrtSetDbgFlag() retrieves and modifies the behavior of the debug heap

    _CrtCheckMemory() performs integrity check on memory blocks

    _CrtDumpMemoryLeaks() dumps all heap blocks when memory leaks occurs

    For example ((LeakCRTHeapMemory()),

    First, we enable the debug heap functions, include the following statements

    in the program:

    #define _CRTDBG_MAP_ALLOC

    #include <stdlib.h>

    #include <crtdbg.h>

    Note: The #include statements must be in the order shown here. If you change

    the order, the functions you use may not work properly.

    By including crtdbg.h, you map the malloc and free functions to their debug

    versions, _malloc_dbg and _free_dbg, which keep track of memory allocation

    and deallocation. This mapping occurs only in a debug build (in which _DEBUG

    is defined). Release builds use the ordinary malloc and free functions.

    At the end of the function LeakCRTHeapMemory, add the line

    _CrtDumpMemoryLeaks();

    When you run your program under the debugger, _CrtDumpMemoryLeaks displays

    memory leak information in the Output window. The memory leak information

    looks like this:

    Detected memory leaks!

    Dumping objects ->

    ...\cppresourceleaks.cpp(257) : {101} normal block at 0x00511F40, 500 bytes long.

    Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD

    Object dump complete.

    //---------------------------------------------------------------------------------------------

    Fixes:

    A. Handle Leaks

    1. Be careful to close the handles after they are created.

    2. Consider employing an auto acquire/release construct. Very similar to auto

    pointers, this construct allows you to acquire a handle at any given scope

    and automatically free it when the auto construct goes out of scope.

    B. Memory Leaks

    1. Be careful that the allocation and deallocation of memory on the heap

    should be paired and should target the same heap. The most common pairs are:

    new - delete

    malloc - free

    GlobalAlloc - GlobalFree

    LocalAlloc - LocalFree

    CoTaskMemAlloc - CoTaskMemFree

    SysAllocString - SysFreeString

    2. Consider using an auto construct that automatically deletes memory when

    the variable goes out of scope, such as auto_ptr in STL.

    3. Consider overloading the allocation APIs used in your application. This

    allows for trapping all calls to memory allocations, thereby giving you hooks

    to all memory allocations performed by your applications. The allocation

    hooks can then be used to track memory allocations, simulate failures in

    memory allocations, and much more.

    //---------------------------------------------------------------------------------------------

    References:

    Mario Hewardt & Daniel Pravat, Advanced Windows Debugging Ch. 9 (2007), at

    http://advancedwindowsdebugging.com/. Copyright 2008 by Pearson Education,

    Inc. This material may be distributed only subject to the terms and

    conditions set forth in the Open Publication License, v1.0 or later (the

    latest version is presently available at http://www.opencontent.org/openpub/).

    Excerpted by Jialiang Ge, 2009.

    Wiki: Handle leak

    http://en.wikipedia.org/wiki/Handle_leak

    Debug Tutorial Part 5: Handle Leaks

    http://www.codeproject.com/KB/debug/cdbntsd5.aspx

    Use !htrace to debug handle leak

    http://blogs.msdn.com/junfeng/archive/2008/04/21/use-htrace-to-debug-handle-leak.aspx

    Resource Leaks: Detecting, Locating, and Repairing Your Leaky GDI Code

    http://msdn.microsoft.com/en-us/magazine/cc301756.aspx

    Wiki: Memory leak

    http://en.wikipedia.org/wiki/Memory_leak

    The poor man's way of identifying memory leaks

    http://blogs.msdn.com/oldnewthing/archive/2005/08/15/451752.aspx

    Umdhtools.exe: How to use Umdh.exe to find memory leaks

    http://support.microsoft.com/kb/268343

    MSDN: Memory Leak Detection and Isolation

    http://msdn.microsoft.com/en-us/library/x98tx3cf.aspx

    //---------------------------------------------------------------------------------------------

     

  • 相关阅读:
    阿里云的使用运维安装
    阿里云的使用运维安装
    promis:异步编程
    promis:异步编程
    微信开发笔记
    细数那些带打赏功能的平台
    细数那些带打赏功能的平台
    Java Web Services (0)
    4、查询(2)
    COGS——C610. 数对的个数
  • 原文地址:https://www.cnblogs.com/pugang/p/2206538.html
Copyright © 2011-2022 走看看