解決Solaris應用程序開發內存泄漏問題 (1)

作者: 李凌雲,張一峯(laoeyu)

概述

內存泄漏是應用軟件開發過程中經常會遇到的問題,應用長期內存泄漏會佔用大量操作系統內存資源,直接導致應用程序運行不穩定,嚴重時甚至還會影響到操作系統的正常運行。爲了找到應用程序內存泄漏點,許多開發人員不得不在上千行乃至幾十萬行源程序中加入更多的調試代碼,試圖從調試信息中找到內存泄漏的根源,但通常來講這種方法是事倍功半的。幸運的是,Solaris平臺提供了好幾個實用的工具,能夠輔助開發人員對內存泄漏根源進行定位。筆者參考了 Sun公司官方網站上相關的英文技術文檔,並認爲這些對於我們中國廣大的Sun技術愛好者有很好的指導作用。筆者經過消化整理後寫下此文希望與大家共享。在下面的章節 中將初步講述如何在Solaris 10操作系統下利用這些工具查找用戶程序的內存泄漏點。文章各節標題如下:

1. 概述
2. 內存泄漏及危害
3. dbx
4. libumem
5. DTrace
6. libgc
7.
總結
8.
參考資料


內存泄漏及危害

如果要給內存泄漏下個定義的話,它應該屬於軟件程序設計的一種缺陷,該缺陷直接導致了程序在運行過程中無法釋放不再需要的內存空間,從而造成內存資源浪費。具體來說,當用戶程序在運行過程中需要動態獲得內存時,操作系統總是從堆(heap)上分配相應的空間給應用,分配的結果是將該堆內存的起始地址通過指針返回給應用。正常情況下,應用使用完這塊內存後,應通過系統調用主動通知操作系統回收這些堆內存以便重用。但是,如果由於設計缺陷導致在某些情況下程序沒有主動地通知到操作系統,而後應用又失去了對這塊內存的引用時,則該堆內存塊將成爲既不受程序控制,又不能被系統回收重用的“孤兒”內存,這便是我們所指的內存泄漏。

造成內存泄漏的設計缺陷多種多樣,下面例舉了部分典型的內存泄漏設計缺陷,它們都是開發人員經常會犯的毛病。

一個原先運行正常的系統由於安裝運行了用戶應用程序後經常發生以下任意症狀時,則應用程序有可能存在內存泄漏問題:

  • 沒有特殊原因,應用程序經長時間運行後所佔虛擬內存總量仍在持續性地不斷增長(即使增長過程十分緩慢)。通過使用prstat可以查詢進程內存使用情況。

  • swap設備配置和運行正常,且沒有大文件佔用/tmp目錄的情況下,經常有進程報告“Out of Memory”錯誤。通過使用swap -s命令可以查詢swap空間使用情況,以及使用df -k查詢/tmp的使用情況。

一個有內存泄漏問題的應用程序經過長時間運行後,通常會逐漸佔用大量操作系統內存。操作系統會因內存短缺而造成整體性能下降,嚴重時可以造成系統中其他真正需要內存的進程因得不到內存空間而無法正常運行,操作系統也會由於內存耗盡而變得不穩定。在下面的章節中,我們將介紹在Solaris 10平臺上有哪些工具幫助我們分析內存泄漏問題。

dbx

Sun StudioSun公司推出的面向CC++Fortran語言編程的開發環境,目前最新版本是Sun Studio 11。它包括一個非常友好而專業的GUI集成開發環境,以及像dbxPerformance Analyzer等優秀的輔助工具。其中,dbx工具除了可以幫助開發人員進行源代碼級別的跟蹤調試以外,它還可以幫助開發人員查找和定位應用程序中內存泄漏的問題。令人興奮的是,Sun Studio 11不像以前各版本那樣需要購買License,它是免費下載和使用的(包括商用),有興趣的話可以到http://developers.sun.com/prodtech/cc/downloads/index.jsp下載。

Runtime Checking

dbx中提供內存泄漏檢查功能的模塊被稱爲RTC,即Runtime Checking。它除了提供內存泄漏檢查功能外,還可以進行內存訪問檢查和使用檢查,對於發現程序中內存越界訪問或者變量未初始化就訪問等編程問題很有幫助。缺省情況下,dbx不啓用內存泄漏檢查功能,用戶可以通過check -leaks命令啓用它。check -leaks是一個反覆切換開關,在已經啓用內存泄漏檢查功能的情況下再使用該命令可以關閉這個功能。使用內存泄漏檢查功能不需要對程序進行重編譯,它也支持在優化過的目標代碼中查找內存泄漏。

使用RTC模塊有一些前提要求,比如:

  • 程序必須是由Sun提供的編譯器所編譯生成的;

  • 程序使用動態庫方式鏈接libc庫;

  • 內存是通過libc庫的malloc()free()realloc()或其他基於這些調用的函數進行申請和管理的;

  • 程序不能被完全strip,即符號表必須存在。strip -x命令仍可以接受。

RTC還有部分限制,比如:

  • 只能在Solaris操作系統上使用;

  • 在基於非UltraSPARC芯片的主機系統上使用時,程序的text段和數據data段不能超過8MB空間。

RTC對於內存泄漏分三種情況:

  • Memory Leak(mel),即進程中不存在任何一個指針指向某內存塊,則該內存塊爲真正的內存泄漏塊。

  • Address in Block(aib),即進程中不存在任何一個指針指向某內存塊的啓始位置,卻存在指向該內存塊中間某位置的指針。這是一個可疑的內存泄漏,即它很可能會演變爲Memroy Leak,但也不排除程序設計者爲了某種需要故意設計成這樣的。
  • Address in Register(air),即進程代碼段及數據段中沒有任何一個指針指向該內存塊,但在至少一個寄存器中存在相關的指針。這是一個可疑的內存泄漏。如果程序在編譯時使用了優化選項,比如-O等,則編譯器有可能只將內存指針保留於寄存器中,否則這會演變爲真正的內存泄漏。
dbx在報告內存泄漏時會區分上述三種情況,對於可能的內存泄漏,開發人員需要自行判斷是否爲真正的內存泄漏。

用dbx查內存泄漏

使用dbx檢查內存泄漏是所有工具中最方便的。如果程序在編譯時使用了-g選項,dbx可以很方便地將內存泄漏點定位到源程序代碼行。使用dbx檢查內存泄漏的典型過程如下:

1. 使用dbx啓動被跟蹤的程序。

2. 用check -leaks打開內存泄漏檢查開關。

3. 運行程序直至結束。當程序運行結束時,dbx會給出類似以下的內存泄漏報告。


例子中報告了兩個內存泄漏,分別爲從main()過程調用到memory_leak()過程時有1次32字節的內存泄漏,以及從main()過程調用到address_in_register()過程時有1 次11字節的內存泄漏。爲了得到具體的源代碼行號,可以在運行程序前使用以下的命令將內存泄漏報告模式改爲verbose,然後重新運行程序。


例子中報告了內存泄漏點分別在源代碼第8行和第35行。

如果要檢查一個守護進程類型的服務程序是否發生內存泄漏,上述方法就不適用了,這是因爲守護進程永遠不會運行結束。對此,dbx提供了一個showleaks的命令可以讓開發人員在任何時候查看進程內存泄漏情況。另外,守護進程一般會多次進行fork(),所以也不適合採用dbx直接進行啓動。因此,對於守護進程類程序,開發人員可以通過以下方法啓動,然後dbx動態掛接到已運行的進程上再進行內存泄漏檢查。

1. 設定環境變量,預裝librtc.so

缺省情況下在程序啓動時librtc.so不會預裝入系統。這意味着即使後來dbx動態掛接上該進程後,也無法使用RTC功能。但開發人員可以通過設定LD_AUDIT環境變量,指定應用程序啓動時系統預裝入librtc.so。方法是:對於32位應用,將LD_AUDIT指向<Sun Studio 11安裝目錄>/lib/下的rtcaudit.so,對於SPARC 64位應用,須指向lib/v9下的rtcaudit.so,對於AMD 64位應用,則爲lib/amd64/下rtcaudit.so

2. 啓動守護程序,並得到進程號。注意,啓動應用後應及時使用unset命令去除LD_AUDIT設置。後繼命令不應使用LD_AUDIT

3. 令dbx動態掛接上守護進程,關閉同步跟蹤,並打開內存泄漏檢查。可根據需要在程序合適的位置設好斷點,然後繼續執行程序或單步跟蹤執行程序。在必要的時候利用showleaks檢查內存泄漏情況。

其中,showleaks命令缺省只報告自上次報告內存泄漏後新發現的內存泄漏。如果使用-a選項,則showleaks報告所有內存泄漏。-v選項是指verbose模式,可以給出更詳細的報告。跟蹤完成後,可使用quit命令退出dbx

libumem

libumem的由來是原自SunOS 5.4中的Kernel Slab Allocator。爲了加速系統虛擬內存操作,Sun的工程師發明了Kernel Slab Allocator,後來也被推廣到Linux操作系統。Kernel Slab Allocator通過一種object cacheing技術策略來實現高效內存的處理。在實際使用中,這種Kernel Slab Allocator內存分配器被證明非常高效,在多顆CPU上,擴展性(Scability)表現亦極佳。由於Kernel Slab Allocator是工作在kernel狀態,所以相應地產生了一個在用戶空間工作的內存分配器: libumem。自Solaris 9update3)開始,Solaris就自帶這個全新的內存分配器: libumem

libumem不僅能夠優化程序的內存分配,而且還提供內存分配調試,記錄功能,配合mdb工具我們可以輕鬆觀察程序內存的分配情況和內存泄漏。在使用libumem檢測內存泄漏的問題的之前,我們必須瞭解一些在調試中libumem提供給我們信息的內存結構。

libumem工作內存結構

libumem也是使用Slab概念。SlabSlab Allocator中一個基本內存單元:Slab是代表一個或者多個虛擬內存中的頁(Page),它通常會被分割成爲多個大小等同的Chunks,被成爲BufferBuffer含有用戶所使用的數據,還會有一些額外的信息,不過這個取決環境變量的設置。這些額外的信息對我們調試,檢測內存泄漏非常有用。下面就是Buffer的一個基本結構:

Buffer結構中第一個sectionMetadata,主要提供內存分配的長度信息,我們這裏不使用,在32位程序應用中它是8個字節。Metadata後面是存儲用戶數據的User data Section。接着是Redzone部分,Redzone也是8個字節。最後是Debug Metadata也是8個字節。其中前四個字節代表一個指針,指向一個umem_bufctl_audit結構,這個結構記錄着內存分配時候的堆棧。該結構的定義可以在/usr/include/umem_impl.h找到。後面四個字節是校驗位,可以用來和前面字節一起來判斷這個buffer有沒有被破壞。

libumem使用方法

1. 預加載(Preload) libumem

如果在程序中需要調試,尋找內存泄漏,需要預先加載(Preload) libumem,並且設置上環境變量:UMEM_DEBUG=defaultUMEM_LOGGING=transactionLD_PRELOAD=libumem.so.1

csh中設置的例子

bash中設置的例子

2. 運行程序,在程序運行時使用gcore命令對目標程序的進程生成core文件。

3. 使用mdb命令倒入core文件。

  • ::umem_status命令查看libumem的日誌功能是否打開。

  • ::findleaks命令查看是否有內存泄漏。

  • 內存泄漏地址$<bufctl_audit,該命令會將該地址的內容以umem_bufctl_audit的結構,並且會顯示內存泄漏的時候的用戶堆棧。

  • ::umalog命令查看每次內存分配的時間,地址,堆棧。

  • 內存地址::umem_verify可以查看內存是否被破壞,比如內存的越界操作。

  • 內存地址::umem_log可以按CPU,線程打印出內存分配記錄。

發佈了47 篇原創文章 · 獲贊 0 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章