1. 程式人生 > >平臺伺服器控制代碼洩露問題的排查與解決

平臺伺服器控制代碼洩露問題的排查與解決

我們監控平臺有臺報警伺服器,其主要功能是接收前端,TDDC,網管伺服器等傳送的報警,並依據報警聯動配置進行相應的聯動操作,最近發現在該伺服器執行過程中,通過工作管理員檢視其控制代碼數量會不斷增加,以至於影響其他伺服器工作,初步懷疑是控制代碼洩露問題,現對其進行分析排查。

控制代碼是Windows用來標識應用程式所建立或使用的物件的唯一整數,Windows的核心物件包括進執行緒,視窗,點陣圖,GDI物件等等。應用程式通過控制代碼訪問核心物件,當使用完核心物件之後需要釋放資源關閉該核心物件控制代碼,如果未能正確關閉,則會造成控制代碼洩露。

一般而言,如果懷疑發生了控制代碼洩露,最首要任務是查詢洩露的控制代碼型別,這樣有助於後面的排查分析,縮小目標範圍。這時可以通過一些輔助工具來幫助分析,如Process Explorer,PCHunter

等,這些工具能夠非常明瞭的看到程序所正在使用的核心物件,可以幫助我們找到問題所在。

開啟Process Explorer,找到執行的報警伺服器程序,該工具的會顯示出報警伺服器當前建立的核心物件控制代碼,如圖,可以看出該程序的控制代碼數已經有上千個,而且還在快速增長中。

該工具很方便的一點是,當有新的核心物件建立時,在下方的列表框中會以綠色標識出來,方便檢視,觀察一段時間發現,不停的有執行緒物件建立,而且不會關閉,初步猜測應該是執行緒物件的控制代碼沒有關閉導致。接下來的工作就是要找出這個執行緒核心物件在哪兒建立的。

查詢控制代碼的建立位置的可以通過windbg來獲取,windbgwindows

下一款非常強大的核心偵錯程式。使用Windbg!htrace命令可以除錯控制代碼洩露。其原理比較簡單,就是分別為程序的核心物件做兩次快照,比較這兩次的不同,就可以知道有哪些核心物件建立了,同時還能找到是在哪兒分配的。

開啟windbg,按F6,附加報警伺服器程序。在命令列裡面輸入!htraceenable開啟htrace功能。輸入!htrace –snapshot做第一個快照,然後輸入g命令,讓程式執行一段時間。

該過程如下圖所示:

程式執行一段時間後,按Ctrl+Break鍵,將程式中斷下來。這時輸入!handle –diff,可以比較新增控制代碼的分配上下文。

通過仔細觀察,可以看出,有建立了多個執行緒核心物件,檢視一個核心物件的資訊可以使用!handle

命令。例如檢視handle值為f98的執行緒核心物件,可輸入!handlef98 ff。如下圖所示

其起始地址為HPR_GetNetWorkFlowData,可以確定該執行緒是使用HPR庫建立的,可以通過在原始碼中查詢HPR_Thread_Create來縮小範圍。

再圖中還可以看到執行緒ID7670.fef4,其中7670為程序IDfef4是執行緒ID,因此可以切換到該執行緒看看,輸入

~~[fef4]s命令,切換執行緒。

出現了一條錯誤資訊,即該執行緒已經不存在了,那其實我們就基本可以確定這裡發生了洩露,執行緒已經不復存在,但該執行緒的核心物件卻未能關閉。

通過仔細觀察,我們可以看到這些執行緒物件都是同一個執行緒建立的,如圖

最終都指向了71a8這個執行緒,這樣我們可以確定是71a8這個執行緒建立了很多執行緒核心物件,卻沒有將核心物件的控制代碼關閉,這樣造成了執行緒核心物件的控制代碼洩露,只要定位到71a8這個執行緒,就能找到洩露的地方。

輸入~~[71a8]s,切換到這個執行緒,並輸入kb列印其呼叫棧,如下圖:

呼叫棧是一堆非常奇怪的資料,其實是因為我的系統是64位的,而報警伺服器這個程序是32位的,windbg使用64位的上下文去解析32位的程序,造成了錯誤的解析。這時可以通過!sw這條命令來切換至32位的上下文。然後再敲入kb命令,就能看到該執行緒的呼叫棧了,如圖:

可以清楚的看到該執行緒的呼叫棧,此時該執行緒正在等待socket連線,使用的是HPR庫的HPR_Accept函式,到這個地步,在原始碼中定位該執行緒函式就非常容易了。

在原始碼中搜索HPR_Accept函式,只有一個地方:

可以清楚的看到,這個執行緒接受到連線之後,就建立了一個執行緒,不過卻未將建立執行緒的控制代碼關閉,從而造成了控制代碼洩露,這與我們之前分析的一致。知道了問題所在,修改也就很方便了。