如何確定有內(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)目中也需要考慮。
-
服務(wù)器
+關(guān)注
關(guān)注
12文章
9160瀏覽量
85425 -
Free
+關(guān)注
文章
16瀏覽量
11091 -
內(nèi)存泄漏
+關(guān)注
關(guān)注
0文章
39瀏覽量
9218 -
虛擬內(nèi)存
+關(guān)注
關(guān)注
0文章
77瀏覽量
8059
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論