控制代碼洩露例項分析
在上篇文章.NET物件與Windows控制代碼(二):控制代碼分類和.NET控制代碼洩露的例子中,我們有一個控制代碼洩露的例子。例子中多次建立和Dispose了DataReceiver和DataAnalyzer物件,但由於忘記呼叫DataAnalyzer的Stop方法,導致產生控制代碼洩露。本文假定我們已經發現了洩露現象但還不知道原因,討論如何在這種情況下分析問題。
一、發現問題
在程式執行約一個小時以後,通過工作管理員發現控制代碼數超過5000,執行緒數也超過1000。對於一段只需要並行接收和分析資料的簡易程式碼來說,這顯然太不正常了,我們可以判斷程式已經產生了洩露。
通過工作管理員可以非常方便的檢視程式實時的資源佔用情況,但無法瞭解到歷史資料和趨勢。程式是一開始就需要分配和使用這麼多資源,還是長時間執行的結果?如果是後者,那麼是執行過程中平穩持續的增長,還是在某個時間節點之後的突然增長?弄清楚這些問題是必要的,我們可以藉此初步判斷出記憶體洩露是與使用者的特定操作相關,或者與特定時間點上產生的事件相關;是跟程式的初始化有關,還是跟某些從始至終執行的後臺任務相關。
效能監視器可以很直觀的顯示這一趨勢,其中內建了很多有用的計數器,我們可以從圖形化介面中觀察這些計數器值的變化規律,瞭解系統和程序的執行狀況。使用Win + R組合鍵開啟“執行”視窗,輸入perfmon開啟效能監視器。點選綠色加號按鈕開啟“新增計數器”對話方塊,選擇Process中的Handle Count和Thread Count,然後選擇LeakExample程序作為例項,新增這兩個計數器。
接下來觀察這些數值的變化。在這期間,我們像往常一樣的使用程式,可以重複進行一些可能造成記憶體洩露的操作。在執行過一段時間後,得到了如下的圖表。控制代碼數和執行緒數在持續的增長,很容易猜測到跟Timer有關,因為Timer定期觸發,並且每次觸發都需要使用執行緒。即便如此,仍然需要確切的定位究竟是什麼物件產生了洩露,因為實際的專案中可能用到的Timer或者後臺執行緒的程式碼遠遠不止一兩處。
二、分析執行中的程序
首先應該找出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檢視自上次快照以來新開啟的控制代碼。由於命令輸出過長,一些不重要的資訊被隱去用省略號代替。
0:482> !htrace -enable Handle tracing enabled. Handle tracing information snapshot successfully taken. 0:482> g (1988.2f3c): Break instruction exception - code 80000003 (first chance) eax=7fbc0000 ebx=00000000 ecx=00000000 edx=779fd23d esi=00000000 edi=00000000 eip=77993540 esp=5a75ff28 ebp=5a75ff54 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 ntdll!DbgBreakPoint: 77993540 cc int 3 0:015> !htrace -diff Handle tracing information snapshot successfully taken. 0x6 new stack traces since the previous snapshot. Ignoring handles that were already closed... Outstanding handles opened since the previous snapshot: -------------------------------------- Handle = 0x00000b68 - OPEN Thread ID = 0x00002a68, Process ID = 0x00001988 0x779a4b7c: ntdll!ZwCreateThreadEx+0x0000000c 0x75d3bc5d: KERNELBASE!CreateRemoteThreadEx+0x00000161 0x7643281d: KERNEL32!CreateThreadStub+0x00000020 0x6c54b51f: clr!Thread::CreateNewOSThread+0x0000009b 0x6c54b358: clr!Thread::CreateNewThread+0x000000a8 0x6c54b8ad: clr!ThreadpoolMgr::CreateUnimpersonatedThread+0x00000275 0x6c54b9fc: clr!ThreadpoolMgr::MaybeAddWorkingWorker+0x00000129 0x6c53f298: clr!ManagedPerAppDomainTPCount::SetAppDomainRequestsActive+0x0000002f -------------------------------------- Handle = 0x00000b64 - OPEN Thread ID = 0x00002a68, Process ID = 0x00001988 0x779a49fc: ntdll!ZwCreateEvent+0x0000000c 0x75d376a0: KERNELBASE!CreateEventExW+0x0000006e 0x75d376f0: KERNELBASE!CreateEventW+0x00000027 0x6c54a106: clr!CLREventBase::CreateManualEvent+0x00000036 0x6c54a84f: clr!Thread::AllocHandles+0x00000064 0x6c54b4f4: clr!Thread::CreateNewOSThread+0x00000074 0x6c54b358: clr!Thread::CreateNewThread+0x000000a8 0x6c54b8ad: clr!ThreadpoolMgr::CreateUnimpersonatedThread+0x00000275 0x6c54b9fc: clr!ThreadpoolMgr::MaybeAddWorkingWorker+0x00000129 0x6c53f298: clr!ManagedPerAppDomainTPCount::SetAppDomainRequestsActive+0x0000002f 0x6ae49bd3: mscorlib_ni+0x00389bd3 0x6adcd38c: mscorlib_ni+0x0030d38c -------------------------------------- Handle = 0x00000b60 - OPEN Thread ID = 0x00002a68, Process ID = 0x00001988 0x779a49fc: ntdll!ZwCreateEvent+0x0000000c … … -------------------------------------- Handle = 0x00000b70 - OPEN Thread ID = 0x00002a68, Process ID = 0x00001988 0x779a49fc: ntdll!ZwCreateEvent+0x0000000c … … -------------------------------------- Handle = 0x00000b54 - OPEN Thread ID = 0x00002a68, Process ID = 0x00001988 0x779a49fc: ntdll!ZwCreateEvent+0x0000000c … … -------------------------------------- Handle = 0x00000b50 - OPEN Thread ID = 0x000011f8, Process ID = 0x00001988 0x779a49fc: ntdll!ZwCreateEvent+0x0000000c … … -------------------------------------- Displayed 0x6 stack traces for outstanding handles opened since the previous snapshot. |
可以看到,在兩次!htrace命令之間有6個handle被開啟,由呼叫堆疊可知其中有1個Thread物件和5個Event物件,並且在第1個Thread物件之後的4個Event都屬於該執行緒。如果重複!htrace -diff多次,可以發現一個規律,即每個Thread物件被建立之後,緊接著就會有4個Event物件在同一執行緒中被開啟,說明在本例中洩露的根源在於Thread物件,這也解釋了為什麼Event控制代碼數大致是Thread的4倍。實際上每個執行緒在建立的時候的確會建立4個Manual Event,從上面控制代碼開啟時的呼叫堆疊也能看出,clr!Thread::CreateNewOSThread方法除了建立Thread物件,也會建立幾個Manual Reset Event用於控制執行緒的掛起和恢復。
檢視Event和Thread控制代碼的詳細資訊,下面的輸出顯示了Thread控制代碼所指向的執行緒Id,以及其後的Event控制代碼資訊。
0:015> !handle 0x00000b68 f Handle b68 Type Thread Attributes 0 GrantedAccess 0x1fffff: Delete,ReadControl,WriteDac,WriteOwner,Synch Terminate,Suspend,Alert,GetContext,SetContext,SetInfo,QueryInfo,SetToken,Impersonate,DirectImpersonate HandleCount 4 PointerCount 6 Name <none> Object Specific Information Thread Id 1988.261c Priority 10 Base Priority 0 Start Address 6c54a086 clr!Thread::intermediateThreadProc 0:015> !handle 0x00000b64 f Handle b64 Type Event Attributes 0 GrantedAccess 0x1f0003: Delete,ReadControl,WriteDac,WriteOwner,Synch QueryState,ModifyState HandleCount 2 PointerCount 3 Name <none> Object Specific Information Event Type Manual Reset Event is Set |
接下來檢視這個新啟動的執行緒在執行什麼程式碼,這個資訊將幫助我們找到是哪裡的程式碼建立了該執行緒。我們需要載入SOS擴充套件,並利用上面輸出的Thread Id資訊。
0:015> .loadby sos clr 0:015> !threads ThreadCount: 323 UnstartedThread: 0 BackgroundThread: 266 PendingThread: 0 DeadThread: 56 Hosted Runtime: no Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 fb8 005015e8 26020 Preemptive 4EEC2A44:00000000 004f9540 0 STA 2 2 a20 0050e080 2b220 Preemptive 00000000:00000000 004f9540 0 MTA (Finalizer) 8 5 14cc 00553c48 102a220 Preemptive 00000000:00000000 004f9540 0 MTA (Threadpool Worker) 284 280 f34 1178fa50 3029220 Preemptive 00000000:00000000 004f9540 0 MTA (Threadpool Worker) 286 283 1ff4 117bd278 3029220 Preemptive 00000000:00000000 004f9540 0 MTA (Threadpool Worker) 761 764 229c 24cfc070 3029220 Preemptive 00000000:00000000 004f9540 0 MTA (Threadpool Worker) 849 865 1bc8 490eb860 3029220 Preemptive 00000000:00000000 004f9540 0 MTA (Threadpool Worker) XXXX 868 0 490e82f0 1039820 Preemptive 00000000:00000000 004f9540 0 Ukn (Threadpool Worker) 900 900 1054 490edd58 3029220 Preemptive 00000000:00000000 004f9540 0 MTA (Threadpool Worker) 898 901 654 490d9370 3029220 Preemptive 00000000:00000000 004f9540 0 MTA (Threadpool Worker) 903 903 828 490d9e00 3029220 Preemptive 00000000:00000000 004f9540 0 MTA (Threadpool Worker) XXXX 904 0 490ead30 1039820 Preemptive 00000000:00000000 004f9540 0 Ukn (Threadpool Worker) XXXX 1004 0 11758b70 1039820 Preemptive 00000000:00000000 004f9540 0 MTA (Threadpool Worker) 10 1005 2844 117590b8 3029220 Preemptive 00000000:00000000 004f9540 0 MTA (Threadpool Worker) 7 1006 314 11759600 3029220 Preemptive 00000000:00000000 004f9540 0 MTA (Threadpool Worker) … … … … 316 804 2164 0054f960 3029220 Preemptive 00000000:00000000 004f9540 0 MTA (Threadpool Worker) 318 803 1758 24a3e810 3029220 Preemptive 00000000:00000000 004f9540 0 MTA (Threadpool Worker) 317 802 27bc 116e1540 3029220 Preemptive 00000000:00000000 004f9540 0 MTA (Threadpool Worker) 5 801 261c 117152d0 3029220 Preemptive 4EEC0C44:00000000 004f9540 0 MTA (Threadpool Worker) 0:015> ~5s eax=00000000 ebx=00000258 ecx=00000001 edx=4fa6bc17 esi=0465ee48 edi=00000000 eip=779a64f4 esp=0465ee04 ebp=0465ee6c iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206 ntdll!KiFastSystemCallRet: 779a64f4 c3 ret 0:005> !clrstack OS Thread Id: 0x261c (5) Child SP IP Call Site 0465eef4 779a64f4 [HelperMethodFrame: 0465eef4] System.Threading.Thread.SleepInternal(Int32) 0465ef68 6ad83365 System.Threading.Thread.Sleep(Int32) 0465ef6c 001d04cd LeakExample.DataAnalyzer.DoAnalyze(System.Object) [D: \TimerLeak\TimerLeak\Form1.cs @ 88] 0465ef7c 6adede48 System.Threading.TimerQueueTimer.CallCallbackInContext(System.Object) 0465ef80 6adc2367 System.Threading.ExecutionContext.RunInternal(… …) 0465efec 6adc22a6 System.Threading.ExecutionContext.Run(… …) 0465f000 6adedd91 System.Threading.TimerQueueTimer.CallCallback() 0465f034 6adedc4c System.Threading.TimerQueueTimer.Fire() 0465f074 6ade11a5 System.Threading.TimerQueue.FireQueuedTimerCompletion(System.Object) 0465f078 6adcdd34 System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() 0465f08c 6adcd509 System.Threading.ThreadPoolWorkQueue.Dispatch() 0465f0dc 6adcd3a5 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() 0465f300 6c432652 [DebuggerU2MCatchHandlerFrame: 0465f300] |
從呼叫堆疊可以看出,新執行緒是由Timer觸發的,回撥函式是DoAnalyze,參照上篇文章中的程式碼,得知它就是DataAnalyzer中的analyzeTimer。這本身沒有什麼問題,但是檢查多個執行緒的呼叫堆疊,重複以上的步驟進行多次分析後,發現所有新增的執行緒都是由這個timer觸發的。Timer本身被設定為每秒觸發一次,而每次觸發的執行時間都小於一秒。出現大量的執行緒,說明timer物件本身產生了洩露,即程序中有大量的timer例項在執行,而程式設計的本意是程序中只存在一個analyzeTimer。到這裡問題已經比較明顯了,往往已經可以從程式碼審查中找出問題,即analyzeTimer沒有被Dispose。
三、小結
針對有控制代碼洩露的程式,本文描述了一種分析的思路。分析的物件是執行中的程序,因此這是一種動態分析,即我們可以在它執行的過程中,反覆的重現問題,而後觀察新的洩露情況。實際的專案中,這個過程是尋找問題復現關鍵點的過程,也是反覆猜測和證實,以及發現新線索的過程。可以進行動態分析實際上是比較幸運的,因為另一些情況下,問題發生之後很難再次重現,或者現場環境不允許我們進行反覆的嘗試。這時我們需要快速的蒐集環境資料,並打好記憶體轉儲Dump檔案,事後進行靜態分析。下一篇文章,我們仍然用這個例子,探討如何進行Dump分析,並討論一點Timer的實現細節。
下載網頁:https://technet.microsoft.com/en-us/sysinternals/bb896653.aspx
http://www.cnblogs.com/Leo_wl/p/5397274.html
相關推薦
控制代碼洩露例項分析
在上篇文章.NET物件與Windows控制代碼(二):控制代碼分類和.NET控制代碼洩露的例子中,我們有一個控制代碼洩露的例子。例子中多次建立和Dispose了DataReceiver和DataAnalyzer物件,但由於忘記呼叫DataAnalyzer的Stop方法,導
如何快速分析fd leaks, 檔案控制代碼洩露.
[Keyword] FD leaks, File Description Leaks, Too many open files, error 24 [Solution]android 預設每一個程序最多能夠開啟的檔案數量為1024, 一旦達到預置,則會爆錯 error=24, 即Too many open
控制代碼洩露與CloseHandle
參考:http://www.cnblogs.com/zhwl/archive/2012/11/07/2758212.html 多程式在建立執行緒都這樣寫的: ...... ThreadHandle = CreateThread(NULL,0,.....); CloseHandel(T
.NET物件與Windows控制代碼(二):控制代碼分類和.NET控制代碼洩露的例子
上一篇文章介紹了控制代碼的基本概念,也描述了C#中建立檔案控制代碼的過程。我們已經知道控制代碼代表Windows內部物件,檔案物件就是其中一種,但顯然系統中還有更多其它型別的物件。本文將簡單介紹Windows物件的分類。 控制代碼可以代表的Windows物件分為三類,核心物件(Kernel Object)、
windbg檢測控制代碼洩露(可定位到具體程式碼)
1、用c++寫一個控制代碼洩露的樣例程式: #include "stdafx.h"#include <windows.h>voidfun1(void);voidfun2(void);voidfun3(void);voidfun4(void);intmai
android異常:dialog視窗控制代碼洩露
丟擲異常:Activity ... has leaked window com.android.internal.policy.impl.PhoneWindow$... that was orifin
linux中檔案控制代碼洩露
1.檔案控制代碼洩露 在linux中,如果一個檔案正在被某個程序佔用,使用者操作rm刪除該檔案後,我們ls後發現檔案已經不存在了,但實際上該檔案仍然在磁碟上。直到使用它的程序退出後,檔案佔用的磁碟空間才會被釋放。 其原理如下:
平臺伺服器控制代碼洩露問題的排查與解決
我們監控平臺有臺報警伺服器,其主要功能是接收前端,TDDC,網管伺服器等傳送的報警,並依據報警聯動配置進行相應的聯動操作,最近發現在該伺服器執行過程中,通過工作管理員檢視其控制代碼數量會不斷增加,以至於影響其他伺服器工作,初步懷疑是控制代碼洩露問題,現對其進行分析排查。 控
【舊文章搬運】Windows控制代碼表分配演算法分析(三)
原文發表於百度空間,2009-03-30========================================================================== 三、當需要申請一個新的二級表(MidLevelTable)時,呼叫ExpAllocateMidLevelTable函式
【舊文章搬運】Windows控制代碼表分配演算法分析(實驗部分)
原文發表於百度空間,2009-03-31========================================================================== 理論結合實踐,這是我一貫的學習方法~~實驗目的:以實驗的方式觀察PspCidTable的變化,從中瞭解Windows控
多執行緒委託之跨執行緒問題分析--在建立視窗控制代碼之前,不能在控制元件上呼叫 Invoke 或 BeginInvoke(解決方法已更新)
檢視巢狀檢視+groupby+sum+if超慢?檢視巢狀檢視+groupby+sum+if超慢? 炯蕉蔚郝iar貉k湯秤TP2Fx扯訃詬壤撞蝸 《 http://babyknow.baidu.com/article/1376a5480527629546e457877078
libevent原始碼分析(6)--2.1.8--建立和釋放libevent控制代碼event_base的相關函式
一、event_base_new 建立預設的event_base ** * Create and return a new event_base to use with the rest of Libevent. * * @return a new event_ba
C#獲取視窗控制代碼概念和方法例項
在Windows中,控制代碼是一個系統內部資料結構的引用。例如當你操作一個視窗,或說是一個Delphi窗體時,系統會給你一個該視窗的控制代碼,系統會通知你:你正在操作142號視窗,就此你的應用程式就能要求系統對142號視窗進行操作——移動視窗、改變視窗大小、把視窗極小化為圖
從父視窗中獲取子視窗控制代碼以及其例項控制代碼
獲取父視窗控制代碼 HWND hWnddlg = AfxGetMainWnd()->m_hWnd; //主視窗控制代碼 或者 HWND hWnddlg = this->m_hWnd; //主視窗控制代碼 獲取子視窗控制代碼 如果子視窗是控制元件,並有控制元件
利用windbg分析崩潰,控制代碼洩漏,死鎖,CPU高,記憶體洩漏
Windbg的一些簡單使用命令 一、崩潰 1、 輸入.ecxr;kbn得到崩潰的堆疊 其中原始碼如下 2、 檢視堆疊和原始碼,發現第0幀導致崩潰,程式碼也是原生代碼 輸入.frame 0,切到第0幀如下 3、 輸入 dv 檢視當前幀的一些變數資訊
對Java中的物件、例項、控制代碼、直接指標的理解
虛擬機器在建立物件的時候,會優先查詢常量池中是否有該物件的例項,如果沒有則需要載入、解析、初始化class,然後分配記憶體,初始化記憶體,設定物件(HASH CODE 、 GC年代等),最後執行init才算是建立完物件。 物件即例項。
11 個 Visual Studio 代碼性能分析工具
集成 line add 一個 能夠 max sta 過程 preview 軟件開發中的性能優化對程序猿來說是一個很重要的問題。一個小問題可能成為一個大的系統的瓶頸。可是對於程序猿來說。通過自身去優化代碼是十分困難的。幸運的是。有一些很棒的工具能夠幫助程序猿進行代碼分析
小程序商城開發小程序系統代碼編程分析
類型 com 方式 微信小程序 優勢 復雜 未來 電子商務 主體 小程序商城開發小程序系統代碼編程分析:151.1222.4001(微/電)微信小程序開發,小程序商城開發,小程序模式開發,小程序源碼開發,小程序軟件開發,小程序應用開發,小程序微商城搭建,小程序分銷返利系統開
Apache Tomcat CVE-2017-12615遠程代碼執行漏洞分析
文件 only html ive zip pre clas one 判斷 2017年9月19日, Apache Tomcat官方發布兩個嚴重的安全漏洞, 其中CVE-2017-12615為遠程代碼執行漏洞,通過put請求向服務器上傳惡意jsp文件, 再通過jsp文件在服
代碼測試覆蓋率---分析
func ons 函數 語句 覆蓋率 執行 分析 fun 測試覆蓋率 測試代碼的覆蓋率要求80%以上 Statements: 語句覆蓋率,執行到每個語句; Branches: 分支覆蓋率,執行到每個if代碼塊; Functions: 函數覆蓋率,調用到程式中的每