Thursday, January 5, 2012

Looking at Heap Allocations With Windbg

I will keep this brief.  !heap is main debugger extension for allocation debugging.  I will show you some tricks to get started using it.  You can read up on all of the options by either typing !heap -? in the debugger or reading up on it in MSDN.

As always, don't do it the hard way.  Use tools like Application Verifier to help you find issues more quickly.  Appverif is awesome at identifying and even root causing many heap, leak, deadlock, and many other issues.  You should get in the habit of always debugging with it on.  Still, you should still know how to debug heap issues, so read on.


First think you will notice is that most of !heap's options take either a heap index or a heap address.  You can use 0 for process' heap, but there can be a lot and you might have to wait a while.  I will show you how to get it, and then you can play with the other options to learn what they do. 

Generally you have a pointer of an allocation, and you want to know more about it.  To find the heap for the allocation you are looking for there are two simple ways that I can think of.
  1. !heap -x ptr_address

    This will cause the debugger to search for the heap block with that address.  This can take a little while depending on how many allocations there are.  Using the -v option will take even longer, so you should probably avoid using it.

    example, double free:
    0:000> !analyze -v
    ...
    STACK_TEXT: 
    008ffbf4 77b37d01 c0000374 77b67130 008ffc38 ntdll!RtlReportCriticalFailure+0x33
    008ffc04 77b36eb5 00000002 91731eca 00c50000 ntdll!RtlpReportHeapFailure+0x21
    008ffc38 77b06e39 00000008 00c50000 00c50d88 ntdll!RtlpLogHeapFailure+0xa2
    008ffc6c 76de9898 00c50000 00000000 00c50d90 ntdll!RtlFreeHeap+0x5c
    008ffcb8 00df14c2 00c50d90 00000000 00c50d90 msvcrt!free+0x65
    008ffcd0 00df1884 00000001 00c50c80 00c52f10 program!wmain+0x42
    008ffd20 00df193f 008ffd34 759c2914 7fe6f000 program!__wmainCRTStartup+0x164
    008ffd28 759c2914 7fe6f000 008ffd74 77ab7fb6 program!wmainCRTStartup+0xf
    008ffd34 77ab7fb6 7fe6f000 91731f86 00000000 KERNEL32!BaseThreadInitThunk+0xe
    008ffd74 77ab7f62 ffffffff 77b2db43 00000000 ntdll!__RtlUserThreadStart+0x4a
    008ffd84 00000000 00df1930 7fe6f000 00000000 ntdll!_RtlUserThreadStart+0x1c

    FOLLOWUP_IP:
    program!wmain+42 [s:\program\program.cpp @ 423]
    00df14c2 83c404          add     esp,4

    FAULTING_SOURCE_CODE: 
       419:     UNREFERENCED_PARAMETER(argv);
       420:
       421:     p = malloc(1);
       422:     free(p);
    >  423:     free(p);
       424:
       425:

    So the double free in this case is obvious, but normally if you hit one it wasn't.

    0:000> .frame 7
    07 008ffcd0 00df1884 program!wmain+0x42 [s:\program\program.cpp @ 423]
    0:000> dv
               argc = 1
               argv = 0x00c50c80
                 hr = 0x00000000
                  p = 0x00c50d90
    0:000> !heap -x 0x00c50d90
    Entry     User      Heap      Segment       Size  PrevSize  Unused    Flags
    -----------------------------------------------------------------------------
    00c50d88  00c50d90  00c50000  00c50000       4c0       110         0  free fill

    0:000> !heap -a 00c50000
    Index   Address  Name      Debugging options enabled
      3:   00c50000
        Segment at 00c50000 to 00c5f000 (00004000 bytes committed)
        Flags:                40001062
        ForceFlags:           40000060
        Granularity:          8 bytes
        Segment Reserve:      00100000
        Segment Commit:       00002000
        DeCommit Block Thres: 00000200
        DeCommit Total Thres: 00002000
        Total Free Size:      000002a7
        Max. Allocation Size: 7ffdefff
        Lock Variable at:     00c50248
        Next TagIndex:        0000
        Maximum TagIndex:     0000
        Tag Entries:          00000000
        PsuedoTag Entries:    00000000
        Virtual Alloc List:   00c5009c
        Uncommitted ranges:   00c5008c
                00c54000: 0000b000  (45056 bytes)
        FreeList[ 00 ] at 00c500c0: 00c52388 . 00c50d90 
            00c50d88: 00110 . 004c0 [104] - free
            00c53af0: 00be8 . 004f0 [104] - free
            00c52380: 00818 . 00b88 [104] - free

    Segment00 at 00c50000:
            Flags:           00000000
            Base:            00c50000
            First Entry:     00c50498
            Last Entry:      00c5f000
            Total Pages:     0000000f
            Total UnCommit:  0000000b
            Largest UnCommit:00000000
            UnCommitted Ranges: (1)

    Heap entries for Segment00 in Heap 00c50000
             address: psize . size  flags   state (requested size)
            00c50000: 00000 . 00498 [101] - busy (497)
            00c50498: 00498 . 00118 [107] - busy (117), tail fill Internal
            00c505b0: 00118 . 00230 [107] - busy (214), tail fill
            00c507e0: 00230 . 00498 [107] - busy (480), tail fill
            00c50c78: 00498 . 00110 [107] - busy (f8), tail fill
            00c50d88: 00110 . 004c0 [104] free fill        00c51248: 004c0 . 00238 [107] - busy (220), tail fill
            00c51480: 00238 . 00650 [107] - busy (632), tail fill
            00c51ad0: 00650 . 00098 [107] - busy (80), tail fill
            00c51b68: 00098 . 00818 [107] - busy (800), tail fill
            00c52380: 00818 . 00b88 [104] free fill
            00c52f08: 00b88 . 00be8 [107] - busy (bd0), tail fill
            00c53af0: 00be8 . 004f0 [104] free fill
            00c53fe0: 004f0 . 00020 [111] - busy (1d)
            00c54000:      0000b000      - uncommitted bytes.

    You can see the memory is already freed. 
  2. If your heap call is on the stack at the time, you can find this value by walking the stack to and dumping variables.  This can be helpful where there are a lot of allocations and -x will take a long time.  The heap handle is the first parameter passed in to the ntdll RtlHeap*/Heap* functions.

    example:
    free((void*)1234);This will cause an access violation.
    0:000> kn
     # ChildEBP RetAddr 
    00 0102fa6c 77b37d01 ntdll!RtlReportCriticalFailure+0x33 [d:\6588\minkernel\ntos\rtl\rtlutil.c @ 161]
    01 0102fa7c 77b36eb5 ntdll!RtlpReportHeapFailure+0x21 [d:\6588\minkernel\ntos\rtl\heaplog.c @ 161]
    02 0102fab0 77b06e4c ntdll!RtlpLogHeapFailure+0xa2 [d:\6588\minkernel\ntos\rtl\heaplog.c @ 672]
    03 (Inline) -------- ntdll!RtlpProbeUserBufferUnsafe+0x8066d [d:\6588\minkernel\ntos\rtl\heappriv.h @ 2720]
    04 (Inline) -------- ntdll!RtlpProbeUserBuffer+0x80677 [d:\6588\minkernel\ntos\rtl\heappriv.h @ 2754]
    05 0102fae4 76de9898 ntdll!RtlFreeHeap+0x6c [d:\6588\minkernel\ntos\rtl\heap.c @ 1910]
    06 0102fb30 00ce14a1 msvcrt!free+0x65 [d:\6588\minkernel\crts\crtw32\heap\free.c @ 183]
    07 0102fb44 00ce1864 program!wmain+0x21 [s:\program\program.cpp @ 420]
    08 0102fb94 00ce191f program!__wmainCRTStartup+0x164 [s:\dep\minkernel\crts\crtw32\dllstuff\crtexe.c @ 692]
    09 0102fb9c 759c2914 program!wmainCRTStartup+0xf [s:\dep\minkernel\crts\crtw32\dllstuff\crtexe.c @ 510]
    0a 0102fba8 77ab7fb6 KERNEL32!BaseThreadInitThunk+0xe [d:\6588\base\win32\client\thread.c @ 65]
    0b 0102fbe8 77ab7f62 ntdll!__RtlUserThreadStart+0x4a [d:\6588\minkernel\ntdll\rtlstrt.c @ 1018]
    0c 0102fbf8 00000000 ntdll!_RtlUserThreadStart+0x1c [d:\6588\minkernel\ntdll\rtlstrt.c @ 936]
    0:000> .frame 5
    05 0102fae4 76de9898 ntdll!RtlFreeHeap+0x6c [d:\6588\minkernel\ntos\rtl\heap.c @ 1910]
    0:000> dv
         HeapHandle = 0x010d0000          Flags = 0
        BaseAddress = 0x000004d2
          BusyBlock = 0x00000000
        Interceptor =
    0:000> !heap -a 0x010d0000Index   Address  Name      Debugging options enabled
      3:   010d0000
        Segment at 010d0000 to 010df000 (00004000 bytes committed)
        Flags:                40001062
        ForceFlags:           40000060
        Granularity:          8 bytes
        Segment Reserve:      00100000
        Segment Commit:       00002000
        DeCommit Block Thres: 00000200
        DeCommit Total Thres: 00002000
        Total Free Size:      000002a7
        Max. Allocation Size: 7ffdefff
        Lock Variable at:     010d0248
        Next TagIndex:        0000
        Maximum TagIndex:     0000
        Tag Entries:          00000000
        PsuedoTag Entries:    00000000
        Virtual Alloc List:   010d009c
        Uncommitted ranges:   010d008c
                010d4000: 0000b000  (45056 bytes)
        FreeList[ 00 ] at 010d00c0: 010d2388 . 010d0d90 
            010d0d88: 00110 . 004c0 [104] - free
            010d3af0: 00be8 . 004f0 [104] - free
            010d2380: 00818 . 00b88 [104] - free
    Segment00 at 010d0000:
            Flags:           00000000
            Base:            010d0000
            First Entry:     010d0498
            Last Entry:      010df000
            Total Pages:     0000000f
            Total UnCommit:  0000000b
            Largest UnCommit:00000000
            UnCommitted Ranges: (1)
    Heap entries for Segment00 in Heap 010d0000
             address: psize . size  flags   state (requested size)
            010d0000: 00000 . 00498 [101] - busy (497)
            010d0498: 00498 . 00118 [107] - busy (117), tail fill Internal
            010d05b0: 00118 . 00230 [107] - busy (214), tail fill
            010d07e0: 00230 . 00498 [107] - busy (480), tail fill
            010d0c78: 00498 . 00110 [107] - busy (f8), tail fill
            010d0d88: 00110 . 004c0 [104] free fill
            010d1248: 004c0 . 00238 [107] - busy (220), tail fill
            010d1480: 00238 . 00650 [107] - busy (632), tail fill
            010d1ad0: 00650 . 00098 [107] - busy (80), tail fill
            010d1b68: 00098 . 00818 [107] - busy (800), tail fill
            010d2380: 00818 . 00b88 [104] free fill
            010d2f08: 00b88 . 00be8 [107] - busy (bd0), tail fill
            010d3af0: 00be8 . 004f0 [104] free fill
            010d3fe0: 004f0 . 00020 [111] - busy (1d)
            010d4000:      0000b000      - uncommitted bytes.