0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何寫(xiě)一個(gè)內(nèi)存泄漏檢測(cè)工具

科技綠洲 ? 來(lái)源:Linux開(kāi)發(fā)架構(gòu)之路 ? 作者:Linux開(kāi)發(fā)架構(gòu)之路 ? 2023-11-11 16:19 ? 次閱讀

如何確定有內(nèi)存泄露問(wèn)題,如何定位到內(nèi)存泄露位置,如何寫(xiě)一個(gè)內(nèi)存泄漏檢測(cè)工具?

1:概述

內(nèi)存泄露本質(zhì):其實(shí)就是申請(qǐng)調(diào)用malloc/new,但是釋放調(diào)用free/delete有遺漏,或者重復(fù)釋放的問(wèn)題。

內(nèi)存泄露會(huì)導(dǎo)致的現(xiàn)象:作為一個(gè)服務(wù)器,長(zhǎng)時(shí)間運(yùn)行,內(nèi)存泄露會(huì)導(dǎo)致進(jìn)程虛擬內(nèi)存被占用完,導(dǎo)致進(jìn)程崩潰吧。(堆上分配的內(nèi)存)

如何規(guī)避或者發(fā)現(xiàn)內(nèi)存泄露呢?

===》1:如何檢測(cè)有內(nèi)存泄露?(除了內(nèi)存監(jiān)控工具h(yuǎn)top,耗時(shí),效果不明顯)

===》2:如何定位內(nèi)存泄露的代碼問(wèn)題(少量代碼可以閱讀代碼排除,線上版本呢?)

=====》引入gc

=====》少量代碼可以通過(guò)排查代碼進(jìn)行定位

=====》已經(jīng)確定代碼有內(nèi)存泄露,可以用過(guò)valgrind/mtrace等市場(chǎng)上已有的一些工具

=====》本質(zhì)是malloc和free的次數(shù)不一致導(dǎo)致,我們通過(guò)hook的方式,對(duì)malloc和free次數(shù)進(jìn)行統(tǒng)計(jì)

2:通過(guò)hook的方式檢測(cè),定位內(nèi)存泄露(四種方法)

在生產(chǎn)環(huán)境重定位內(nèi)存泄露的問(wèn)題,我們可以在產(chǎn)品中增加這些定位手段,通過(guò)配置文件開(kāi)關(guān)控制其打開(kāi),方便內(nèi)存泄露定位。

幾種不同的方式本質(zhì):都是對(duì)malloc和free進(jìn)行hook,增加一些處理進(jìn)行檢測(cè)。

2.1:測(cè)試代碼描述內(nèi)存泄露

如下代碼,從代碼看,明顯可以看到是有內(nèi)存泄露的,但是如果看不到代碼,或者代碼量過(guò)多,從運(yùn)行現(xiàn)象上我們就很難發(fā)現(xiàn)了。

#include < stdio.h >
#include < stdlib.h >

int main()
{
    void * ptr1 = malloc(10);
    void * ptr2 = malloc(20);

    free(ptr1);

    void * ptr3 = malloc(30);
    free(ptr3);
    return 0;
}

//代碼運(yùn)行是沒(méi)有問(wèn)題,也沒(méi)有報(bào)錯(cuò)的,但是明顯可以看到ptr2是沒(méi)有內(nèi)存釋放的,如果是服務(wù)器有這種代碼,長(zhǎng)時(shí)間運(yùn)行會(huì)有嚴(yán)重問(wèn)題的。

2.2:通過(guò)dlsym庫(kù)函數(shù)對(duì)malloc/free進(jìn)行hook

我在 Linux/unix系統(tǒng)編程手冊(cè) 這本書(shū)中了解相關(guān)dlsym函數(shù)的使用

要想知道有內(nèi)存泄露,或者直接定位內(nèi)存泄露的代碼位置,本質(zhì)還是對(duì)調(diào)用的malloc/free進(jìn)行hook, 對(duì)調(diào)用malloc/free分別增加監(jiān)控來(lái)分析。

使用dlsym庫(kù)函數(shù),獲取malloc/free函數(shù)的地址,通過(guò)RTLD_NEXT進(jìn)行比標(biāo)記(這個(gè)標(biāo)記適用于在其他地方定義的函數(shù)同名的包裝函數(shù),如在主程序中定義的malloc,代替系統(tǒng)的malloc),實(shí)現(xiàn)用我們主程序中malloc代替系統(tǒng)調(diào)用malloc.

2.2.1:第一版試著

在調(diào)用malloc和free前,使用dlsym函數(shù)和RTLD_NEXT標(biāo)記,獲取系統(tǒng)庫(kù)malloc/free地址,以及用本地定義的malloc/free代替系統(tǒng)調(diào)用。

//1:使用void * dlsym(void* handle, char* symbool)函數(shù)和handle為RTLD_NEXT標(biāo)記,對(duì)malloc/free進(jìn)行hook
//2:RTLD_NEXT標(biāo)記 需要在本地實(shí)現(xiàn) symbool同名函數(shù)達(dá)到hook功能,即這里要實(shí)現(xiàn)malloc/free功能
//3:dlsym()返回的是symbool 參數(shù)對(duì)應(yīng)的函數(shù)的地址,在同名函數(shù)中用該地址實(shí)現(xiàn)真正的調(diào)用

//RTLD_NEXT 是dlsym() 庫(kù)中的偽句柄,定義_GNU_SOURCE宏才能識(shí)別
//可以通過(guò) man dlsym
//測(cè)試發(fā)現(xiàn) :必須放在最頂部,不然編譯報(bào) RTLD_NEXT沒(méi)有定義 
#define _GNU_SOURCE
#include < dlfcn.h >  //對(duì)應(yīng)的頭文件

//第一步功能,確定hook成功,先在我們的hook函數(shù)中增加一些打印信息驗(yàn)證
#include < stdio.h >
#include < stdlib.h >

//定義相關(guān)全局變量,獲取返回的函數(shù)地址,進(jìn)行實(shí)際調(diào)用
typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;

typedef void (*free_t)(void* p);
free_t free_f = NULL;

//要hook的同名函數(shù)
void * malloc(size_t size){
    printf("exec malloc n");
    return malloc_f(size);
}

void free(void * p){
    printf("exec free n");
    free_f(p);
}
//通過(guò)dlsym 對(duì)malloc和free使用前進(jìn)行hook
static void init_malloc_free_hook(){
    //只需要執(zhí)行一次
    if(malloc_f == NULL){
        malloc_f = dlsym(RTLD_NEXT, "malloc"); //除了RTLD_NEXT 還有一個(gè)參數(shù)RTLD_DEFAULT
    }

    if(free_f == NULL)
    {
        free_f =  dlsym(RTLD_NEXT, "free");
    }
    return ;
}


int main()
{
    init_malloc_free_hook(); //執(zhí)行一次
    void * ptr1 = malloc(10);
    void * ptr2 = malloc(20);

    free(ptr1);

    void * ptr3 = malloc(30);
    free(ptr3);
    return 0;
}

上述代碼是有問(wèn)題的,現(xiàn)象及定位問(wèn)題:

hlp@ubuntu:~/mem_test$ gcc dlsym_hook.c -o dlsym_hook -ldl
hlp@ubuntu:~/mem_test$ ./dlsym_hook 
Segmentation fault (core dumped)


#使用gdb對(duì)問(wèn)題進(jìn)行定位 
hlp@ubuntu:~/mem_test$ gdb ./dlsym_hook 
(gdb) b 54    #加斷點(diǎn)
Breakpoint 1 at 0x400729: file dlsym_hook.c, line 54.
(gdb) b 28	  #加斷點(diǎn)
Breakpoint 2 at 0x400682: file dlsym_hook.c, line 28.
(gdb) r       #開(kāi)始運(yùn)行
Starting program: /home/hlp/mem_test/dlsym_hook 
Breakpoint 1, main () at dlsym_hook.c:54
54	    void * ptr1 = malloc(10);
(gdb) c		#單步執(zhí)行
Continuing.
Breakpoint 2, malloc (size=10) at dlsym_hook.c:28   #第一個(gè)mallocy已經(jīng)執(zhí)行
28	    printf("exec malloc n");	
(gdb) c
Continuing.
Breakpoint 2, malloc (size=1024) at dlsym_hook.c:28  #這里的1024不是我們代碼里面的,
28	    printf("exec malloc n");
(gdb) c
Continuing.

Breakpoint 2, malloc (size=1024) at dlsym_hook.c:28    #發(fā)現(xiàn)malloc 1024一直循環(huán)執(zhí)行 懷疑是printf中會(huì)調(diào)用malloc,
28	    printf("exec malloc n");
(gdb) c
Continuing.

Breakpoint 2, malloc (size=1024) at dlsym_hook.c:28
28	    printf("exec malloc n");
(gdb) 

#通過(guò)gdb進(jìn)行定位時(shí),可以確定,我們hook malloc函數(shù)內(nèi)部調(diào)用printf,printf底層其實(shí)是有調(diào)用malloc,從而printf內(nèi)部成為遞歸,一直調(diào)用了。
#所以我們需要規(guī)避這種現(xiàn)象,讓hook函數(shù)內(nèi)部其他業(yè)務(wù)只執(zhí)行一次,不要因?yàn)榈谌綆?kù)內(nèi)部機(jī)制導(dǎo)致類似問(wèn)題

增加特定標(biāo)識(shí),優(yōu)化上述代碼:

//使用標(biāo)識(shí),使hook的函數(shù)內(nèi)部只執(zhí)行一次,不因?yàn)榈谌綆?kù)原因?qū)е逻f歸現(xiàn)象
#define _GNU_SOURCE
#include < dlfcn.h >  //對(duì)應(yīng)的頭文件
#include < stdio.h >
#include < stdlib.h >

typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;
typedef void (*free_t)(void* p);
free_t free_f = NULL;

//定義一個(gè)hook函數(shù)的標(biāo)志 使內(nèi)部邏輯只執(zhí)行一次
int enable_malloc_hook = 1;
int enable_free_hook = 1;

//要hook的同名函數(shù)
void * malloc(size_t size){
    if(enable_malloc_hook) //對(duì)第三方調(diào)用導(dǎo)致的遞歸進(jìn)行規(guī)避
    {
        enable_malloc_hook = 0;
        printf("exec malloc n");
        enable_malloc_hook = 1;
    }
    return malloc_f(size);
}

void free(void * p){
    if(enable_free_hook){
        enable_free_hook = 0;
        printf("exec free n");
        enable_free_hook = 1;
    }
    free_f(p);
}

//通過(guò)dlsym 對(duì)malloc和free使用前進(jìn)行hook
static void init_malloc_free_hook(){
    //只需要執(zhí)行一次
    if(malloc_f == NULL){
        malloc_f = dlsym(RTLD_NEXT, "malloc"); //除了RTLD_NEXT 還有一個(gè)參數(shù)RTLD_DEFAULT
    }

    if(free_f == NULL)
    {
        free_f =  dlsym(RTLD_NEXT, "free");
    }
    return ;
}
int main()
{
    init_malloc_free_hook(); //執(zhí)行一次
    void * ptr1 = malloc(10);
    void * ptr2 = malloc(20);

    free(ptr1);

    void * ptr3 = malloc(30);
    free(ptr3);
    return 0;
}

上述代碼執(zhí)行成功,現(xiàn)象如下:

hlp@ubuntu:~/mem_test$ gcc dlsym_hook_ok.c -o dlsym_hook_ok -ldl -g
hlp@ubuntu:~/mem_test$ ./dlsym_hook_ok 
exec malloc 
exec malloc 
exec free 
exec malloc 
exec free
#對(duì)比執(zhí)行的 malloc和free次數(shù) 可以確定有內(nèi)存泄露

如何增加行號(hào)標(biāo)識(shí)呢?讓我們確定到代碼位置?

如何確定有內(nèi)存泄露呢?直接通過(guò)代碼,識(shí)別到malloc/free的對(duì)應(yīng)次數(shù),定位到有代碼問(wèn)題的位置。

2.2.2:能識(shí)別到行號(hào),以及有問(wèn)題代碼位置

//我們知道,一般可以通過(guò)__LINE__ 標(biāo)識(shí)日志當(dāng)前行號(hào)位置,但是這里不適用
//可以通過(guò) __builtin_return_address 獲取上級(jí)調(diào)用的退出的地址,可以設(shè)置時(shí)1級(jí),也可以設(shè)置是2級(jí)別...

// 增加打印調(diào)用malloc和free位置的信息。 這里打印地址  通過(guò)addr2line進(jìn)行地址和行號(hào)轉(zhuǎn)換
#define _GNU_SOURCE
#include < dlfcn.h >  //對(duì)應(yīng)的頭文件
#include < stdio.h >
#include < stdlib.h >

typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;
typedef void (*free_t)(void* p);
free_t free_f = NULL;

int enable_malloc_hook = 1;
int enable_free_hook = 1;

void * malloc(size_t size){
    if(enable_malloc_hook) //對(duì)第三方調(diào)用導(dǎo)致的遞歸進(jìn)行規(guī)避
    {
        enable_malloc_hook = 0;
        //打印上層調(diào)用的地址

        void *carrer = __builtin_return_address(0);
        printf("exec malloc [%p ]n", carrer );
        enable_malloc_hook = 1;
    }
    return malloc_f(size);
}

void free(void * p){
    if(enable_free_hook){
        enable_free_hook = 0;
        void *carrer = __builtin_return_address(0);
        printf("exec free [%p]n", carrer);
        enable_free_hook = 1;
    }
    free_f(p);
}

//通過(guò)dlsym 對(duì)malloc和free使用前進(jìn)行hook
static void init_malloc_free_hook(){
    //只需要執(zhí)行一次
    if(malloc_f == NULL){
        malloc_f = dlsym(RTLD_NEXT, "malloc"); //除了RTLD_NEXT 還有一個(gè)參數(shù)RTLD_DEFAULT
    }

    if(free_f == NULL)
    {
        free_f =  dlsym(RTLD_NEXT, "free");
    }
    return ;
}
int main()
{
    init_malloc_free_hook(); //執(zhí)行一次
    void * ptr1 = malloc(10);
    void * ptr2 = malloc(20);

    free(ptr1);

    void * ptr3 = malloc(30);
    free(ptr3);
    return 0;
}

執(zhí)行結(jié)果及查找對(duì)應(yīng)行數(shù):

#執(zhí)行結(jié)果如下
hlp@ubuntu:~/mem_test$ gcc dlsym_hook_addr.c -o dlsym_hook_addr -ldl
hlp@ubuntu:~/mem_test$ ./dlsym_hook_addr 
exec malloc [0x400797 ]
exec malloc [0x4007a5 ]
exec free [0x4007b5]
exec malloc [0x4007bf ]
exec free [0x4007cf]
#可以通過(guò)addr2line 獲取到對(duì)應(yīng)的代碼行數(shù) 編譯的時(shí)候要帶 -g
hlp@ubuntu:~/mem_test$ addr2line -fe ./dlsym_hook_addr -a 0x400797
0x0000000000400797
main
/home/hlp/mem_test/dlsym_hook_addr.c:57

2.2.3:通過(guò)策略,查找有問(wèn)題的代碼

從上文可以知道,我們通過(guò)對(duì)malloc和free的hook,可以獲得各自malloc和hook的次數(shù)。

以及我們可以通過(guò)__builtin_return_address 接口獲取到實(shí)際調(diào)用malloc/free的位置。

除此之外,malloc之間有所關(guān)聯(lián)的是申請(qǐng)內(nèi)存的地址,

匯總:

===》可以思考,通過(guò)malloc和free關(guān)聯(lián)的地址作為標(biāo)識(shí),對(duì)malloc和free的次數(shù)進(jìn)行統(tǒng)計(jì)即可。

===》可以設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu),對(duì)不同地址,malloc的地址和free的地址進(jìn)行保存,malloc的次數(shù)和free的次數(shù)進(jìn)行控制判斷

===》這里根據(jù)老師的邏輯,用文件的方式進(jìn)行控制。

測(cè)試代碼如下:

// malloc和free 之間的關(guān)聯(lián)是申請(qǐng)內(nèi)存的地址,以該地址作為基準(zhǔn)
// malloc時(shí)寫(xiě)入一個(gè)文件,打印行數(shù)等必要信息  free時(shí)刪除這個(gè)文件 通過(guò)有剩余文件判斷內(nèi)存泄露
#define _GNU_SOURCE
#include < dlfcn.h >  //對(duì)應(yīng)的頭文件
#include < stdio.h >
#include < stdlib.h >
#include < unistd.h >

typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;
typedef void (*free_t)(void* p);
free_t free_f = NULL;

int enable_malloc_hook = 1;
int enable_free_hook = 1;

#define MEM_FILE_LENGTH 40
void * malloc(size_t size){
    if(enable_malloc_hook) //對(duì)第三方調(diào)用導(dǎo)致的遞歸進(jìn)行規(guī)避
    {
        enable_malloc_hook = 0;
        //實(shí)際的內(nèi)存申請(qǐng),根據(jù)該地址寫(xiě)文件和free 相互關(guān)聯(lián)
        void *ptr =malloc_f(size);
        //打印上層調(diào)用的地址
        void *carrer = __builtin_return_address(0);
        printf("exec malloc [%p ]n", carrer );

        //通過(guò)寫(xiě)入文件的方式 對(duì)malloc和free進(jìn)行關(guān)聯(lián)  malloc時(shí)寫(xiě)入文件
        char file_buff[MEM_FILE_LENGTH] = {0};
        sprintf(file_buff, "./mem/%p.mem", ptr);

        //打開(kāi)文件寫(xiě)入必要信息 使用前創(chuàng)建目錄級(jí)別
        FILE *fp = fopen(file_buff, "w");
        fprintf(fp, "[malloc addr : +%p ] ---- >mem:%p  size:%lu n",carrer, ptr, size);
        fflush(fp); //刷新寫(xiě)入文件

        enable_malloc_hook = 1;
        return ptr;
    }else
    {
         return malloc_f(size);
    }
}

void free(void * p){
    if(enable_free_hook){
        enable_free_hook = 0;
        void *carrer = __builtin_return_address(0);

        //free時(shí)刪除文件  根據(jù)剩余文件判斷內(nèi)存泄露
        char file_buff[MEM_FILE_LENGTH] = {0};
        sprintf(file_buff, "./mem/%p.mem", p);
        //刪除文件 根據(jù)malloc對(duì)應(yīng)的指針
        if(unlink(file_buff) < 0)
        {
            printf("double free: %p, %p n", p, carrer);
        }
        //這里的打印實(shí)際就沒(méi)意義了
        printf("exec free [%p]n", carrer);
        free_f(p);
        enable_free_hook = 1;
    }else
    {
        free_f(p);
    }
}

//通過(guò)dlsym 對(duì)malloc和free使用前進(jìn)行hook
static void init_malloc_free_hook(){
    //只需要執(zhí)行一次
    if(malloc_f == NULL){
        malloc_f = dlsym(RTLD_NEXT, "malloc"); //除了RTLD_NEXT 還有一個(gè)參數(shù)RTLD_DEFAULT
    }

    if(free_f == NULL)
    {
        free_f =  dlsym(RTLD_NEXT, "free");
    }
    return ;
}
int main()
{
    init_malloc_free_hook(); //執(zhí)行一次
    void * ptr1 = malloc(10);
    void * ptr2 = malloc(20);

    free(ptr1);

    void * ptr3 = malloc(30);
    free(ptr3);
    return 0;
}

執(zhí)行結(jié)果:

# 這里的打印只是為了理解  沒(méi)有多大意義,真正的分析還得依靠文件目錄
hlp@ubuntu:~/mem_test$ mkdir mem
hlp@ubuntu:~/mem_test$ gcc dlsym_hook_file.c -o dlsym_hook_file -ldl -g
hlp@ubuntu:~/mem_test$ ./dlsym_hook_file 
exec malloc [0x400ad5 ]
exec malloc [0x400ae3 ]
exec free [0x400af3]
exec malloc [0x400afd ]
exec free [0x400b0d]
hlp@ubuntu:~/mem_test$ cd mem/

# 這里在我們的目標(biāo)目錄下  看到有文件存在,說(shuō)明存在內(nèi)存泄露
hlp@ubuntu:~/mem_test/mem$ ls
0xe38680.mem
# 通過(guò)文件中的日志信息,對(duì)其進(jìn)行分析,找到問(wèn)題代碼位置
hlp@ubuntu:~/mem_test/mem$ cat 0xe38680.mem 
[malloc addr : +0x400ae3 ] ---- >mem:0xe38680  size:20 
hlp@ubuntu:~/mem_test/mem$ cd ../
#通過(guò)地址轉(zhuǎn)換 找到我們有問(wèn)題代碼 沒(méi)有釋放的代碼位置 
hlp@ubuntu:~/mem_test$ addr2line -fe ./dlsym_hook_file 0x400ae3
main
/home/hlp/mem_test/dlsym_hook_file.c:85

2.3:通過(guò)宏定義的方式對(duì)malloc/free進(jìn)行hook

本質(zhì)其實(shí)就是對(duì)系統(tǒng)調(diào)用的malloc/free進(jìn)行替換,調(diào)用我們的目標(biāo)方法,可以通過(guò)hook或者重載的方法實(shí)現(xiàn)。

使用宏定義的方式,實(shí)現(xiàn)malloc/free的替換。

#include < stdio.h >
#include < stdlib.h >

//不能放在這里  放在這里  會(huì)對(duì)malloc_hook 和 free_hook 內(nèi)部實(shí)際調(diào)用的也替換,就形成的遞歸調(diào)用了 并且無(wú)法規(guī)避
//#define malloc(size) 	malloc_hook(size, __FILE__, __LINE__)
//#define free(p) 		free_hook(p,  __FILE__, __LINE__)

#define MEM_FILE_LENGTH 40
//實(shí)現(xiàn)目標(biāo)函數(shù)
void *malloc_hook(size_t size, const char* file, int line)
{
    //這里還是通過(guò)文件的方式進(jìn)行識(shí)別
    void *ptr =malloc(size);
    char file_name_buff[MEM_FILE_LENGTH] = {0};
    sprintf(file_name_buff, "./mem/%p.mem", ptr);

    //打開(kāi)文件寫(xiě)入必要信息 使用前創(chuàng)建目錄級(jí)別
    FILE *fp = fopen(file_name_buff, "w");
    fprintf(fp, "[file:%s  line:%d ] ---- >mem:%p  size:%lu n",file, line, ptr, size);
    fflush(fp); //刷新寫(xiě)入文件

    printf("exec malloc [%p:%lu], file: %s, line:%d n", ptr, size, file, line );
    return ptr;
}

void free_hook(void *p, const char* file, int line)
{
    char file_name_buff[MEM_FILE_LENGTH] = {0};
    sprintf(file_name_buff, "./mem/%p.mem", p);

    if(unlink(file_name_buff) < 0)
    {
        printf("double free: %p, file: %s. line :%d n", p, file, line);
    }

    //這里的打印實(shí)際就沒(méi)意義了
    printf("exec free [%p], file: %s line:%d n", p, file, line);
    free(p);
}
//宏定義實(shí)現(xiàn)代碼中調(diào)用malloc/free時(shí)調(diào)用我們目標(biāo)函數(shù)
#define malloc(size) 	malloc_hook(size, __FILE__, __LINE__)
#define free(p) 		free_hook(p,  __FILE__, __LINE__)

int main()
{
    //init_malloc_free_hook(); //執(zhí)行一次
    void * ptr1 = malloc(10);
    void * ptr2 = malloc(20);

    free(ptr1);

    void * ptr3 = malloc(30);
    free(ptr3);
    return 0;
}

代碼執(zhí)行如下:

hlp@ubuntu:~/mem_test$ gcc define_hook.c -o define_hook
hlp@ubuntu:~/mem_test$ ./define_hook 
exec malloc [0x1f91010:10], file: define_hook.c, line:47 
exec malloc [0x1f92680:20], file: define_hook.c, line:48 
exec free [0x1f91010], file: define_hook.c line:50 
exec malloc [0x1f938e0:30], file: define_hook.c, line:52 
exec free [0x1f938e0], file: define_hook.c line:53 
#通過(guò)目錄下文件 以及文件內(nèi)容  可以分析到代碼有問(wèn)題的行號(hào)
#注意  測(cè)試前最好清空目錄下的文件 
hlp@ubuntu:~/mem_test$ ls ./mem
0x1f92680.mem
hlp@ubuntu:~/mem_test$ cat ./mem/0x1f92680.mem 
[file:define_hook.c  line:48 ] ---- >mem:0x1f92680  size:20

2.4:_libc_malloc

對(duì)malloc進(jìn)行劫持 使用實(shí)際的內(nèi)存申請(qǐng)_libc_malloc 進(jìn)行申請(qǐng)內(nèi)存以及其他控制

#include < stdio.h >
#include < stdlib.h >
#include < unistd.h >
//實(shí)際內(nèi)存申請(qǐng)的函數(shù)
extern void *__libc_malloc(size_t size);
int enable_malloc_hook = 1;

extern void __libc_free(void* p);
int enable_free_hook = 1;

// func -- > malloc() { __builtin_return_address(0)}
// callback -- > func -- > malloc() { __builtin_return_address(1)}
// main -- > callback -- > func -- > malloc() { __builtin_return_address(2)}

//calloc, realloc
void *malloc(size_t size) {

	if (enable_malloc_hook) {
		enable_malloc_hook = 0;

		void *p = __libc_malloc(size); //重載達(dá)到劫持后 實(shí)際內(nèi)存申請(qǐng)
		void *caller = __builtin_return_address(0); // 0
		
		char buff[128] = {0};
		sprintf(buff, "./mem/%p.mem", p);

		FILE *fp = fopen(buff, "w");
		fprintf(fp, "[+%p] -- > addr:%p, size:%ldn", caller, p, size);
		fflush(fp);

		//fclose(fp); //free
		
		enable_malloc_hook = 1;
		return p;
	} else {
		return __libc_malloc(size);
	}
	
	return NULL;
}


void free(void *p) {
	if (enable_free_hook) {

		enable_free_hook = 0;

		char buff[128] = {0};
		sprintf(buff, "./mem/%p.mem", p);

		if (unlink(buff) < 0) { // no exist
			printf("double free: %pn", p);
		}
		
		__libc_free(p);

		// rm -rf p.mem
		enable_free_hook = 1;

	} else {
		__libc_free(p);
	}
}

int main()
{
    //init_malloc_free_hook(); //執(zhí)行一次
    void * ptr1 = malloc(10);
    void * ptr2 = malloc(20);

    free(ptr1);

    void * ptr3 = malloc(30);
    free(ptr3);
    return 0;
}

代碼運(yùn)行及分析如下:

hlp@ubuntu:~/mem_test$ gcc libc_hook.c -o libc_hook -g
hlp@ubuntu:~/mem_test$ ./libc_hook 
#通過(guò)查看目錄下生成的文件   可以知道有內(nèi)存泄露 通過(guò)日志內(nèi)部信息  使用addr2line 通過(guò)打印地址找到對(duì)應(yīng)的代碼位置
hlp@ubuntu:~/mem_test$ cat ./mem/0x733270.mem 
[+0x4009f7] -- > addr:0x733270, size:20
hlp@ubuntu:~/mem_test$ addr2line -fe ./libc_hook 0x4009f7
main
/home/hlp/mem_test/libc_hook.c:69   #找到問(wèn)題代碼位置

2.5:mem_trace

原理:glic提供__malloc_hook, __realloc_hook, __free_hook可以實(shí)現(xiàn)hook自定義malloc/free函數(shù)

#include < stdlib.h >
#include < stdio.h >
#include < unistd.h >
#include < malloc.h >

	  /* #include < malloc.h >
       void *(*__malloc_hook)(size_t size, const void *caller);
       void *(*__realloc_hook)(void *ptr, size_t size, const void *caller);
       void *(*__memalign_hook)(size_t alignment, size_t size,
                                const void *caller);
       void (*__free_hook)(void *ptr, const void *caller);
       void (*__malloc_initialize_hook)(void);
       void (*__after_morecore_hook)(void);*/
//#include < mcheck.h >

typedef void *(*malloc_hook_t)(size_t size, const void *caller);
typedef void (*free_hook_t)(void *p, const void *caller);

malloc_hook_t 	old_malloc_f = NULL;
free_hook_t 	old_free_f = NULL;
int replaced = 0;

void mem_trace(void);
void mem_untrace(void);

void *malloc_hook_f(size_t size, const void *caller) {

	mem_untrace();
	void *ptr = malloc(size);
	//printf("+%p: addr[%p]n", caller, ptr);
	char buff[128] = {0};
	sprintf(buff, "./mem/%p.mem", ptr);

	FILE *fp = fopen(buff, "w");
	fprintf(fp, "[+%p] -- > addr:%p, size:%ldn", caller, ptr, size);
	fflush(fp);

	fclose(fp); //free
	mem_trace();
	return ptr;
}

void *free_hook_f(void *p, const void *caller) {

	mem_untrace();
	//printf("-%p: addr[%p]n", caller, p);

	char buff[128] = {0};
	sprintf(buff, "./mem/%p.mem", p);

	if (unlink(buff) < 0) { // no exist
		printf("double free: %pn", p);
		return NULL;
	}
	free(p);
	mem_trace();
}
//對(duì)__malloc_hook 和__free_hook 重賦值 
void mem_trace(void) { //mtrace
	replaced = 1;      
	old_malloc_f = __malloc_hook; //malloc -- > 
	old_free_f = __free_hook;

	__malloc_hook = malloc_hook_f;
	__free_hook = free_hook_f;
}

//還原 __malloc_hook 和__free_hook
void mem_untrace(void) {
	__malloc_hook = old_malloc_f;
	__free_hook = old_free_f;
	replaced = 0;
}

int main()
{
    mem_trace();  //mtrace();    //進(jìn)行hook劫持
    void * ptr1 = malloc(10);
    void * ptr2 = malloc(20);

    free(ptr1);

    void * ptr3 = malloc(30);
    free(ptr3);
    mem_untrace();  //muntrace(); //取消劫持
    return 0;
}

這里編譯的時(shí)候有一些警告,但是編譯成功了,

除此之外 相關(guān)資料可以用 man __malloc_hook 去了解

hlp@ubuntu:~/mem_test$ gcc 3.c -o 3 -g
hlp@ubuntu:~/mem_test$ ./3
hlp@ubuntu:~/mem_test$ cat mem/0x1789030.mem 
[+0x400ab2] -- > addr:0x1789030, size:20
hlp@ubuntu:~/mem_test$ addr2line -fe ./3 0x400ab2
main
#可以確定問(wèn)題代碼位置
/home/hlp/mem_test/3.c:79

3:總結(jié):

內(nèi)存泄露的本質(zhì)是,在堆上分配內(nèi)存,使用malloc/calloc/realloc 以及內(nèi)存的釋放free不匹配導(dǎo)致的。

怎么檢測(cè),定位內(nèi)存泄露?:

===》實(shí)際上對(duì)內(nèi)存管理的相關(guān)函數(shù)進(jìn)行劫持(hook),增加一些必要信息供我們分析。

===》不同的方案,其實(shí)就是劫持(hook的方式不一樣)

===》可以使用重載,宏定義,操作系統(tǒng)提供的hook方式(__malloc_hook)等不通的方案

===》在hook的基礎(chǔ)上,要進(jìn)行分析,需要用相關(guān)的策略,這里用的多個(gè)文件,可以用數(shù)據(jù)結(jié)構(gòu)管理。

===》除此之外,__builtin_return_address函數(shù)可以獲取函數(shù)調(diào)用地址,以及addr2line命令對(duì)地址和代碼行數(shù)進(jìn)行轉(zhuǎn)換,確定問(wèn)題代碼位置。

===》編譯的時(shí)候,要加-g,addr2line才可用。

這里只是簡(jiǎn)單的內(nèi)存泄露hook的一些方案demo,有關(guān)多線程等細(xì)節(jié)再實(shí)際項(xiàng)目中也需要考慮。

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 服務(wù)器
    +關(guān)注

    關(guān)注

    12

    文章

    9160

    瀏覽量

    85425
  • Free
    +關(guān)注

    關(guān)注

    0

    文章

    16

    瀏覽量

    11091
  • 內(nèi)存泄漏
    +關(guān)注

    關(guān)注

    0

    文章

    39

    瀏覽量

    9218
  • 虛擬內(nèi)存
    +關(guān)注

    關(guān)注

    0

    文章

    77

    瀏覽量

    8059
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    煤氣泄漏檢測(cè)系統(tǒng)!畢業(yè)設(shè)計(jì)

    煤氣泄漏檢測(cè)系統(tǒng)!畢業(yè)設(shè)計(jì),高手請(qǐng)幫忙!
    發(fā)表于 03-24 01:48

    如何去解決電信設(shè)備內(nèi)的泄漏檢測(cè)

    基于電信設(shè)備內(nèi)液體泄漏檢測(cè)的光電液位傳感器用于昂貴和關(guān)鍵系統(tǒng)的低成本泄漏檢測(cè)解決方案
    發(fā)表于 02-23 06:34

    寫(xiě)了個(gè)內(nèi)存泄漏檢工具

    嵌入式環(huán)境內(nèi)存泄漏檢查比較麻煩,valgrind比較適合于在pc上跑,嵌入式上首先移植就很麻煩,移植完了內(nèi)存比較小,跑起來(lái)也比較費(fèi)勁。所以手動(dòng)寫(xiě)了
    發(fā)表于 12-17 08:25

    基于物聯(lián)網(wǎng)的LPG氣體泄漏檢測(cè)

    使用MQ-5傳感器、ESP8266和Arduino構(gòu)建個(gè)基于物聯(lián)網(wǎng)的LPG氣體泄漏檢測(cè)器。
    發(fā)表于 09-22 06:06

    泄漏檢測(cè)儀校正與調(diào)整

    本文概述了泄漏檢測(cè)儀的基本結(jié)構(gòu),針對(duì)泄漏檢測(cè)儀出現(xiàn)“誤判”故障,在校正及修理調(diào)試時(shí)采取了相應(yīng)措施,恢復(fù)了泄漏檢測(cè)儀正常使用功能。
    發(fā)表于 01-14 15:29 ?13次下載

    泄漏檢測(cè)技術(shù)

    從割草機(jī)到咖啡機(jī),任何的流體處理設(shè)備都需要進(jìn)行泄漏檢測(cè),從而為其投入市場(chǎng)做論證準(zhǔn)備。通常,應(yīng)用在樣機(jī)設(shè)計(jì)階段的泄漏檢測(cè)方法也是在大批量生產(chǎn)中用于檢測(cè)的最好方法
    發(fā)表于 01-23 12:04 ?13次下載

    泄漏檢測(cè)及定位原理

    泄漏檢測(cè)及定位原理 當(dāng)管 道 發(fā) 生泄漏時(shí),泄漏點(diǎn)處由于管道內(nèi)外的壓差,流體迅速消失,壓力下降。泄漏點(diǎn)兩邊的流體由于存在壓差而
    發(fā)表于 01-08 11:48 ?1848次閱讀
    <b class='flag-5'>泄漏檢測(cè)</b>及定位原理

    沼氣泄漏檢測(cè)電路

    沼氣泄漏檢測(cè)電路
    發(fā)表于 02-15 13:35 ?530次閱讀
    沼氣<b class='flag-5'>泄漏檢測(cè)</b>電路

    騰訊內(nèi)部內(nèi)存泄漏分析工具簡(jiǎn)析

    精益求精。 鏈接:wetest.qq.com 工具使用入口 【工具簡(jiǎn)介】 tMemoryMonitor簡(jiǎn)稱TMM,是款運(yùn)行時(shí)C/C++內(nèi)存泄漏檢測(cè)
    發(fā)表于 10-11 15:30 ?0次下載
    騰訊內(nèi)部<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>分析<b class='flag-5'>工具</b>簡(jiǎn)析

    氨氣泄漏的危害_氨氣泄漏檢測(cè)儀怎么使用_氨氣泄漏檢測(cè)儀的使用方法

    氨氣泄漏檢測(cè)儀 氨氣泄漏檢測(cè)儀測(cè)量范圍:0-100ppm、0-400ppm,聲光報(bào)警,高防水防塵設(shè)計(jì),具有數(shù)據(jù)存儲(chǔ)功能,聲光報(bào)警。
    發(fā)表于 01-03 09:57 ?2735次閱讀

    使用AI進(jìn)行流體泄漏檢測(cè)

    電子發(fā)燒友網(wǎng)站提供《使用AI進(jìn)行流體泄漏檢測(cè).zip》資料免費(fèi)下載
    發(fā)表于 06-12 15:58 ?0次下載
    使用AI進(jìn)行流體<b class='flag-5'>泄漏檢測(cè)</b>

    真空泄漏檢測(cè)儀的重要性和應(yīng)用

    真空泄漏檢測(cè)儀是種強(qiáng)大的設(shè)備,它能夠檢測(cè)和定位系統(tǒng)或部件的微小泄漏。在許多行業(yè)中,包括汽車、航空航天、醫(yī)療設(shè)備和半導(dǎo)體等,這種設(shè)備都是必不可少的。下面我們將詳細(xì)討論真空
    的頭像 發(fā)表于 08-15 09:52 ?972次閱讀

    基于C++代碼實(shí)現(xiàn)內(nèi)存泄漏檢測(cè)工具

    看到的個(gè)文章,有人用個(gè)很簡(jiǎn)短的代碼實(shí)現(xiàn)了內(nèi)存檢測(cè)工具,大家看看實(shí)用性如何?
    發(fā)表于 08-21 10:11 ?736次閱讀
    基于C++代碼實(shí)現(xiàn)<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏檢測(cè)工具</b>

    如何檢測(cè)內(nèi)存泄漏

    檢測(cè)內(nèi)存泄漏是軟件開(kāi)發(fā)過(guò)程中項(xiàng)至關(guān)重要的任務(wù),它有助于識(shí)別和解決那些導(dǎo)致程序占用過(guò)多內(nèi)存資源,從而影響程序性能甚至導(dǎo)致程序崩潰的問(wèn)題。以下
    的頭像 發(fā)表于 07-30 11:50 ?1901次閱讀

    超聲波泄漏檢測(cè)

    電子發(fā)燒友網(wǎng)站提供《超聲波泄漏檢測(cè).pdf》資料免費(fèi)下載
    發(fā)表于 09-02 11:33 ?0次下載
    超聲波<b class='flag-5'>泄漏檢測(cè)</b>