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.
- !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. - 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] - freeSegment00 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.
nice write up! Thanks1
ReplyDelete