內存泄漏
1、方法一:批處理 umdh
原理:借助winDBG 工具,先設置好pdb路徑,然后加入進程,運行進程,抓取第一次快照(Snap1.log );運行一段時間(讓內存增長),然后執行內存泄漏檢測分析.bat,抓取第二次快照(Snap2.log)。最終,比較兩次快照結果存到result.txt 中
step1:內存泄漏開始.bat
C:
cd C:\Program Files\Debugging Tools for Windows (x86)//windbg 安裝路徑
set _NT_SYMBOL_PATH=E:\testwy\Pdb //pdb 路徑
gflags -i OPCServer.exe +ust //要加載的進程
umdh -pn:OPCServer.exe -f:d:\Snap1.log //快照路徑
pause
step2:內存泄漏檢測.bat
cd C:\Program Files\Debugging Tools for Windows (x86) //winDbg 路徑
set _NT_SYMBOL_PATH=E:\testwy\Pdb //pdb 路徑
umdh -pn:OPCServer.exe -f:d:\Snap2.log //第二次快照路徑
umdh -d D:\Snap1.log D:\Snap2.log -f:d:\result.txt //比較兩次快照的分析結果
pause
方法二:騰訊內存泄漏檢測工具tMemMonitor (TMM)
C/C++由於靈活、高效的優點一直以來都是主流的程序設計語言之一,但是其內存的分配與釋放均由程序員自己管理,當由於疏忽或錯誤造成程序未能釋放不再使用的內存時就會造成內存泄漏。在大型、復雜的應用程序中,內存泄漏往往是最常見的問題,因而及時解決內存泄漏非常必要。tMemMonitor (TMM)作為一個專業、准確、易用的內存泄漏分析工具,可以幫助C/C++程序員迅速地解決內存泄漏這個令人頭疼的問題。
TMM下載地址(中文版): http://download.csdn.net/detail/tmemmonitor/9444634
TMM下載地址(英文版): http://download.csdn.net/detail/tmemmonitor/9444660
一.背景
目前市面上已有一些Windows平台下的內存泄漏動態檢測工具,比如UMDH,VLD,Purify,BoundsCheck等,其中Purify和BoundsCheck是昂貴的商用軟件,UMDH需要人工獲取內存快照,操作門檻較高,VLD則需要修改源程序的代碼,同時這幾款工具都存在誤報情況,因此准確性不高。針對Windows平台,C/C++程序員迫切需要一款專業、准確、易用的內存泄漏分析工具。
二.TMM簡介
TMM是一款運行時C/C++內存泄漏檢測工具。TMM認為在進程退出時,堆內存中沒有被釋放且沒有指針指向的無主內存塊即為內存泄漏,並進而引入垃圾回收(GC, Garbage Collection)機制,在進程退出時檢測出堆內存中所有沒有被引用的內存單元,因而內存泄露檢測准確率為100%。
TMM工具主要包含兩部分,第一部分是客戶端的檢測界面,客戶端部分主要負責監控目標進程中的內存行為並計算內存泄漏。檢測時只要將被檢測程序添加到監控列表中,然后正常運行被檢程序即可,以下為客戶端界面:
另一部分是結果的展示與分析。TMM支持本地查看和在線查詢兩種方式。本地查看時,提供按泄漏次數或泄漏大小對結果進行排序的功能,並在安裝目錄的data文件夾中給出詳細分析報告。用戶也可憑QQ帳號登錄WeTest網站對內存泄漏情況進行在線查詢。以下為本地查看結果:
三.TMM的特性和優點
專業
Ø 最快的注入技術
Ø 基於GC的精准算法,無需內存快照
Ø 無損目標程序性能
Ø 檢測結果精准定位到代碼堆棧信息
准確
Ø 二次遍歷堆內存對象里的指針
Ø 寄存器級的問題跟蹤,完整掃描每個線程里32位寄存器內的指針
Ø 不放過全局數據區里的內容
易用
Ø 支持自定義程序
Ø 無須編譯,立即使用
Ø 一鍵操作,無需切換
四.算法原理
1. 替換/注入堆內存分配函數的算法
Windows中有多種級別的內存分配函數,其中,最底層的是ntdll.dll提供的Rtl系列函數,在這之上的有Windows API提供的heap管理函數,再上層,C/C++庫提供了malloc/free函數和new/delete操作符,因此替換如此層級復雜的函數比較困難,同時如果替換現有的堆分配函數,則無法做到和原有函數的執行效果完全一致,所以替換原生的堆分配函數對於Windows系統來說幾乎是不可行的,那么只能wrap(包裹)這些堆分配函數。
Linux下的內存檢測工具,比如Valgrind就采用了包裹堆分配函數的方法,但它包裹的方法是不透明的,在調用棧中會多出額外一幀。TMM則采用全透明的包裹函數,將掛鈎函數分為前后二部分(如圖1所示)。在函數調用前執行per-hook函數,在函數返回前調用執行post-hook函數。有了前后掛鈎函數,TMM就可以在堆分配/釋放函數執行前獲得參數,並修改分配大小之類的參數;在函數執行后,記錄分配的大小和地址、調用棧之類的信息。
圖1
2. 泄露檢測的算法
TMM使用基於堆內存可訪問性的內存泄露檢測法( reachability-based leak detection),該算法的核心就是檢測(掃描)沒有任何指針指向的堆內存,具體分為五步:
Step 1. 進程退出時,suspend所有線程,防止數據在掃描過程中更改。假設此時進程中的堆內存布局如圖2所示;
圖2
Step 2. 統計root-set,它由每個線程的寄存器、所有非堆內存、所有線程棧幀頂部RSP/ESP以上區域、所有庫的數據區組成;
Step 3. 從root-set出發遍歷圖2,標記出有指針指向的內存塊,即beginning reachable blocks,如圖3中A、C;
圖3
Step 4. 由於beginning reachable blocks也會包含有指針,因此通過遍歷beginning reachable blocks可以找出其內部指針指向的內存塊並標記,如圖4中B;
圖4
Step 5. 統計檢測出來的內存泄漏,即圖4中unreachable的堆內存塊D、E、F。
五.使用步驟
圖5
1. 在擁有Administrator權限的情況下啟動TMM。
2. 在監控列表中右鍵添加目標程序,正常操作。
3. 正常退出目標程序。
4. 耐心等待檢測結果生成(目標程序狀態由running變為null時,說明程序正常退出,檢測結果生成完畢)。
5. 查看結果。
六.注意事項
1. 安裝TMM時,用戶應具有Administrator權限,並且TMM不支持中文安裝路徑。
2. 使用TMM時需要修改注冊表,如遇安全軟件彈窗警告,可將TMM加入信任列表放心使用。
3. 被檢測程序不能是加殼版本,因為加殼程序的函數名和函數地址已經混淆。
4. 被檢測程序需是release版本。
5. 如需在分析報告中顯示泄漏點詳細堆棧信息,請在被檢測程序同級目錄放置同版本的PDB文件,PDB解析時目錄不支持中文。
6. 使用TMM導致被測程序退出時變慢屬於正常情況,此時TMM正在統計內存泄漏情況,請不要手動強制結束進程。
七.總結
TMM適用於PC端所有C/C++程序的內存泄漏分析。對於被測程序,不需要修改源代碼,運行一次被測程序就能夠准確定位泄漏的文件名和行號。TMM是一款專業、准確、易用的內存泄漏檢測工具,值得每個程序員擁有。
TMM下載地址(中文版): http://download.csdn.net/detail/tmemmonitor/9444634
TMM下載地址(英文版): http://download.csdn.net/detail/tmemmonitor/9444660
句柄增長
句柄泄露調試(Handles Leak Debug)
一、概述
造成句柄泄露的主要原因,是進程在調用系統文件之后,沒有釋放已經打開的文件句柄。
對於句柄泄露,輕則影響某個功能模塊正常運行,重則導致整個應用程序崩潰。在 Windows系統中, GDI 句柄上限是 12000 個,USER 句柄上限是 18000 個。
與 Windows 系統的設置不同,Linux 系統對進程可以調用的文件句柄數做了限制,在默認情況下,每個進程可以調用的最大句柄數為 1024 個。超過了這個數值,進程則無法獲得新的句柄。因此,句柄的泄露將會對進程的功能失效造成極大的隱患。
理論上,我們編程時,1 個進程使用的句柄數建議不應該超過 1000。
二、分析
根據我們項目的測試經驗,通常統計出來的句柄圖形如下列 3 種:
1、平穩型
圖 2-1. 平穩圖
在程序運行當中,句柄被不斷地打開關閉,因此統計圖形呈現平穩的鋸齒形。在程序運行后期,很多臨時打開的句柄被逐漸關閉,總的句柄數量沒有隨着時間的推移而增加,因此該程序不存在句柄泄露。
2、峰值穩定型
圖 2-2. 峰值穩定圖
在該程序運行初期,程序打開的句柄數量會隨着時間的推移而逐步增加。但是當運行一
段時間后,句柄數量會達到一個相對平穩的狀態,大概 3500 左右。這個時候表明程序打開
了很多臨時句柄,但是句柄數量相對穩定,也不存在句柄泄露問題。
3、遞增型
圖 2-3. 遞增圖
程序在運行當中,某一操作引起了程序打開句柄數量逐步增加,而且沒有出現相對平穩的跡象,說明該程序可能存在句柄泄露,需要進一步分析是哪一部分的句柄存在泄漏,以及什么操作會引起程序句柄的泄露。
通過對程序句柄數量進行采樣統計,並且繪制出相應的統計圖形,能夠以比較直觀的方式判斷在程序中是否存在句柄泄露。該方法基於程序要運行大量的測試用例,增加測試用例的覆蓋率,盡可能多的用測試用例觸發程序打開和關閉句柄的操作,這樣才能發現潛在的句柄泄露 bug。對於如何能夠快速的發現句柄泄露代碼,我們將做進一步研究。
方法一:WinDbg工具分析
step1:觀察句柄增長規律(資源管理器、性能計數器)
在程序運行約一個小時以后,通過任務管理器發現句柄數超過5000,線程數也超過1000。對於一段只需要並行接收和分析數據的簡易代碼來說,這顯然太不正常了,我們可以判斷程序已經產生了泄露。
通過任務管理器可以非常方便的查看程序實時的資源占用情況,但無法了解到歷史數據和趨勢。程序是一開始就需要分配和使用這么多資源,還是長時間運行的結果?如果是后者,那么是運行過程中平穩持續的增長,還是在某個時間節點之后的突然增長?弄清楚這些問題是必要的,我們可以借此初步判斷出內存泄露是與用戶的特定操作相關,或者與特定時間點上產生的事件相關;是跟程序的初始化有關,還是跟某些從始至終運行的后台任務相關。
性能監視器可以很直觀的顯示這一趨勢,其中內置了很多有用的計數器,我們可以從圖形化界面中觀察這些計數器值的變化規律,了解系統和進程的運行狀況。使用Win + R組合鍵打開“運行”窗口,輸入perfmon打開性能監視器。點擊綠色加號按鈕打開“添加計數器”對話框,選擇Process中的Handle Count和Thread Count,然后選擇LeakExample進程作為實例,添加這兩個計數器。
接下來觀察這些數值的變化。在這期間,我們像往常一樣的使用程序,可以重復進行一些可能造成內存泄露的操作。在運行過一段時間后,得到了如下的圖表。句柄數和線程數在持續的增長,很容易猜測到跟Timer有關,因為Timer定期觸發,並且每次觸發都需要使用線程。即便如此,仍然需要確切的定位究竟是什么對象產生了泄露,因為實際的項目中可能用到的Timer或者后台線程的代碼遠遠不止一兩處。
step2:分析運行中的進程
首先應該找出5000多個句柄究竟代表什么對象。利用Process Explorer查看該進程,在下方面板中檢查句柄列表,發現有大量的Event句柄和Thread句柄,更進一步的,我們想知道到底有多少Event和Thread。
在這個列表中難以看出各種句柄的數量。可以按下Ctrl+A組合鍵,將Process Explorer中的進程列表和選中進程的句柄列表保存為文本文件,而后利用你所習慣使用的文本查看工具統計其中特定句柄的數量,我們這里使用Chrome瀏覽器的搜索功能看到約有4063個Event句柄和1008個Thread句柄。
到這里,我們有一個大致的印象,即泄露的對象是Event和Thread,其中Event占大多數。下一步需要找出是誰創建出了這些對象,可以使用Windbg跟蹤對象的創建。Windbg是非常方便的Windows調試工具,可以利用強大的SOS擴展命令診斷.NET程序中的各種問題,最新的Windbg(截止2016年4月)可以從MSDN的Download the WDK, WinDbg, and associated tools頁面下載,點擊頁面上的Get Debugging Tools for Windows (WinDbg)鏈接即可。
將Windbg附加到LeakExample.exe進程,而后使用!handle和!htrace命令對進程句柄進行分析。!handle命令可以列出進程內所有句柄,也可以查看特定句柄的信息,而!htrace顯示句柄的堆棧跟蹤。我們先使用!htrace -enable啟用句柄跟蹤,然后讓進程繼續運行幾分鍾時間,再中斷程序的執行,用!htrace -diff查看自上次快照以來新打開的句柄。由於命令輸出過長,一些不重要的信息被隱去用省略號代替。
step3:定位問題
0、先配置好相應的pdb符號文件
1、運行程序
WinDbg 提供了圖形界面和命令行兩種運行方式。這里介紹使用圖形界面的 WinDbg 來
調試應用程序:
File->OpenExecutable->可以選擇一個可執行文件進行調試。
File->Attache to a Process->可以選擇一個運行中的進程,並對其進行調試。
圖 3-1
2、啟動句柄操作的棧回溯
按下 F5 快捷鍵,第 1 次中斷進程運行,用!htrace -enable 命令開啟句柄檢測;htrace 提
供了進行句柄相關檢測的命令,可查看 WinDBG 幫助文檔。
圖 3-2
同時用 g 命令讓程序運行。
圖 3-3
3、抓取快照
第 2 次中斷進程運行,使用!htrace -snapshot 命令,獲得此時進程句柄的鏡像。並再次讓程序運行。
圖 3-4
4、運行程序,定位句柄泄露
第 3 次中斷進程運行,使用!htrace -diff 命令獲得當前句柄狀態與第 3 步 snapshot 鏡像句柄的差異;
圖 3-5
通過上面的棧回溯信息,很清楚可以看到句柄打開的地方。使用 lsa 命令可以定位到產
生句柄泄露的方法體代碼行,lsa handlew2!fun4+0x0000002e :
利用windbg分析崩潰,句柄泄漏,死鎖,CPU高,內存泄漏
一、崩潰
1、 輸入.ecxr;kbn得到崩潰的堆棧
其中源代碼如下
2、 查看堆棧和源代碼,發現第0幀導致崩潰,代碼也是本地代碼
輸入.frame 0,切到第0幀如下
3、 輸入 dv 查看當前幀的一些變量信息
發現變量p =0x00000000
二、句柄泄漏
1、 啟動進程
2、 用windbg附加到進程
3、 !htrace -enable命令開啟句柄檢測
4、 !htrace –snapshot
5、 運行一段時間后
6、 !htrace –diff
得到如下信息
標紅函數創建了event
7、輸入lsahandleLeak!ThreadProc1+0x00000037
這樣就可以看出代碼中在不停CreateEvent。
補充:
可以在windbg調式中,輸入!handle可以得到當前堆棧的一些句柄信息,可以看出這個堆棧event非常多。
三、死鎖
1、 啟動進程
2、 Windbg附加進程
3、 輸入~*kv, 輸出所有線程
4、 輸入!findstackntdll!RtlEnterCriticalSection,查找哪些線程在等待鎖
或者看代碼某一函數沒執行,對比windbg中的線程,找到線程id分析
圖1是源代碼,圖2是執行結果, ThreadProc1函數中的” ThreadProc1 lock g_mutex2”沒發生,懷疑是否死鎖了
5、windbg中線程信息如下,發現ThreadProc1在等某一把鎖
第三幀是本地代碼對比源代碼發現在等鎖g_mutex2;
第二幀是系統函數在等待鎖,鎖地址為00bf7140
6、輸入!cs 00bf7140,查看這把鎖信息
發現鎖的占有者是0x00002cc4
7、輸入~~[0x00002cc4],發現對應是3號線程
8、切到3號線程,並輸出堆棧
發現代碼27號,也在等一把鎖,鎖地址00bf7158
9、同理輸出鎖信息
10、發現鎖占有者0x00004c80,切到線程0x00004c80,並輸出堆棧
發現是剛才的2號線程
至此分析完成2號線程和3號線程發生死鎖。
四、CPU高
1、 啟動進程
2、 Windbg附加進程
3、 輸入!runaway
4、 發現2號線程cpu最高,切到2號線程,並輸出堆棧
5、 可以得到是cpuhigh!ThreadProc1+0x35
五、內存泄漏
5.1、windbg手動分析
1、設置gflags.exe,這工具和windbg在同一目錄
2、 windbg附加到進程,輸入!heap –s
3、程序運行一段時間之后,再次輸入!heap–s
發現00970000這個堆有增加,其他無變化
4、輸入!heap -stat -h00970000,查看這個堆狀態
發現這個堆的內存主要是被大小為0x224的塊占用
5、輸入!heap -flt s 224, 查看224這些塊被誰在使用
6、輸入!heap -p -a 00971d20,查看堆棧
7、 已經得到堆棧,本地有源代碼,還可以查看代碼,
輸入lsa memoryleak!ThreadProc1+0x00000048
5.2、利用umdh分析
1、同5.1設置gflags配置
2、開啟命令窗口cmd,輸入要定位內存泄露的程序gflags.exe /i memoryleak.exe +ust
3、 設置程序的符號表路徑
SET _NT_SYMBOL_PATH=SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols;F:\windbgtest\Debug
4、 啟動memoryleak.exe,利用umdh創建第一次heap快照
輸入umdh-pn:memoryleak.exe -f:memory1.log
5、 運行一段時間后,再次輸入快照,umdh -pn:memoryleak.exe -f:memory2.log
6、 分析前后2次快照的差異umdh -dmemory1.log memory2.log -f:memoryleak.log
會在當前路徑下面生成memoryleak.log,打開分析
7、
定位到代碼,需要具體分析邏輯,查看是否真的泄漏,還是還沒來得及釋放。
from:https://blog.csdn.net/zqw_4181/article/details/79162309