歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> linux中內存洩漏的檢測(五)記錄內存洩漏的代碼

linux中內存洩漏的檢測(五)記錄內存洩漏的代碼

日期:2017/3/1 12:17:44   编辑:關於Linux

到目前為止,先後通過wrap malloc、new函數重載和計算指針內存大小的方法,基本上滿足了對內存洩漏檢測的需要。

如果發現了內存洩漏,那麼就要找到內存洩漏的地方並且修正它了。

茫茫代碼,如何去找?如果能根據未釋放的內存找到申請它的地方就好了。

我們今天就是要做這個事情。

想要根據內存地址查出申請者的信息,那麼在一開始申請的時候就要建立地址與申請者之間的映射。

1.內存地址

內存地址,是一個unsigned long型的數值,用void *來存儲也可以。為了避免類型轉換,我使用了void *

2.申請者信息

申請者的信息比較復雜,不是一個類型可以搞定的。它包括哪些內容呢?

在C情況下,主要是需要知道誰調用了__wrap_malloc。但在C++情況下,調用__wrap_malloc的一定是new,這沒有什麼意義,還需要知道是誰調用了new。再進一步說,new有可能是在構造函數中被調用的,那麼很有可能我們真正需要知道的是誰調用了構造函數。

由此可見,僅僅知道是誰調用了__wrap_malloc不夠的,我們需要的是整個棧信息。

整個棧包含了很多內容,在這裡,我們只記錄棧的深度(int)和每一層的符號名(char **)。符號名在整個程序中是唯一的(不管C還是C++)且相對位置是確定的(動態庫除外),當程序結束時再根據符號名反推出調用者的文件名和行號。

為什麼不直接獲取文件名和行號?
因為求符號名的實現比較簡單。

3.映射方式

說到映射,首先想到的是map、hash這樣的東西。

但需要說明的是,這裡是__wrap_malloc函數,是每次程序動態分配空間時必然會走到的地方。

這有什麼關系呢?想象一下,在由於某個動態申請內存的操作來到了這個函數,而在這個函數裡又不小心申請了一次內存,會怎樣呢?在-Wl,--wrap,malloc的作用下又來到了這裡,於是開啟了“雞生蛋、蛋生雞”的死循環中,直到——stack overflow。

所以,在這個函數裡能使用的,只能使用棧空間或者全局空間,如果一定要使用堆空間,也必須顯示地使用__real_malloc代替new或者malloc。由於在map、hash中會不可避免地使用動態內存空間的情況,還是放棄吧。

怎麼辦呢?為了避免節外生枝,我這裡使用了最簡單但是有點笨的方法——數組。

struct memory_record
{
    void * addr;
    size_t count;
    int depth;
    char **symbols;
}mc[1000];

4.怎樣獲取棧中的符號?

gcc給我們提相應的函數,按照要求調用就行。

char* stack[20] = {0};
mc[i].depth = backtrace(reinterpret_cast(stack), sizeof(stack)/sizeof(stack[0])); 
if (mc[i].depth){ 
    mc[i].symbols = backtrace_symbols(reinterpret_cast(stack), mc[i].depth); 
}

backtrace函數用於獲取棧的深度(depth),以及每一層棧地址(stack)。
backtrace_symbols函數根據棧地址返回符號名(symbols)。
需要注意的是,backtrace_symbols返回的是符號的數組,這個數組的空間是由backtrace_symbols分配的,但需要調用者釋放。

為什麼這裡backtrace_symbols分配了內存卻沒有引起stack overflow呢?以下是我的猜測:
backtrace_symbols函數和wrap機制都是GNU提供的,屬性親戚關系。既然是親戚,那麼大家通融一下,讓backtrace_symbols繞過wrap機制直接使用內存也是有可能的。

源代碼:

#include 
using namespace std;

#include "string.h"
#include 
#include 
#include 

#if(defined(_X86_) && !defined(__x86_64))
#define _ALLOCA_S_MARKER_SIZE 4
#elif defined(__ia64__) || defined(__x86_64)
#define _ALLOCA_S_MARKER_SIZE 8
#endif

size_t count = 0;

int backtrace(void **buffer, int size);

struct memory_record
{
    void * addr;
    size_t count;
    int depth;
    char **symbols;
}mc[1000];

extern "C"
{
void* __real_malloc(int c); 
void * __wrap_malloc(size_t size)
{
    void *p =  __real_malloc(size);
    size_t w = *((size_t*)((char*)p -  _ALLOCA_S_MARKER_SIZE));
    cout<<"malloc "<(stack), sizeof(stack)/sizeof(stack[0])); 
            if (mc[i].depth){ 
                mc[i].symbols = backtrace_symbols(reinterpret_cast(stack), mc[i].depth); 
            } 
            break;
        }
    }
    return p;
}

void __real_free(void *ptr);
void __wrap_free(void *ptr)
{
    cout<<"free "<

編譯命令:

g++ -o test test.cpp -g -Wl,--wrap,malloc -Wl,--wrap,free

運行:

./test | grep "===" | cut -d"[" -f3 | tr -d "]" | addr2line -e test

方法分析:

優點:

(1)在程序運行結束時,打印程序內存洩漏情況以及導致洩漏發生的代碼所在的文件及行號

(2)C/C++都適用

(3)需要修改產品源代碼即可實現功能

(4)對一起鏈接的所有.o和靜態庫都有效

缺點:

(1)對動態庫不適用

(2)求堆棧信息和求文件名行號是兩個操作,不能一次性解決問題

Copyright © Linux教程網 All Rights Reserved