基于WinDbg的内存泄漏分析
生活随笔
收集整理的這篇文章主要介紹了
基于WinDbg的内存泄漏分析
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
在前面C++中基于Crt的內存泄漏檢測一文中提到的方法已經可以解決我們的大部分內存泄露問題了,但是該方法是有前提的,那就是一定要有源代碼,而且還只能是Debug版本調試模式下。實際上很多時候我們的程序會用到第三方沒有源代碼的模塊,有些情況下我們甚至懷疑系統模塊有內存泄露,但是有沒有證據,我們該怎么辦? 這時我們就要依靠無所不能的WinDbg了。
WinDbg的!heap命令非常強大,結合AppVerifier可以對堆(heap)內存進行詳細的跟蹤和分析, 我們接下來對下面的代碼進行內存泄漏的分析:
//?MemLeakTest.cpp?:?Defines?the?entry?point?for?the?console?application.
//
#include?"stdafx.h"
#include?<Windows.h>
#include?<stdio.h>
int?_tmain(int?argc,?_TCHAR*?argv[])
{
????char*?p1?=?new?char;
????printf("%p\n",?p1);
????char*?pLargeMem?=?new?char[40000];
????for(int?i=0;?i<1000;?++i)
????{
????????char*?p?=?new?char[20];
????}
????
????system("pause");
????return?0;
}
首先下載安裝AppVerifier, 可到 這里 下載,?把我們需要測試的程序添加到AppVerifier的檢測列表中, 然后保存。
注: 我們這里用AppVerifier主要是為了打開頁堆(page heap)調試功能,你也可以用系統工具 gflags.exe 來做同樣的事。?
雙擊運行我們要調試的MemLeakTest.exe, 效果如下:
然后將WinDbg Attach上去, 輸入命令? !heap -p -a 0x02FC1FF8,結果如下:
0:001>?!heap?-p?-a?0x02FC1FF8
????address?02fc1ff8?found?in
????_DPH_HEAP_ROOT?@?2f01000
????in?busy?allocation?(??DPH_HEAP_BLOCK:?????????UserAddr?????????UserSize?-?????????VirtAddr?????????VirtSize)
?????????????????????????????????2f02548:??????????2fc1ff8????????????????1?-??????????2fc1000?????????????2000
????5a8c8e89?verifier!AVrfDebugPageHeapAllocate+0x00000229
????77485c4e?ntdll!RtlDebugAllocateHeap+0x00000030
????77447e5e?ntdll!RtlpAllocateHeap+0x000000c4
????774134df?ntdll!RtlAllocateHeap+0x0000023a
????5b06a65d?vrfcore!VfCoreRtlAllocateHeap+0x00000016
????5a92f9ea?vfbasics!AVrfpRtlAllocateHeap+0x000000e2
????72893db8?MSVCR90!malloc+0x00000079
????72893eb8?MSVCR90!operator?new+0x0000001f
????012c1008?MemLeakTest!wmain+0x00000008?[f:\test\memleaktest\memleaktest\memleaktest.cpp?@?11]
????77331114?kernel32!BaseThreadInitThunk+0x0000000e
????7741b429?ntdll!__RtlUserThreadStart+0x00000070
????7741b3fc?ntdll!_RtlUserThreadStart+0x0000001b
怎么樣, 神奇吧?我們當分配該地址內存時的堆棧(stack)被完整地打印了出來。
當然有人很快會說:這是你知道內存地址的情況, 很多情況下我們是不知道該地址的,該如何分析?
對于這種情況, 我們首先需要明確一些概念, 我們new出來的內存是分配在堆上, 那一個進程里究竟有多少個堆, 每個模塊都有自己單獨的堆嗎?實際上一個進程可以有任意多個堆,我們可以通過CreateHeap創建自己單獨的堆, 然后通過HeapAlloc分配內存。 我們new出來的內存是crt(C運行庫)分配的, 那就涉及到crt究竟有多少個堆了? crt有多少個堆由你編譯每個模塊(Dll/Exe)時的編譯選項決定, 如果你運行庫選項用的是/MD, 那就和其他模塊共享一個堆; 如果用/MT, 那就是自己單獨的堆。大部分情況下我們會用/MD,這樣我們在一個模塊里new內存, 另一個模塊里delete不會有問題, 因為大家共享一個堆。
明確這些概念之后, 我們看看我們的測試程序有多少個堆, 輸入 !heap -p
0:001>?!heap?-p
????Active?GlobalFlag?bits:
????????vrf?-?Enable?application?verifier
????????hpa?-?Place?heap?allocations?at?ends?of?pages
????StackTraceDataBase?@?00160000?of?size?01000000?with?00000034?traces
????PageHeap?enabled?with?options:
????????ENABLE_PAGE_HEAP
????????COLLECT_STACK_TRACES
????active?heaps:
????+?1160000
????????ENABLE_PAGE_HEAP?COLLECT_STACK_TRACES?
??????NormalHeap?-?1300000
??????????HEAP_GROWABLE?
????+?1400000
????????ENABLE_PAGE_HEAP?COLLECT_STACK_TRACES?
??????NormalHeap?-?16b0000
??????????HEAP_GROWABLE?HEAP_CLASS_1?
????+?2360000
????????ENABLE_PAGE_HEAP?COLLECT_STACK_TRACES?
??????NormalHeap?-?1280000
??????????HEAP_GROWABLE?HEAP_CLASS_1?
????+?2f00000
????????ENABLE_PAGE_HEAP?COLLECT_STACK_TRACES?
??????NormalHeap?-?31d0000
??????????HEAP_GROWABLE?HEAP_CLASS_1? 可以看到我們的測試程序一共有4 個堆。
接下來我們的問題就是確定哪個是我們的crt堆, 也就是我們需要分析每個堆創建時的堆棧(stack)情況.
我們接下來分析最后一個堆, handle是 2f00000, 輸入 !heap -p -h 02f00000 分析該堆的內存分配情況
0:001>?!heap?-p?-h?02f00000
????_DPH_HEAP_ROOT?@?2f01000
????Freed?and?decommitted?blocks
??????DPH_HEAP_BLOCK?:?VirtAddr?VirtSize
????????02f01f04?:?02f09000?00002000
????????02f02e38?:?02f69000?00002000
????????037e2548?:?03892000?00002000
????????037e2514?:?03894000?00002000
????Busy?allocations
??????DPH_HEAP_BLOCK?:?UserAddr??UserSize?-?VirtAddr?VirtSize
????????02f01f6c?:?02f05de8?00000214?-?02f05000?00002000
????????02f01f38?:?02f07800?00000800?-?02f07000?00002000
????????02f01ed0?:?02f0bde0?00000220?-?02f0b000?00002000
????????02f01e9c?:?02f0df50?000000ac?-?02f0d000?00002000
????????02f01e68?:?02f0ffe0?0000001f?-?02f0f000?00002000
????????02f01e34?:?02f11fd8?00000028?-?02f11000?00002000
????????02f01e00?:?02f13fe0?0000001d?-?02f13000?00002000
????????02f01dcc?:?02f15fc0?0000003a?-?02f15000?00002000
? ? ? ? ....
可以看到該堆? _DPH_HEAP_ROOT 結構的地址是? 2f01000,通過dt命令打印該結構地址
0:001>?dt?ntdll!_DPH_HEAP_ROOT?CreateStackTrace?2f01000
???+0x0b8?CreateStackTrace?:?0x0017cbe4?_RTL_TRACE_BLOCK
可以看到StackTrace的地址是? 0x0017cbe4, 通過dds命令打印該地址內的符號
0:001>?dds?0x0017cbe4?
0017cbe4??00178714
0017cbe8??00007001
0017cbec??000f0000
0017cbf0??5a8c8969?verifier!AVrfDebugPageHeapCreate+0x439
0017cbf4??7743a9e8?ntdll!RtlCreateHeap+0x41
0017cbf8??5a930109?vfbasics!AVrfpRtlCreateHeap+0x56
0017cbfc??755fdda2?KERNELBASE!HeapCreate+0x55
0017cc00??72893a4a?MSVCR90!_heap_init+0x1b
0017cc04??72852bb4?MSVCR90!__p__tzname+0x2a
0017cc08??72852d5e?MSVCR90!_CRTDLL_INIT+0x1e
0017cc0c??5a8dc66d?verifier!AVrfpStandardDllEntryPointRoutine+0x99
0017cc10??5b069164?vrfcore!VfCoreStandardDllEntryPointRoutine+0x121
0017cc14??5a92689c?vfbasics!AVrfpStandardDllEntryPointRoutine+0x9f
0017cc18??7741af58?ntdll!LdrpCallInitRoutine+0x14
0017cc1c??7741fd6f?ntdll!LdrpRunInitializeRoutines+0x26f
0017cc20??774290c6?ntdll!LdrpInitializeProcess+0x137e
0017cc24??77428fc8?ntdll!_LdrpInitialize+0x78
0017cc28??7741b2f9?ntdll!LdrInitializeThunk+0x10
0017cc2c??00000000
0017cc30??00009001
現在我們可以看到該堆被Create時的完整堆棧了, 通過堆棧,我們可以看到該堆正是由crt創建的, 也就是說我們new的內存都分配在該堆內。
如果你覺得上面跟蹤堆創建的過程太復雜,可以先忽略, 下面我們分析堆狀態, 輸入 !heap -stat -h 0,它會分析所有堆的當前使用狀態, 我們著重關注我們的crt堆 02f00000:
Allocations?statistics?for
?heap?@?02f00000
group-by:?TOTSIZE?max-display:?20
????size?????#blocks?????total?????(?%)?(percent?of?total?busy?bytes)
????9c40?1?-?9c40??(52.66)
????14?3ea?-?4e48??(26.38)
????1000?1?-?1000??(5.39)
????800?2?-?1000??(5.39)
????490?1?-?490??(1.54)
????248?1?-?248??(0.77)
????220?1?-?220??(0.72)
????214?1?-?214??(0.70)
????ac?2?-?158??(0.45)
????82?2?-?104??(0.34)
????6a?2?-?d4??(0.28)
????50?2?-?a0??(0.21)
????28?4?-?a0??(0.21)
????98?1?-?98??(0.20)
????94?1?-?94??(0.19)
????8a?1?-?8a??(0.18)
????2e?3?-?8a??(0.18)
????41?2?-?82??(0.17)
????80?1?-?80??(0.17)
????7c?1?-?7c??(0.16)
我們可以看到排在第一位的是大小為0x 9c40 (0n40000)的內存,分配了1次, 第二位的是大小為 0x 14 (0n20) ?的內存,分配了 3ea (0n1002)次.
?回頭再看我們的測試程序,怎么樣? 是不是感覺很熟悉了。
輸入 !heap -flt s 0x9c40, 讓WinDbg列出所有大小為 0x9c40的內存:
0:001>?!heap?-flt?s?0x9c40
????_DPH_HEAP_ROOT?@?1161000
????Freed?and?decommitted?blocks
??????DPH_HEAP_BLOCK?:?VirtAddr?VirtSize
????Busy?allocations
??????DPH_HEAP_BLOCK?:?UserAddr??UserSize?-?VirtAddr?VirtSize
????_HEAP?@?1300000
????_DPH_HEAP_ROOT?@?1401000
????Freed?and?decommitted?blocks
??????DPH_HEAP_BLOCK?:?VirtAddr?VirtSize
????Busy?allocations
??????DPH_HEAP_BLOCK?:?UserAddr??UserSize?-?VirtAddr?VirtSize
????_HEAP?@?16b0000
????_DPH_HEAP_ROOT?@?2361000
????Freed?and?decommitted?blocks
??????DPH_HEAP_BLOCK?:?VirtAddr?VirtSize
????Busy?allocations
??????DPH_HEAP_BLOCK?:?UserAddr??UserSize?-?VirtAddr?VirtSize
????_HEAP?@?1280000
????_DPH_HEAP_ROOT?@?2f01000
????Freed?and?decommitted?blocks
??????DPH_HEAP_BLOCK?:?VirtAddr?VirtSize
????Busy?allocations
??????DPH_HEAP_BLOCK?:?UserAddr??UserSize?-?VirtAddr?VirtSize
????????02f024e0?:?02fc63c0?00009c40?-?02fc6000?0000b000
????_HEAP?@?31d0000
可以看到, WinDbg幫我們找到了一個符合要求的分配, 它的UserAddr是 02fc63c0, 該地址實際上就是代碼 char* pLargeMem = new char[40000] 分配的地址, 按照開頭的方法, 輸入 !heap -p -a 02fc63c0?
0:001>?!heap?-p?-a?02fc63c0
????address?02fc63c0?found?in
????_DPH_HEAP_ROOT?@?2f01000
????in?busy?allocation?(??DPH_HEAP_BLOCK:?????????UserAddr?????????UserSize?-?????????VirtAddr?????????VirtSize)
?????????????????????????????????2f024e0:??????????2fc63c0?????????????9c40?-??????????2fc6000?????????????b000
????5a8c8e89?verifier!AVrfDebugPageHeapAllocate+0x00000229
????77485c4e?ntdll!RtlDebugAllocateHeap+0x00000030
????77447e5e?ntdll!RtlpAllocateHeap+0x000000c4
????774134df?ntdll!RtlAllocateHeap+0x0000023a
????5b06a65d?vrfcore!VfCoreRtlAllocateHeap+0x00000016
????5a92f9ea?vfbasics!AVrfpRtlAllocateHeap+0x000000e2
????72893db8?MSVCR90!malloc+0x00000079
????72893eb8?MSVCR90!operator?new+0x0000001f
????012c101e?MemLeakTest!wmain+0x0000001e?[f:\test\memleaktest\memleaktest\memleaktest.cpp?@?13]
????77331114?kernel32!BaseThreadInitThunk+0x0000000e
????7741b429?ntdll!__RtlUserThreadStart+0x00000070
????7741b3fc?ntdll!_RtlUserThreadStart+0x0000001b
可以看到該堆棧就是我們 new char[40000]的堆棧, 用同樣的方法, 我們可以分析出上面代碼for循環中的1000次內存泄漏。
最后, 總結一下, 通過WinDbg結合AppVerifier, 我們可以詳細的跟蹤堆中new出來的每一塊內存。 很多時候在沒有源代碼的Release版本中,在程序運行一段時間后,如果我們發現有大塊內存或是大量同樣大小的小內存一直沒有釋放, ?我們就可以用上面的方法進行分析。有些情況下,我們甚至可以將 ?_CrtDumpMemoryLeaks()和WinDbg的!heap -p -a [address]命令結合起來使用, 由前者打印泄漏地址,后者分析調用堆棧,以便 快速的定位問題。
WinDbg的!heap命令非常強大,結合AppVerifier可以對堆(heap)內存進行詳細的跟蹤和分析, 我們接下來對下面的代碼進行內存泄漏的分析:
//?MemLeakTest.cpp?:?Defines?the?entry?point?for?the?console?application.
//
#include?"stdafx.h"
#include?<Windows.h>
#include?<stdio.h>
int?_tmain(int?argc,?_TCHAR*?argv[])
{
????char*?p1?=?new?char;
????printf("%p\n",?p1);
????char*?pLargeMem?=?new?char[40000];
????for(int?i=0;?i<1000;?++i)
????{
????????char*?p?=?new?char[20];
????}
????
????system("pause");
????return?0;
}
首先下載安裝AppVerifier, 可到 這里 下載,?把我們需要測試的程序添加到AppVerifier的檢測列表中, 然后保存。
注: 我們這里用AppVerifier主要是為了打開頁堆(page heap)調試功能,你也可以用系統工具 gflags.exe 來做同樣的事。?
雙擊運行我們要調試的MemLeakTest.exe, 效果如下:
然后將WinDbg Attach上去, 輸入命令? !heap -p -a 0x02FC1FF8,結果如下:
0:001>?!heap?-p?-a?0x02FC1FF8
????address?02fc1ff8?found?in
????_DPH_HEAP_ROOT?@?2f01000
????in?busy?allocation?(??DPH_HEAP_BLOCK:?????????UserAddr?????????UserSize?-?????????VirtAddr?????????VirtSize)
?????????????????????????????????2f02548:??????????2fc1ff8????????????????1?-??????????2fc1000?????????????2000
????5a8c8e89?verifier!AVrfDebugPageHeapAllocate+0x00000229
????77485c4e?ntdll!RtlDebugAllocateHeap+0x00000030
????77447e5e?ntdll!RtlpAllocateHeap+0x000000c4
????774134df?ntdll!RtlAllocateHeap+0x0000023a
????5b06a65d?vrfcore!VfCoreRtlAllocateHeap+0x00000016
????5a92f9ea?vfbasics!AVrfpRtlAllocateHeap+0x000000e2
????72893db8?MSVCR90!malloc+0x00000079
????72893eb8?MSVCR90!operator?new+0x0000001f
????012c1008?MemLeakTest!wmain+0x00000008?[f:\test\memleaktest\memleaktest\memleaktest.cpp?@?11]
????77331114?kernel32!BaseThreadInitThunk+0x0000000e
????7741b429?ntdll!__RtlUserThreadStart+0x00000070
????7741b3fc?ntdll!_RtlUserThreadStart+0x0000001b
怎么樣, 神奇吧?我們當分配該地址內存時的堆棧(stack)被完整地打印了出來。
當然有人很快會說:這是你知道內存地址的情況, 很多情況下我們是不知道該地址的,該如何分析?
對于這種情況, 我們首先需要明確一些概念, 我們new出來的內存是分配在堆上, 那一個進程里究竟有多少個堆, 每個模塊都有自己單獨的堆嗎?實際上一個進程可以有任意多個堆,我們可以通過CreateHeap創建自己單獨的堆, 然后通過HeapAlloc分配內存。 我們new出來的內存是crt(C運行庫)分配的, 那就涉及到crt究竟有多少個堆了? crt有多少個堆由你編譯每個模塊(Dll/Exe)時的編譯選項決定, 如果你運行庫選項用的是/MD, 那就和其他模塊共享一個堆; 如果用/MT, 那就是自己單獨的堆。大部分情況下我們會用/MD,這樣我們在一個模塊里new內存, 另一個模塊里delete不會有問題, 因為大家共享一個堆。
明確這些概念之后, 我們看看我們的測試程序有多少個堆, 輸入 !heap -p
0:001>?!heap?-p
????Active?GlobalFlag?bits:
????????vrf?-?Enable?application?verifier
????????hpa?-?Place?heap?allocations?at?ends?of?pages
????StackTraceDataBase?@?00160000?of?size?01000000?with?00000034?traces
????PageHeap?enabled?with?options:
????????ENABLE_PAGE_HEAP
????????COLLECT_STACK_TRACES
????active?heaps:
????+?1160000
????????ENABLE_PAGE_HEAP?COLLECT_STACK_TRACES?
??????NormalHeap?-?1300000
??????????HEAP_GROWABLE?
????+?1400000
????????ENABLE_PAGE_HEAP?COLLECT_STACK_TRACES?
??????NormalHeap?-?16b0000
??????????HEAP_GROWABLE?HEAP_CLASS_1?
????+?2360000
????????ENABLE_PAGE_HEAP?COLLECT_STACK_TRACES?
??????NormalHeap?-?1280000
??????????HEAP_GROWABLE?HEAP_CLASS_1?
????+?2f00000
????????ENABLE_PAGE_HEAP?COLLECT_STACK_TRACES?
??????NormalHeap?-?31d0000
??????????HEAP_GROWABLE?HEAP_CLASS_1? 可以看到我們的測試程序一共有4 個堆。
接下來我們的問題就是確定哪個是我們的crt堆, 也就是我們需要分析每個堆創建時的堆棧(stack)情況.
我們接下來分析最后一個堆, handle是 2f00000, 輸入 !heap -p -h 02f00000 分析該堆的內存分配情況
0:001>?!heap?-p?-h?02f00000
????_DPH_HEAP_ROOT?@?2f01000
????Freed?and?decommitted?blocks
??????DPH_HEAP_BLOCK?:?VirtAddr?VirtSize
????????02f01f04?:?02f09000?00002000
????????02f02e38?:?02f69000?00002000
????????037e2548?:?03892000?00002000
????????037e2514?:?03894000?00002000
????Busy?allocations
??????DPH_HEAP_BLOCK?:?UserAddr??UserSize?-?VirtAddr?VirtSize
????????02f01f6c?:?02f05de8?00000214?-?02f05000?00002000
????????02f01f38?:?02f07800?00000800?-?02f07000?00002000
????????02f01ed0?:?02f0bde0?00000220?-?02f0b000?00002000
????????02f01e9c?:?02f0df50?000000ac?-?02f0d000?00002000
????????02f01e68?:?02f0ffe0?0000001f?-?02f0f000?00002000
????????02f01e34?:?02f11fd8?00000028?-?02f11000?00002000
????????02f01e00?:?02f13fe0?0000001d?-?02f13000?00002000
????????02f01dcc?:?02f15fc0?0000003a?-?02f15000?00002000
? ? ? ? ....
可以看到該堆? _DPH_HEAP_ROOT 結構的地址是? 2f01000,通過dt命令打印該結構地址
0:001>?dt?ntdll!_DPH_HEAP_ROOT?CreateStackTrace?2f01000
???+0x0b8?CreateStackTrace?:?0x0017cbe4?_RTL_TRACE_BLOCK
可以看到StackTrace的地址是? 0x0017cbe4, 通過dds命令打印該地址內的符號
0:001>?dds?0x0017cbe4?
0017cbe4??00178714
0017cbe8??00007001
0017cbec??000f0000
0017cbf0??5a8c8969?verifier!AVrfDebugPageHeapCreate+0x439
0017cbf4??7743a9e8?ntdll!RtlCreateHeap+0x41
0017cbf8??5a930109?vfbasics!AVrfpRtlCreateHeap+0x56
0017cbfc??755fdda2?KERNELBASE!HeapCreate+0x55
0017cc00??72893a4a?MSVCR90!_heap_init+0x1b
0017cc04??72852bb4?MSVCR90!__p__tzname+0x2a
0017cc08??72852d5e?MSVCR90!_CRTDLL_INIT+0x1e
0017cc0c??5a8dc66d?verifier!AVrfpStandardDllEntryPointRoutine+0x99
0017cc10??5b069164?vrfcore!VfCoreStandardDllEntryPointRoutine+0x121
0017cc14??5a92689c?vfbasics!AVrfpStandardDllEntryPointRoutine+0x9f
0017cc18??7741af58?ntdll!LdrpCallInitRoutine+0x14
0017cc1c??7741fd6f?ntdll!LdrpRunInitializeRoutines+0x26f
0017cc20??774290c6?ntdll!LdrpInitializeProcess+0x137e
0017cc24??77428fc8?ntdll!_LdrpInitialize+0x78
0017cc28??7741b2f9?ntdll!LdrInitializeThunk+0x10
0017cc2c??00000000
0017cc30??00009001
現在我們可以看到該堆被Create時的完整堆棧了, 通過堆棧,我們可以看到該堆正是由crt創建的, 也就是說我們new的內存都分配在該堆內。
如果你覺得上面跟蹤堆創建的過程太復雜,可以先忽略, 下面我們分析堆狀態, 輸入 !heap -stat -h 0,它會分析所有堆的當前使用狀態, 我們著重關注我們的crt堆 02f00000:
Allocations?statistics?for
?heap?@?02f00000
group-by:?TOTSIZE?max-display:?20
????size?????#blocks?????total?????(?%)?(percent?of?total?busy?bytes)
????9c40?1?-?9c40??(52.66)
????14?3ea?-?4e48??(26.38)
????1000?1?-?1000??(5.39)
????800?2?-?1000??(5.39)
????490?1?-?490??(1.54)
????248?1?-?248??(0.77)
????220?1?-?220??(0.72)
????214?1?-?214??(0.70)
????ac?2?-?158??(0.45)
????82?2?-?104??(0.34)
????6a?2?-?d4??(0.28)
????50?2?-?a0??(0.21)
????28?4?-?a0??(0.21)
????98?1?-?98??(0.20)
????94?1?-?94??(0.19)
????8a?1?-?8a??(0.18)
????2e?3?-?8a??(0.18)
????41?2?-?82??(0.17)
????80?1?-?80??(0.17)
????7c?1?-?7c??(0.16)
我們可以看到排在第一位的是大小為0x 9c40 (0n40000)的內存,分配了1次, 第二位的是大小為 0x 14 (0n20) ?的內存,分配了 3ea (0n1002)次.
?回頭再看我們的測試程序,怎么樣? 是不是感覺很熟悉了。
輸入 !heap -flt s 0x9c40, 讓WinDbg列出所有大小為 0x9c40的內存:
0:001>?!heap?-flt?s?0x9c40
????_DPH_HEAP_ROOT?@?1161000
????Freed?and?decommitted?blocks
??????DPH_HEAP_BLOCK?:?VirtAddr?VirtSize
????Busy?allocations
??????DPH_HEAP_BLOCK?:?UserAddr??UserSize?-?VirtAddr?VirtSize
????_HEAP?@?1300000
????_DPH_HEAP_ROOT?@?1401000
????Freed?and?decommitted?blocks
??????DPH_HEAP_BLOCK?:?VirtAddr?VirtSize
????Busy?allocations
??????DPH_HEAP_BLOCK?:?UserAddr??UserSize?-?VirtAddr?VirtSize
????_HEAP?@?16b0000
????_DPH_HEAP_ROOT?@?2361000
????Freed?and?decommitted?blocks
??????DPH_HEAP_BLOCK?:?VirtAddr?VirtSize
????Busy?allocations
??????DPH_HEAP_BLOCK?:?UserAddr??UserSize?-?VirtAddr?VirtSize
????_HEAP?@?1280000
????_DPH_HEAP_ROOT?@?2f01000
????Freed?and?decommitted?blocks
??????DPH_HEAP_BLOCK?:?VirtAddr?VirtSize
????Busy?allocations
??????DPH_HEAP_BLOCK?:?UserAddr??UserSize?-?VirtAddr?VirtSize
????????02f024e0?:?02fc63c0?00009c40?-?02fc6000?0000b000
????_HEAP?@?31d0000
可以看到, WinDbg幫我們找到了一個符合要求的分配, 它的UserAddr是 02fc63c0, 該地址實際上就是代碼 char* pLargeMem = new char[40000] 分配的地址, 按照開頭的方法, 輸入 !heap -p -a 02fc63c0?
0:001>?!heap?-p?-a?02fc63c0
????address?02fc63c0?found?in
????_DPH_HEAP_ROOT?@?2f01000
????in?busy?allocation?(??DPH_HEAP_BLOCK:?????????UserAddr?????????UserSize?-?????????VirtAddr?????????VirtSize)
?????????????????????????????????2f024e0:??????????2fc63c0?????????????9c40?-??????????2fc6000?????????????b000
????5a8c8e89?verifier!AVrfDebugPageHeapAllocate+0x00000229
????77485c4e?ntdll!RtlDebugAllocateHeap+0x00000030
????77447e5e?ntdll!RtlpAllocateHeap+0x000000c4
????774134df?ntdll!RtlAllocateHeap+0x0000023a
????5b06a65d?vrfcore!VfCoreRtlAllocateHeap+0x00000016
????5a92f9ea?vfbasics!AVrfpRtlAllocateHeap+0x000000e2
????72893db8?MSVCR90!malloc+0x00000079
????72893eb8?MSVCR90!operator?new+0x0000001f
????012c101e?MemLeakTest!wmain+0x0000001e?[f:\test\memleaktest\memleaktest\memleaktest.cpp?@?13]
????77331114?kernel32!BaseThreadInitThunk+0x0000000e
????7741b429?ntdll!__RtlUserThreadStart+0x00000070
????7741b3fc?ntdll!_RtlUserThreadStart+0x0000001b
可以看到該堆棧就是我們 new char[40000]的堆棧, 用同樣的方法, 我們可以分析出上面代碼for循環中的1000次內存泄漏。
最后, 總結一下, 通過WinDbg結合AppVerifier, 我們可以詳細的跟蹤堆中new出來的每一塊內存。 很多時候在沒有源代碼的Release版本中,在程序運行一段時間后,如果我們發現有大塊內存或是大量同樣大小的小內存一直沒有釋放, ?我們就可以用上面的方法進行分析。有些情況下,我們甚至可以將 ?_CrtDumpMemoryLeaks()和WinDbg的!heap -p -a [address]命令結合起來使用, 由前者打印泄漏地址,后者分析調用堆棧,以便 快速的定位問題。
總結
以上是生活随笔為你收集整理的基于WinDbg的内存泄漏分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++中基于Crt的内存泄漏检测
- 下一篇: C++ 常见崩溃问题分析