應用 Valgrind 發現 Linux 程式的記憶體問題(轉)

來源:互聯網
上載者:User

標籤:

Valgrind 概述體繫結構

Valgrind 是一套Linux下,開放原始碼(GPL V2)的模擬調試工具的集合。Valgrind由核心(core)以及基於核心的其他調試工具組成。核心類似於一個架構(framework),它類比了 一個CPU環境,並提供服務給其他工具;而其他工具則類似於外掛程式 (plug-in),利用核心提供的服務完成各種特定的記憶體調試任務。Valgrind的體繫結構如所示:

圖 1 Valgrind 體繫結構

Valgrind包括如下一些工具:

  1. Memcheck。這是valgrind應用最廣泛的工具,一個重量級的記憶體檢查器,能夠發現開發中絕大多數記憶體錯誤使用方式,比如:使用未初始化的記憶體,使用已經釋放了的記憶體,記憶體訪問越界等。這也是本文將重點介紹的部分。
  2. Callgrind。它主要用來檢查程式中函數調用過程中出現的問題。
  3. Cachegrind。它主要用來檢查程式中緩衝使用出現的問題。
  4. Helgrind。它主要用來檢查多線程程式中出現的競爭問題。
  5. Massif。它主要用來檢查程式中堆棧使用中出現的問題。
  6. Extension。可以利用core提供的功能,自己編寫特定的記憶體調試工具。
Linux 程式記憶體空間布局

要發現Linux下的記憶體問題,首先一定要知道在Linux下,記憶體是如何被分配的?展示了一個典型的Linux C程式記憶體空間布局:

圖 2: 典型記憶體空間布局

一個典型的Linux C程式記憶體空間由如下幾部分組成:

  • 程式碼片段(.text)。這裡存放的是CPU要執行的指令。程式碼片段是可共用的,相同的代碼在記憶體中只會有一個拷貝,同時這個段是唯讀,防止程式由於錯誤而修改自身的指令。
  • 初始化資料區段(.data)。這裡存放的是程式中需要明確賦初始值的變數,例如位於所有函數之外的全域變數:int val=100。需要強調的是,以上兩段都是位於程式的可執行檔中,核心在調用exec函數啟動該程式時從來源程式檔案中讀入。
  • 未初始化資料區段(.bss)。位於這一段中的資料,核心在執行該程式前,將其初始化為0或者null。例如出現在任何函數之外的全域變數:int sum;
  • 堆(Heap)。這個段用於在程式中進行動態記憶體申請,例如經常用到的malloc,new系列函數就是從這個段中申請記憶體。
  • 棧(Stack)。函數中的局部變數以及在函數調用過程中產生的臨時變數都儲存在此段中。
記憶體檢查原理

Memcheck檢測記憶體問題的原理如所示:

圖 3 記憶體檢查原理

Memcheck 能夠檢測出記憶體問題,關鍵在於其建立了兩個全域表。

  1. Valid-Value 表:

對於進程的整個地址空間中的每一個位元組(byte),都有與之對應的 8 個 bits;對於 CPU 的每個寄存器,也有一個與之對應的 bit 向量。這些 bits 負責記錄該位元組或者寄存器值是否具有有效、已初始化的值。

  1. Valid-Address

對於進程整個地址空間中的每一個位元組(byte),還有與之對應的 1 個 bit,負責記錄該地址是否能夠被讀寫。

檢測原理:

  • 當要讀寫記憶體中某個位元組時,首先檢查這個位元組對應的 A bit。如果該A bit顯示該位置是無效位置,memcheck 則報告讀寫錯誤。
  • 核心(core)類似於一個虛擬 CPU 環境,這樣當記憶體中的某個位元組被載入到真實的 CPU 中時,該位元組對應的 V bit 也被載入到虛擬 CPU 環境中。一旦寄存器中的值,被用來產生記憶體位址,或者該值能夠影響程式輸出,則 memcheck 會檢查對應的V bits,如果該值尚未初始化,則會報告使用未初始化記憶體錯誤。

回頁首

Valgrind 使用

第一步:準備好程式

為了使valgrind發現的錯誤更精確,如能夠定位到原始碼行,建議在編譯時間加上-g參數,編譯最佳化選項請選擇O0,雖然這會降低程式的執行效率。

這裡用到的樣本程式檔案名稱為:sample.c(如下所示),選用的編譯器為gcc。

產生可執行程式 gcc –g –O0 sample.c –o sample

清單 1

第二步:在valgrind下,運行可執行程式。

利用valgrind調試記憶體問題,不需要重新編譯來源程式,它的輸入就是二進位的可執行程式。調用Valgrind的通用格式是:valgrind [valgrind-options] your-prog [your-prog-options]

Valgrind 的參數分為兩類,一類是 core 的參數,它對所有的工具都適用;另外一類就是具體某個工具如 memcheck 的參數。Valgrind 預設的工具就是 memcheck,也可以通過“--tool=tool name”指定其他的工具。Valgrind 提供了大量的參數滿足你特定的調試需求,具體可參考其使用者手冊。

這個例子將使用 memcheck,於是可以輸入命令入下:valgrind <Path>/sample.

第三步:分析 valgrind 的輸出資訊。

以下是運行上述命令後的輸出。

清單 2

  • 左邊顯示類似行號的數字(32372)表示的是 Process ID。
  • 最上面的紅色方框表示的是 valgrind 的版本資訊。
  • 中間的紅色方框表示 valgrind 通過運行被測試程式,發現的記憶體問題。通過閱讀這些資訊,可以發現:
    1. 這是一個對記憶體的非法寫操作,非法寫操作的記憶體是4 bytes。
    2. 發生錯誤時的函數堆棧,以及具體的原始碼行號。
    3. 非法寫操作的具體地址空間。
  • 最下面的紅色方框是對發現的記憶體問題和記憶體泄露問題的總結。記憶體泄露的大小(40 bytes)也能夠被檢測出來。

樣本程式顯然有兩個問題,一是fun函數中動態申請的堆記憶體沒有釋放;二是對堆記憶體的訪問越界。這兩個問題均被valgrind發現。

回頁首

利用Memcheck發現常見的記憶體問題

在Linux平台開發應用程式時,最常遇見的問題就是錯誤的使用記憶體,我們總結了常見了記憶體錯誤使用方式,並說明了如何用valgrind將其檢測出來。

使用未初始化的記憶體

問題分析:

對於位於程式中不同段的變數,其初始值是不同的,全域變數和靜態變數初始值為0,而局部變數和動態申請的變數,其初始值為隨機值。如果程式使用了為隨機值的變數,那麼程式的行為就變得不可預期。

下面的程式就是一種常見的,使用了未初始化的變數的情況。數組a是局部變數,其初始值為隨機值,而在初始化時並沒有給其所有數群組成員初始化,如此在接下來使用這個數組時就潛在有記憶體問題。

清單 3

結果分析:

假設這個檔案名稱為:badloop.c,產生的可執行程式為badloop。用memcheck對其進行測試,輸出如下。

清單 4

輸出結果顯示,在該程式第11行中,程式的跳轉依賴於一個未初始化的變數。準確的發現了上述程式中存在的問題。

記憶體讀寫越界

問題分析:

這 種情況是指:訪問了你不應該/沒有許可權訪問的記憶體位址空間,比如訪問數組時越界;對動態記憶體訪問時超出了申請的記憶體大小範圍。下面的程式就是一個典型的數 組越界問題。pt是一個局部陣列變數,其大小為4,p初始指向pt數組的起始地址,但在對p迴圈疊加後,p超出了pt數組的範圍,如果此時再對p進行寫操 作,那麼後果將不可預期。

清單 5

結果分析:

假設這個檔案名稱為badacc.cpp,產生的可執行程式為badacc,用memcheck對其進行測試,輸出如下。

清單 6

輸出結果顯示,在該程式的第15行,進行了非法的寫操作;在第16行,進行了非法讀操作。準確地發現了上述問題。

記憶體覆蓋

問題分析:

C 語言的強大和可怕之處在於其可以直接操作記憶體,C 標準庫中提供了大量這樣的函數,比如 strcpy, strncpy, memcpy, strcat 等,這些函數有一個共同的特點就是需要設定源地址 (src),和目標地址(dst),src 和 dst 指向的地址不能發生重疊,否則結果將不可預期。

下面就是一個 src 和 dst 發生重疊的例子。在 15 與 17 行中,src 和 dst 所指向的地址相差 20,但指定的拷貝長度卻是 21,這樣就會把之前的拷貝值覆蓋。第 24 行程式類似,src(x+20) 與 dst(x) 所指向的地址相差 20,但 dst 的長度卻為 21,這樣也會發生記憶體覆蓋。

清單 7

結果分析:

假設這個檔案名稱為 badlap.cpp,產生的可執行程式為 badlap,用 memcheck 對其進行測試,輸出如下。

清單 8

輸出結果顯示上述程式中第15,17,24行,源地址和目標地址設定出現重疊。準確的發現了上述問題。

動態記憶體管理錯誤

問題分析:

常 見的記憶體配置方式分三種:靜態儲存,棧上分配,堆上分配。全域變數屬於靜態儲存,它們是在編譯時間就被分配了儲存空間,函數內的局部變數屬於棧上分配,而最 靈活的記憶體使用量方式當屬堆上分配,也叫做記憶體動態分配了。常用的記憶體動態分配函數包括:malloc, alloc, realloc, new等,動態釋放函數包括free, delete。

一旦成功申請了動態記憶體,我們就需要自己對其進行記憶體管理,而這又是最容易犯錯誤的。下面的一段程式,就包括了記憶體動態管理中常見的錯誤。

清單 9

常見的記憶體動態管理錯誤包括:

  • 申請和釋放不一致

由於 C++ 相容 C,而 C 與 C++ 的記憶體申請和釋放函數是不同的,因此在 C++ 程式中,就有兩套動態記憶體管理函數。一條不變的規則就是採用 C 方式申請的記憶體就用 C 方式釋放;用 C++ 方式申請的記憶體,用 C++ 方式釋放。也就是用 malloc/alloc/realloc 方式申請的記憶體,用 free 釋放;用 new 方式申請的記憶體用 delete 釋放。在上述程式中,用 malloc 方式申請了記憶體卻用 delete 來釋放,雖然這在很多情況下不會有問題,但這絕對是潛在的問題。

  • 申請和釋放不匹配

申請了多少記憶體,在使用完成後就要釋放多少。如果沒有釋放,或者少釋放了就是記憶體泄露;多釋放了也會產生問題。上述程式中,指標p和pt指向的是同一塊記憶體,卻被先後釋放兩次。

  • 釋放後仍然讀寫

本質上說,系統會在堆上維護一個動態記憶體鏈表,如果被釋放,就意味著該塊記憶體可以繼續被分配給其他部分,如果記憶體被釋放後再訪問,就可能覆蓋其他部分的資訊,這是一種嚴重的錯誤,上述程式第16行中就在釋放後仍然寫這塊記憶體。

結果分析:

假設這個檔案名稱為badmac.cpp,產生的可執行程式為badmac,用memcheck對其進行測試,輸出如下。

清單 10

輸出結果顯示,第14行分配和釋放函數不一致;第16行發生非法寫操作,也就是往釋放後的記憶體位址寫值;第17行釋放記憶體函數無效。準確地發現了上述三個問題。

記憶體泄露

問題描述:

內 存泄露(Memory leak)指的是,在程式中動態申請的記憶體,在使用完後既沒有釋放,又無法被程式的其他部分訪問。記憶體泄露是在開發大型程式中最令人頭疼的問題,以至於有 人說,記憶體泄露是無法避免的。其實不然,防止記憶體泄露要從良好的編程習慣做起,另外重要的一點就是要加強單元測試(Unit Test),而memcheck就是這樣一款優秀的工具。

下面是一個比較典型的記憶體泄露案例。main函數調用了mk函數產生樹結點,可是在調用完成之後,卻沒有相應的函數:nodefr釋放記憶體,這樣記憶體中的這個樹結構就無法被其他部分訪問,造成了記憶體泄露。

在 一個單獨的函數中,每個人的記憶體泄露意識都是比較強的。但很多情況下,我們都會對malloc/free 或new/delete做一些封裝,以符合我們特定的需要,無法做到在一個函數中既使用又釋放。這個例子也說明了記憶體泄露最容易發生的地方:即兩個部分的 介面部分,一個函數申請記憶體,一個函數釋放記憶體。並且這些函數由不同的人開發、使用,這樣造成記憶體泄露的可能性就比較大了。這需要養成良好的單元測試習 慣,將記憶體泄露消滅在初始階段。

清單 11

清單 11.2

清單 11.3

結果分析:

假設上述檔案名稱位tree.h, tree.cpp, badleak.cpp,產生的可執行程式為badleak,用memcheck對其進行測試,輸出如下。

清單 12

該 樣本程式是產生一棵樹的過程,每個樹節點的大小為12(考慮記憶體對齊),共8個節點。從上述輸出可以看出,所有的記憶體泄露都被發現。Memcheck將內 存泄露分為兩種,一種是可能的記憶體泄露(Possibly lost),另外一種是確定的記憶體泄露(Definitely lost)。Possibly lost 是指仍然存在某個指標能夠訪問某塊記憶體,但該指標指向的已經不是該記憶體首地址。Definitely lost 是指已經不能夠訪問這塊記憶體。而Definitely lost又分為兩種:直接的(direct)和間接的(indirect)。直接和間接的區別就是,直接是沒有任何指標指向該記憶體,間接是指指向該記憶體的 指標都位於記憶體泄露處。在上述的例子中,根節點是directly lost,而其他節點是indirectly lost。

回頁首

總結

本 文介紹了valgrind的體繫結構,並重點介紹了其應用最廣泛的工具:memcheck。闡述了memcheck發現記憶體問題的基本原理,基本使用方 法,以及利用memcheck如何發現目前開發中最廣泛的五大類記憶體問題。在項目中儘早的發現記憶體問題,能夠極大地提高開發效率,valgrind就是能 夠協助你實現這一目標的出色工具。

應用 Valgrind 發現 Linux 程式的記憶體問題(轉)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.