天天看點

記憶體洩漏簡介洩漏原因檢測方法檢測工具參考資料

記憶體洩漏

  • 簡介
  • 洩漏原因
      • 産生方式的分類
  • 檢測方法
      • 靜态分析技術
      • 源代碼插裝技術
      • 目标代碼插裝技術
      • 被測代碼預處理
      • 測試執行階段
      • 資料統計與結果彙總
  • 檢測工具
      • 部分工具
  • 參考資料

簡介

記憶體洩漏(Memory Leak)是指程式中已動态配置設定的堆記憶體由于某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導緻程式運作速度減慢甚至系統崩潰等嚴重後果。

記憶體洩漏缺陷具有隐蔽性、積累性的特征,比其他記憶體非法通路錯誤更難檢測。因為記憶體洩漏的産生原因是記憶體塊未被釋放,屬于遺漏型缺陷而不是過錯型缺陷。此外,記憶體洩漏通常不會直接産生可觀察的錯誤症狀,而是逐漸積累,降低系統整體性能,極端的情況下可能使系統崩潰。

随着計算機應用需求的日益增加,應用程式的設計與開發也相應的日趨複雜,開發人員在程式實作的過程中處理的變量也大量增加,如何有效進行記憶體配置設定和釋放,防止記憶體洩漏的問題變得越來越突出。例如伺服器應用軟體,需要長時間的運作,不斷的處理由用戶端發來的請求,如果沒有有效的記憶體管理,每處理一次請求資訊就有一定的記憶體洩漏。這樣不僅影響到伺服器的性能,還可能造成整個系統的崩潰。是以,記憶體管理成為軟體設計開發人員在設計中考慮的主要方面

洩漏原因

在C語言中,從變量存在的時間生命周期角度上,把變量分為靜态存儲變量和動态存儲變量兩類。靜态存儲變量是指在程式運作期間配置設定了固定存儲空間的變量而動态存儲變量是指在程式運作期間根據實際需要進行動态地配置設定存儲空間的變量。在記憶體中供使用者使用的記憶體空間分為三部分:

  • 程式存儲區
  • 靜态存儲區
  • 動态存儲區

程式中所用的資料分别存放在靜态存儲區和動态存儲區中。靜态存儲區資料在程式的開始就配置設定好記憶體區,在整個程式執行過程中它們所占的存儲單元是固定的,在程式結束時就釋放,是以靜态存儲區資料一般為全局變量。動态存儲區資料則是在程式執行過程中根據需要動态配置設定和動态釋放的存儲單元,動态存儲區資料有三類函數形參變量、局部變量和函數調用時的現場保護與傳回位址。由于動态存儲變量可以根據函數調用的需要,動态地配置設定和釋放存儲空間,大大提高了記憶體的使用效率,使得動态存儲變量在程式中被廣泛使用。

開發人員進行程式開發的過程使用動态存儲變量時,不可避免地面對記憶體管理的問題。程式中動态配置設定的存儲空間,在程式執行完畢後需要進行釋放。沒有釋放動态配置設定的存儲空間而造成記憶體洩漏,是使用動态存儲變量的主要問題。一般情況下,開發人員使用系統提供的記憶體管理基本函數,如malloc、recalloc、calloc、free等,完成動态存儲變量存儲空間的配置設定和釋放。但是,當開發程式中使用動态存儲變量較多和頻繁使用函數調用時,就會經常發生記憶體管理錯誤,例如:

  • 配置設定一個記憶體塊并使用其中未經初始化的内容;
  • 釋放一個記憶體塊,但繼續引用其中的内容;
  • 子函數中配置設定的記憶體空間在主函數出現異常中斷時、或主函數對子函數傳回的資訊使用結束時,沒有對配置設定的記憶體進行釋放;
  • 程式實作過程中配置設定的臨時記憶體在程式結束時,沒有釋放臨時記憶體。記憶體錯誤一般是不可再現的,開發人員不易在程式調試和測試階段發現,即使花費了很多精力和時間,也無法徹底消除。

産生方式的分類

以産生的方式來分類,記憶體洩漏可以分為四類:

  1. 常發性記憶體洩漏:發生記憶體洩漏的代碼會被多次執行到,每次被執行時都會導緻一塊記憶體洩漏。
  2. 偶發性記憶體洩漏:發生記憶體洩漏的代碼隻有在某些特定環境或操作過程下才會發生。常發性和偶發性是相對的。對于特定的環境,偶發性的也許就變成了常發性的。是以測試環境和測試方法對檢測記憶體洩漏至關重要。
  3. 一次性記憶體洩漏:發生記憶體洩漏的代碼隻會被執行一次,或者由于算法上的缺陷,導緻總會有一塊且僅有一塊記憶體發生洩漏。
  4. 隐式記憶體洩漏:程式在運作過程中不停的配置設定記憶體,但是直到結束的時候才釋放記憶體。嚴格的說這裡并沒有發生記憶體洩漏,因為最終程式釋放了所有申請的記憶體。但是對于一個伺服器程式,需要運作幾天,幾周甚至幾個月,不及時釋放記憶體也可能導緻最終耗盡系統的所有記憶體。是以,我們稱這類記憶體洩漏為隐式記憶體洩漏。從使用者使用程式的角度來看,記憶體洩漏本身不會産生什麼危害,作為一般的使用者,根本感覺不到記憶體洩漏的存在。真正有危害的是記憶體洩漏的堆積,這會最終耗盡系統所有的記憶體。從這個角度來說,一次性記憶體洩漏并沒有什麼危害,因為它不會堆積,而隐式記憶體洩漏危害性則非常大,因為較之于常發性和偶發性記憶體洩漏它更難被檢測到。

檢測方法

無論是C還是C++程式,運作時候的變量主要有三種配置設定方式:堆配置設定、棧配置設定、全局和靜态記憶體配置設定。記憶體洩漏主要是發生在堆記憶體配置設定方式中,即“配置了記憶體後,所有指向該記憶體的指針都遺失了”,若缺乏語言這樣的垃圾回收機制,這樣的記憶體片就無法歸還系統。因為記憶體洩漏屬于程式運作中的問題,無法通過編譯識别,是以隻能在程式運作過程中來判别和診斷。下面将介紹幾種常用的記憶體檢測方法,每種方法均以現有的記憶體檢測工具為分析範例,并對各種方法進行比較。

靜态分析技術

靜态分析技術就是直接分析程式的源代碼或機器代碼,獲得一些有用的資訊,而并不運作程式本身。目前有許多靜态分析的工具,編譯器就屬于這一類,它讀入源程式代碼,對源程式進行詞法和文法分析,進行資料類型的檢查以及一些優化的分析等,以此來提高程式的品質與運作效率。這類靜态的分析工具僅僅是讀入程式代碼進行相關的分析,而并不進行其它額外的操作,如修改源程式代碼等。

LCLink是一種通過對源代碼及添加到源代碼中特定格式的注釋說明進行靜态分析的程式了解和檢錯工具,的檢查對象是源程式,能檢查出的記憶體錯誤有記憶體配置設定釋放故障、空指針的錯誤使用、使用未定義或已被釋放的記憶體等程式錯誤。

LCLink重點分析兩類記憶體釋放錯誤:

  • 試圖釋放某記憶體塊,該記憶體塊有兩個或兩個以上的有效指針指向它。
  • 試圖釋放某記憶體塊,該記憶體塊沒有任何有效指針指向它。

解決此類記憶體錯誤的方法是規定配置設定某塊記憶體時傳回的指針必須釋放該記憶體。使用注釋表示某指針是唯一指向某記憶體塊的指針,使用注釋表示被調用函數可能釋放函數參數指向的記憶體塊或建立新的指針指向該記憶體塊。

源代碼插裝技術

為了獲得被測程式的動态執行資訊,需要對其進行跟蹤,一般使用插裝方法。所謂插裝就是在保持被測程式的邏輯完整性的基礎上,在被測程式的特定部位插入一段檢測程式又稱探針函數,通過探針的執行抛出程式的運作特征資料。基于這些特征資料分析,可以獲得程式的控制流及資料流資訊,進而獲得邏輯覆寫等動态資訊,這樣就可以在被測程式執行的過程中動态地同步執行程式的檢測工作。插裝方法又分為源代碼級程式插裝和目标代碼級程式插裝。源代碼插裝測試必須在靜态測試部分獲得的被測程式的結構資訊、靜态資料資訊、控制流資訊等基礎上,應用插裝技術向被測程式中的适當位置植入相應類型的探針,通過運作帶有探針的被測程式而獲得程式運作的動态資料。源代碼插裝要通過運作被測程式來測定程式的各種名額,如覆寫率、時間性能、記憶體使用等等,實作源代碼插裝的關鍵技術是借助于插入到源程式中的監控語句來收集執行資訊,以達到揭示程式内部行為和特性的目的,如圖所示。

記憶體洩漏簡介洩漏原因檢測方法檢測工具參考資料

.

基于源代碼插裝的動态測試架構分為個主要的階段:

  • 插裝互動與動态測試資訊分析;
  • 插裝階段;
  • 插裝庫制作階段;
  • 測試實施階段。

插裝互動與動态測試資訊分析是軟體測試工具與使用者互動的界面。使用者通過該界面選擇要進行動态測試的程式子產品,拓撲産生相應的插裝選擇記錄檔案。使用者還可以通過該互動界而浏覽動态測試結果資訊,在軟體測試工具的實作上,采用可視化的方式顯示這些動态資訊。插裝階段實作了在被測程式中植入探針,并生成帶有插裝資訊的源檔案。在此過程中,首先将被測程式經過預處理展開為不包含宏、條件編譯和頭檔案的檔案格式。然後,按照一定的插裝政策,根據前面生成的插裝選擇記錄檔案,将探針函數加載到該檔案中,最後生成插裝後的程式。插裝庫制作階段的目的是生成插裝庫中的探針函數,它含有插裝語句調用的函數及其函數的定義。顯然,插裝過程中生成的目标檔案中含有探針函數的樁,而探針函數的實作恰恰在本過程完成。需要指出的是,插裝庫的制作過程是獨立于動态測試過程之外的,可以與軟體測試工具開發同步。測試實施階段将插裝過程生成的檔案與插裝庫制作過程生成的插裝靜态庫連接配接生成帶有插裝資訊的可執行檔案,選取測試用例,運作該程式,可以獲得被測程式的動态跟蹤資訊。

在以上四個階段中,其中的插裝互動與動态測試資訊分析與測試實施階段是測試人員的可視部分,通過這兩部分,使用者與系統互動,完成測試工作。而插裝階段與插裝庫制作階段對測試人員是不可見的,在背景完成,對于使用者而言,這兩部分是完全透明的。在性能方面,采用插裝方法應盡量減少插裝開銷。為了達到不同的統計目的如語句覆寫、分支覆寫等,應盡量減少插裝次數。若能僅僅插裝一次就能完成多種類型的統計,則可使插裝代碼得到優化。此外,應盡量減少插裝代碼的數量,減少插裝代碼的運作次數,進而達到減小插裝代碼運作開銷的目的。特别是對于一些實時系統的測試,在這方面的要求尤為苛刻。一個運作時錯誤檢測工具,能夠自動檢測一應用中大量的程式設計和運作時錯誤。通過使用源碼插裝和運作時指針跟蹤的專利技術,在編譯時,附十插入測試和分析代碼,它建立一個有關程式中各種對象的資料庫。然後在運作時通過檢查資料值和記憶體引用驗證對象的一緻性和正确性。使用這些技術,包括變異測試技術等,一能夠檢查和測試使用者的代碼,精确定位錯誤的準确位置并給出詳細的診斷資訊。十十能夠可視化實時記憶體操作,優化記憶體算法。還能執行覆寫性分析,清楚地訓示那些代碼已經測試過。将內建到開發環境中,能夠極大地減少調試時間并有效地防止錯誤。檢驗每一次記憶體操作的有效性,包括靜态全局和堆棧以及動态配置設定記憶體的操作。葉有兩種運作模式。監護模式下使用者可以快速檢測代碼中的錯誤,不需要對代碼作任何插裝和處理源碼插裝模式則進行徹底地代碼檢測。

目标代碼插裝技術

目标代碼插裝實作主要分為預處理、測試執行和結果彙總個階段,工作流程如圖所示,系統主要工作是圍繞斷點而進行的。在預處理階段,首先靜态分析被測程式的目标代碼,查找待測程式中源代碼各語句、函數入口點在目标代碼中的對`應位置,然後在相應位置插入斷點在測試執行階段,啟動調試程序,當被測程式執行到斷點處時,響應斷點資訊,在相應的斷點處完成相應的統計操作在結果彙總階段,根據各斷點處的統計結果,按不同的統計角度進行歸并、綜合得到最終的統計資料。

記憶體洩漏簡介洩漏原因檢測方法檢測工具參考資料

.

被測代碼預處理

在測試預處理階段對被測程式的目标代碼進行分析,可以獲得目标代碼與源代碼中語句、函數的對應關系。在目标代碼中為相對應的源代碼的每條語句及每個函數的入口點插入斷點。對于第三方代碼,隻要其目标代碼格式與下生成的目标代碼格式一緻,我們就可以用與分析使用者代碼同樣的方法擷取資訊。擷取斷點的資訊後,為所有的斷點建立斷點連結清單,同時建立語句及函數的資訊連結清單,供随後的測試執行階段存儲資訊。預處理流程如圖所示。

記憶體洩漏簡介洩漏原因檢測方法檢測工具參考資料

.

測試執行階段

利用OCI技術,我們把測試執行看作是一個在被測程序和檢測程序間不斷切換的過程。每當被測程序遇到斷點,就會将自身挂起,同時發送消息喚醒檢測程序,檢測程序根據目前斷點的位址在斷點連結清單中查找相應節點,并查找對應的語句或函數資訊,記錄該語句或函數的執行次數、到達或離開的時刻,供以後統計之用。然後,将插入的斷點資訊去除,恢複原來的指令,轉入被測程序繼續執行。在轉入被測程序之前,必須将上一個斷點處的斷點恢複上一個斷點處的斷點在指令運作時被去除了。具體流程如圖所示。

記憶體洩漏簡介洩漏原因檢測方法檢測工具參考資料

.

資料統計與結果彙總

根據各斷點處的統計結果,按不同的統計角度進行歸并、綜合,進行覆寫率及各種時間的計算,得到最終的統計資料。是公司出品的一種軟體測試和品質保證工具,它能檢測程式記憶體洩漏和記憶體通路沖突等錯誤。使用目标碼插裝技術,在編譯器生成的目标碼中直接插入特殊的檢查指令實作對記憶體錯誤的檢測。在程式的所有代碼中插入這些檢查邏輯,包括第三方目标碼庫,并且驗證系統調用的接口。目标碼插裝技術分為連結前插裝和連結後插裝兩種插裝方法。使用如圖所示的連結前插裝法。檢查插裝後程式的每個記憶體讀寫動作,跟蹤記憶體使用情況,使用類似垃圾收集器的技術來檢查記憶體洩漏。垃圾收集機制分為兩階段垃圾檢測和垃圾回收。為了不影響程式的執行速度,提供了一個可調用的垃圾檢測器,使用類似于保守式垃圾收集算法,即标記一清除算法。在标記階段,遞歸地從資料段、堆棧段到資料堆跟蹤分析指針,并使用标準保守式方法為所有被引用的記憶體塊做标記。在清除階段,逐漸通路資料堆,并報告已配置設定但程式不再引用的記憶體塊,即程式的記憶體洩漏。

記憶體洩漏簡介洩漏原因檢測方法檢測工具參考資料

.

檢測工具

部分工具

1.ccmalloc-Linux和Solaris下對C和C++程式的簡單的使用記憶體洩漏和malloc調試庫。

2.Dmalloc-Debug Malloc Library.

3.Electric Fence-Linux分發版中由Bruce Perens編寫的malloc()調試庫。

4.Leaky-Linux下檢測記憶體洩漏的程式。

5.LeakTracer-Linux、Solaris和HP-UX下跟蹤和分析C++程式中的記憶體洩漏。

6.MEMWATCH-由Johan Lindh編寫,是一個開放源代碼C語言記憶體錯誤檢測工具,主要是通過gcc的precessor來進行。

7.Valgrind-Debugging and profiling Linux programs, aiming at programs written in C and C++.

8.KCachegrind-A visualization tool for the profiling data generated by Cachegrind and Calltree.

9.IBM Rational PurifyPlus-幫助開發人員查明C/C++、托管.NET、Java和VB6代碼中的性能和可靠性錯誤。PurifyPlus 将記憶體錯誤和洩漏檢測、應用程式性能描述、代碼覆寫分析等功能組合在一個單一、完整的工具包中。

10.ParasoftInsure++-針對C/C++應用的運作時錯誤自動檢測工具,它能夠自動監測C/C++程式,發現其中存在着的記憶體破壞、記憶體洩漏、指針錯誤和I/O等錯誤。并通過使用一系列獨特的技術(SCI技術和變異測試等),徹底的檢查和測試我們的代碼,精确定位錯誤的準确位置并給出詳細的診斷資訊。能作為MicrosoftVisual C++的一個插件運作。

11.Compuware DevPartner for Visual C++ BoundsChecker Suite-為C++開發者設計的運作錯誤檢測和調試工具軟體。作為Microsoft Visual Studio和C++ 6.0的一個插件運作。

12.Electric Software GlowCode-包括記憶體洩漏檢查,code profiler,函數調用跟蹤等功能。給C++和.Net開發者提供完整的錯誤診斷,和運作時性能分析工具包。

13.Compuware DevPartner Java Edition-包含Java記憶體檢測,代碼覆寫率測試,代碼性能測試,線程死鎖,分布式應用等幾大功能子產品。

14.Quest JProbe-分析Java的記憶體洩漏。

15.ej-technologies JProfiler-一個全功能的Java剖析工具,專用于分析J2SE和J2EE應用程式。它把CPU、執行緒和記憶體的剖析組合在一個強大的應用中。

16.BEAJRockit-用來診斷Java記憶體洩漏并指出根本原因,專門針對Intel平台并得到優化,能在Intel硬體上獲得最高的性能。

參考資料

一種記憶體洩漏檢測技術的研究和實作.