當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > CPP開發(fā)者
[導(dǎo)讀]之前寫了一篇>"data-itemshowtype="11"tab="innerlink"data-linktype="2">。這種方法有一定的局限性:實(shí)踐證明,當(dāng)程序復(fù)雜,內(nèi)存頻繁的申請(qǐng)釋放,通過(guò)UMDH對(duì)比的文件將會(huì)非常的大,并且很難直接看出內(nèi)存泄露所在。UMDH在收集信息的...

之前寫了一篇>" data-itemshowtype="11" tab="innerlink" data-linktype="2"><<Windows程序內(nèi)存泄漏(Memory Leak)分析之UMDH>>。這種方法有一定的局限性:

  1. 實(shí)踐證明,當(dāng)程序復(fù)雜,內(nèi)存頻繁的申請(qǐng)釋放,通過(guò)UMDH對(duì)比的文件將會(huì)非常的大,并且很難直接看出內(nèi)存泄露所在。

  2. UMDH在收集信息的需要符號(hào)文件,不太適合于在客戶的機(jī)器上進(jìn)行操作。

調(diào)試方法很難一通百用,因?yàn)椴煌墓ぞ叨加凶约旱木窒扌裕灿羞m合自己的分析場(chǎng)景,這個(gè)取決于碰到的問(wèn)題。那么本文來(lái)介紹一種,使用Windbg分析內(nèi)存泄露的方法。

樣例代碼

這個(gè)樣例代碼中循環(huán)調(diào)用一個(gè)Memory Leak的函數(shù):

#include?#include?#include?class?TestClass{public:?char?m_str[100];};void?MemoryLeakObj(){?TestClass?*?pObj?=?new?TestClass;?strcpy_s(pObj->m_str,?100,?"Memory?Leak?Sample");?std::cout?<m_str?<

基礎(chǔ)知識(shí)

這個(gè)章節(jié)了解下堆的一些基本知識(shí)。一個(gè)進(jìn)程可以有若干個(gè)堆,包括CRT庫(kù)中malloc也是從堆中申請(qǐng)內(nèi)存,也可以自己通過(guò)Windows API?HeapCreate創(chuàng)建堆。在windbg中查看所有的堆, 一般主要通過(guò)查看commit的內(nèi)存來(lái)確定是否有內(nèi)存泄露。

0:008>?!heap?-s


*****************************************************************************************************
??????????????????????????????????????????????NT?HEAP?STATS?BELOW
*****************************************************************************************************
NtGlobalFlag?enables?following?debugging?aids?for?new?heaps:
????tail?checking
????free?checking
????validate?parameters
LFH?Key???????????????????:?0x3f0f03d02e6012eb
Termination?on?corruption?:?ENABLED
??????????Heap?????Flags???Reserv??Commit??Virt???Free??List???UCR??Virt??Lock??Fast?
????????????????????????????(k)?????(k)????(k)?????(k)?length??????blocks?cont.?heap?
-------------------------------------------------------------------------------------

0000026349b50000?40000062????2040???1088???2040??????2????26?????2????1??????0??????
00000263499d0000?40008060??????64??????4?????64??????2?????1?????1????0??????0??????
0000026349b30000?40001062??????60?????20?????60??????2?????2?????1????0??????0??????
000002634b440000?40001062????1080?????88???1080??????2?????4?????2????0??????0??????
-------------------------------------------------------------------------------------

Windows中,一個(gè)堆本身并不只是由一個(gè)連續(xù)的空間組成,而是可以由多個(gè)連續(xù)的空間組成,而每一個(gè)連續(xù)的空間我們稱之為Segment。我們挑選一個(gè)堆來(lái)查看他的Segment??梢钥吹竭@個(gè)堆目前由兩個(gè)Segment構(gòu)成,并且列出了每個(gè)Segment的地址范圍。

0:008> !heap 0000026349b50000
Index Address Name Debugging options enabled
1: 26349b50000
Segment at 0000026349b50000 to 0000026349c4f000 (000ff000 bytes committed)
????Segment?at?000002634bef0000?to?000002634bfef000?(00011000?bytes?committed)
可以通過(guò)heap -a 來(lái)查看各個(gè)Segment中申請(qǐng)內(nèi)存。我們申請(qǐng)的內(nèi)存的時(shí)候便是占用每一個(gè)Entry,有時(shí)候也叫做block。

0:008> !heap -a 26349b50000
Index Address Name Debugging options enabled
1: 26349b50000
Segment at 0000026349b50000 to 0000026349c4f000 (000ff000 bytes committed)
Segment at 000002634bef0000 to 000002634bfef000 (00011000 bytes committed)
Flags: 40000062
ForceFlags: 40000060
Granularity: 16 bytes
Segment Reserve: 00200000
Segment Commit: 00002000
DeCommit Block Thres: 00000100
DeCommit Total Thres: 00001000
Total Free Size: 0000009f
Max. Allocation Size: 00007ffffffdefff
Lock Variable at: 0000026349b502a0
Next TagIndex: 0000
Maximum TagIndex: 0000
Tag Entries: 00000000
PsuedoTag Entries: 00000000
Virtual Alloc List: 26349b50110
000002634ba79000: 00100000 [commited 101000, unused 1000] - busy (b)
Uncommitted ranges: 26349b500f0
2634bf01000: 000ee000 (974848 bytes)
FreeList[ 00 ] at 0000026349b50150: 000002634bf00a30 . 0000026349bd9fb0
0000026349bd9fa0: 00050 . 00020 [104] - free
0000026349bd4670: 00050 . 00020 [104] - free
0000026349bd8630: 000b0 . 00020 [104] - free
0000026349bd80c0: 00050 . 00020 [104] - free
0000026349bd60b0: 00060 . 00020 [104] - free
0000026349bd53f0: 000b0 . 00020 [104] - free
0000026349b5f4c0: 00060 . 00020 [104] - free
0000026349b5dea0: 00050 . 00020 [104] - free
0000026349b61860: 00090 . 00020 [104] - free
0000026349b57ae0: 00080 . 00020 [104] - free
0000026349b53990: 00080 . 00020 [104] - free
0000026349b6a800: 00050 . 00030 [104] - free
0000026349b629c0: 00050 . 00030 [104] - free
0000026349b5f610: 00070 . 00030 [104] - free
0000026349b60a90: 00070 . 00030 [104] - free
0000026349b62390: 00070 . 00030 [104] - free
0000026349b5f940: 000c0 . 00030 [104] - free
0000026349b668b0: 00070 . 00030 [104] - free
0000026349b65230: 00040 . 00030 [104] - free
0000026349b65ad0: 00040 . 00030 [104] - free
0000026349b57e70: 00080 . 00030 [104] - free
0000026349b57cb0: 00070 . 00030 [104] - free
0000026349b57930: 00050 . 00030 [104] - free
0000026349bd9c70: 000a0 . 00040 [104] - free
0000026349bd9ea0: 00040 . 00070 [104] - free
000002634bf00a20: 000a0 . 005a0 [104] - free

Segment00 at 49b50000:
Flags: 00000000
Base: 26349b50000
First Entry: 49b50720
Last Entry: 26349c4f000
Total Pages: 000000ff
Total UnCommit: 00000000
Largest UnCommit:00000000
UnCommitted Ranges: (1)

Heap entries for Segment00 in Heap 0000026349b50000
address: psize . size flags state (requested size)
0000026349b50000: 00000 . 00720 [101] - busy (71f)
0000026349b50720: 00720 . 00130 [107] - busy (12f), tail fill Internal
0000026349b50850: 00130 . 00130 [107] - busy (100), tail fill
.......
0000026349c4ede0: 000a0 . 000a0 [107] - busy (64), tail fill
0000026349c4ee80: 000a0 . 000a0 [107] - busy (64), tail fill
0000026349c4ef20: 000a0 . 000a0 [107] - busy (64), tail fill
0000026349c4efc0: 000a0 . 00040 [111] - busy (3d)
0000026349c4f000: 00000000 - uncommitted bytes.
Segment01 at 4bef0000:
Flags: 00000000
Base: 2634bef0000
First Entry: 4bef0070
Last Entry: 2634bfef000
Total Pages: 000000ff
Total UnCommit: 000000ee
Largest UnCommit:00000000
UnCommitted Ranges: (1)

Heap entries for Segment01 in Heap 0000026349b50000
address: psize . size flags state (requested size)
000002634bef0000: 00000 . 00070 [101] - busy (6f)
000002634bef0070: 00070 . 000a0 [107] - busy (64), tail fill
.......
000002634bf00700: 000a0 . 000a0 [107] - busy (64), tail fill
000002634bf00840: 000a0 . 000a0 [107] - busy (64), tail fill
000002634bf008e0: 000a0 . 000a0 [107] - busy (64), tail fill
000002634bf00980: 000a0 . 000a0 [107] - busy (64), tail fill
000002634bf00a20: 000a0 . 005a0 [104] free fill
000002634bf00fc0: 005a0 . 00040 [111] - busy (3d)
000002634bf01000: 000ee000 - uncommitted bytes.
但是Entry的地址并不等同于我們通過(guò)malloc返回的地址,比如通過(guò)heap -x 來(lái)查看剛剛Entry的信息,注意到Entry的地址和User(也就是我們通過(guò)malloc申請(qǐng)的內(nèi)存地址啦)不同,那是堆通過(guò)Entry開頭_HEAP_ENTRY數(shù)據(jù)結(jié)構(gòu)進(jìn)行Entry管理。

0:008> !heap -x 000002634bf00980
Entry User Heap Segment Size PrevSize Unused Flags
-------------------------------------------------------------------------------------------------------------
000002634bf00980 000002634bf00990 0000026349b50000 000002634bef0000 a0 a0 3c busy extra fill
那么假設(shè)我們知道泄漏的內(nèi)存地址了,如何知道申請(qǐng)內(nèi)存的函數(shù)調(diào)用棧呢?在進(jìn)行運(yùn)行前,使用gflag設(shè)置記錄函數(shù)調(diào)用棧信息:?"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags" -i MemoryLeakAnalysisViaWindbg.exe ust。然后調(diào)用heap -p -a ,就可以看到泄露的內(nèi)存地址對(duì)應(yīng)的函數(shù)調(diào)用棧了。那么接下來(lái)我們一起來(lái)看看是如何分析內(nèi)存泄露的。

Windbg內(nèi)存泄露分析

第一步?要做的和UMDH分析一樣,調(diào)用以下命令對(duì)MemoryLeakAnalysisViaWindbg.exe程序在申請(qǐng)堆上內(nèi)存的時(shí)候記錄其函數(shù)調(diào)用棧"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags" -i MemoryLeakAnalysisViaWindbg.exe ust。第二步?開始運(yùn)行程序一段時(shí)間,查看當(dāng)前堆的使用情況, 主要查看commit的大小,再用g指令運(yùn)行一段后,查看是哪個(gè)對(duì)的commit的大小增加比較快。這里鎖定到了堆000001471ba50000

0:006>?!heap?-s


************************************************************************************************************************
??????????????????????????????????????????????NT?HEAP?STATS?BELOW
************************************************************************************************************************
NtGlobalFlag?enables?following?debugging?aids?for?new?heaps:
????stack?back?traces
LFH?Key???????????????????:?0xe82e55f3a47de176
Termination?on?corruption?:?ENABLED
??????????Heap?????Flags???Reserv??Commit??Virt???Free??List???UCR??Virt??Lock??Fast?
????????????????????????????(k)?????(k)????(k)?????(k)?length??????blocks?cont.?heap?
-------------------------------------------------------------------------------------

000001471ba50000?08000002????1220????820???1020?????48????25?????1????1??????0???LFH
000001471a110000?08008000??????64??????4?????64??????2?????1?????1????0??????0??????
000001471bd50000?08001002?????260?????36?????60??????7?????2?????1????0??????0???LFH
000001471bd10000?08001002????1280????112???1080??????4?????3?????2????0??????0???LFH
-------------------------------------------------------------------------------------

通過(guò)指令!heap -stat [-h Handle [-grp GroupBy [MaxDisplay]]]來(lái)做統(tǒng)計(jì)信息。這里按照block的數(shù)量進(jìn)行排序篩選出前5的。這里注意有時(shí)候數(shù)量多不一定就是泄露的點(diǎn),如果運(yùn)行時(shí)間足夠長(zhǎng)也可以使用-grp S選項(xiàng)來(lái)根據(jù)同種類型的內(nèi)存申請(qǐng)的總和進(jìn)行排序。

0:006> !heap -stat -h 000001471ba50000 -grp B 5
heap @ 000001471ba50000
group-by: BLOCKCOUNT max-display: 5
size #blocks total ( %) (percent of totalblocks)
64 1fa - c5a8 (30.43)
30 12c - 3840 (18.04)
48 d1 - 3ac8 (12.57)
20 7f - fe0 (7.64)
10 3c - 3c0 (3.61)
第三步?運(yùn)行一段時(shí)間,足夠明顯的感覺到內(nèi)存的增長(zhǎng),此時(shí)中斷調(diào)試,繼續(xù)按照block的數(shù)量進(jìn)行排序。此時(shí)觀察到大小為0x64的對(duì)象從數(shù)量0x1fa增長(zhǎng)到0x849,增加了1615次申請(qǐng)。那么如此數(shù)量的增長(zhǎng),或者上面如果是用-grp S進(jìn)行觀測(cè),則尋找內(nèi)存增加較多的Entry Size

0:009> !heap -stat -h 000001471ba50000 -grp B 5
heap @ 000001471ba50000
group-by: BLOCKCOUNT max-display: 5
size #blocks total ( %) (percent of totalblocks)
64 849 - 33c84 (64.14)
30 12c - 3840 (9.07)
48 d1 - 3ac8 (6.32)
20 7e - fc0 (3.81)
10 3c - 3c0 (1.81)
第四步?然后根據(jù)這個(gè)特定的大小,查看所有對(duì)應(yīng)的entry。此時(shí)可能有很多的entry, 如果想保存下來(lái)windbg 提供.logopen.logclose來(lái)保存命令輸出結(jié)果。

0:009> !heap -flt s 64
_HEAP @ 1471ba50000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
000001471ba61790 0009 0000 [00] 000001471ba617c0 00064 - (busy)
000001471ba66d80 0009 0009 [00] 000001471ba66db0 00064 - (busy)
000001471bafaa80 0009 0009 [00] 000001471bafaab0 00064 - (busy)
000001471bafab10 0009 0009 [00] 000001471bafab40 00064 - (busy)
......
000001471df9fd10 0009 0009 [00] 000001471df9fd40 00064 - (busy)
000001471df9fda0 0009 0009 [00] 000001471df9fdd0 00064 - (busy)
000001471df9fe30 0009 0009 [00] 000001471df9fe60 00064 - (busy)
000001471df9fec0 0009 0009 [00] 000001471df9fef0 00064 - (busy)
000001471df9ff50 0009 0009 [00] 000001471df9ff80 00064 - (busy)
000001471df9ffe0 0009 0009 [00] 000001471dfa0010 00064 - (busy)
_HEAP @ 1471a110000
_HEAP @ 1471bd50000
_HEAP @ 1471bd10000
第五步?隨便找?guī)讉€(gè)Entry的地址查看其函數(shù)調(diào)用棧,比如這里查看000001471df9ff50。比較容易就定位到了申請(qǐng)內(nèi)存的代碼。不過(guò)這里注意一下為什么函數(shù)棧是main?而不是MemoryLeakObj,這是因?yàn)槲覀兊木幾g進(jìn)行的優(yōu)化,不過(guò)這也不妨礙我們找到問(wèn)題。

0:009>?!heap?-p?-a?000001471df9ff50
????address?000001471df9ff50?found?in
????_HEAP?@?1471ba50000
??????????????HEAP_ENTRY?Size?Prev?Flags????????????UserPtr?UserSize?-?state
????????000001471df9ff50?0009?0000??[00]???000001471df9ff80????00064?-?(busy)
????????7ff8350fbe47?ntdll!RtlpCallInterceptRoutine 0x000000000000003f
????????7ff8350baa6f?ntdll!RtlpAllocateHeapInternal 0x000000000009192f
????????7ff8315b9686?ucrtbase!_malloc_base 0x0000000000000036
????????7ff6558613a3?MemoryLeakAnalysisViaWindbg!operator?new 0x000000000000001f
????????7ff65586102d?MemoryLeakAnalysisViaWindbg!main 0x000000000000002d
????????7ff6558615b0?MemoryLeakAnalysisViaWindbg!__scrt_common_main_seh 0x000000000000010c
????????7ff834e84034?KERNEL32!BaseThreadInitThunk 0x0000000000000014
????????7ff835083691?ntdll!RtlUserThreadStart 0x0000000000000021

總結(jié)

  1. 本文所闡述的方式是針對(duì)同一種大小的內(nèi)存申請(qǐng)導(dǎo)致的內(nèi)存泄露。而內(nèi)存泄露在大型工程中還有可能是可變大小的,那么這種方法就不適合。這也是為什么內(nèi)存泄露問(wèn)題寫了兩篇文章還沒寫完: 內(nèi)存泄露各式各樣,在客戶環(huán)境如何定位問(wèn)題,也是難上加難。計(jì)劃后面還會(huì)寫幾篇比如vmmap, DebugDialog,以及其他的一些非使用工具的一些方法。

  2. 上面的例子是筆者attach到進(jìn)程調(diào)試的結(jié)果。如果碰到在客戶環(huán)境有這樣的問(wèn)題,顯然在線調(diào)試是不太可能的,可以用gflag開啟ust后收集兩次Dump來(lái)查找問(wèn)題(這兩次dump的間隔時(shí)間要足以觀測(cè)到內(nèi)存泄露,根據(jù)實(shí)際情況而定)。

  3. 編寫代碼的時(shí)候盡量使用智能指針unique_ptrshared_ptr,埋坑簡(jiǎn)單,但找到問(wèn)題的原因可能比寫代碼的時(shí)間都長(zhǎng)。

發(fā)送關(guān)鍵字?內(nèi)存泄漏?獲取內(nèi)存泄漏系列文章

- EOF -

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來(lái)越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來(lái)越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語(yǔ)權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉