# dynamic memleak detect 最近在rathat上研究bpf看能不能往runtime 去做memleak free,其實可以去從中觀看出,在runtime 的時候,某些lib 或者funtion ,實際是可以透過signal去重寫結束狀態,或許可以在程式退出的時候,徹底把memleak 給釋放。 https://github.com/iovisor/bcc/blob/master/tools/memleak.py 最初想法是,當c是否可以像其他程式有生命流程,或者有可以攔截exit 的地方,從發生coredump 的時候思考,我們或許有地方可以做出攔截,所以我找到了 signal ```c= #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <malloc.h> //#include <malloc-internal.h> struct malloc_chuck{ size_t mchunk_prev_size; size_t mchunk_size; struct malloc_chunk* fd; struct malloc_chunk* bk; struct malloc_chunk* fd_nextsize; struct malloc_chunk* bk_nextsize; }; typedef struct malloc_chuck* mchunkptr22; #define PREV_INUSE 0x1 #define IS_MMAPPED 0x2 #define NON_MAIN_ARENA 0x4 //#define SIZE_BITS(PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) __attribute__((space(prog),aligned(2))) #define chunksize (p) (chunksize_nomask(p) & ~(SIZE_BITS)) #define chunklsize_nomask(p) ((p)->mchunk_size) #define chunk2mem(p) ((void*) ((char*) (p) +2*sizeof(size_t) )) #define chunk2mem2(p) ((void*) ((char*) (p) )) #define mem2chunk(mem) ((mchunkptr22) ((char*)(mem) - 2* sizeof (size_t))) void fun (int signal){ mchunkptr22 p; mchunkptr22 p3; mchunkptr22 p5; mchunkptr22 p6; int *p4 = malloc (64); int *p2 = malloc (32); p4[0] =10; p2 [0]=10; p= mem2chunk(p2); p3 = mem2chunk(p4); p5 = (size_t) (p->bk); p6 = (size_t) (p3->bk); size_t tx =(size_t) (p->mchunk_prev_size); size_t tx2 = chunk2mem(p); size_t tx3 =(size_t) (p->mchunk_size); size_t tx4 =(size_t) (p3->mchunk_prev_size); size_t tx5 = chunk2mem(p3); size_t tx6 =(size_t) (p3->mchunk_size); size_t tx7 =chunk2mem2(p); size_t tx8 =chunk2mem2(p3); size_t tx9 =(size_t) (p->fd); size_t tx10 = (size_t) (p->bk); // size_t tx3 =(size_t) (p3->mchunk_size); //size_t tx4 = chunk2mem(p2); printf("%zu\n",tx); printf("%zu\n",tx2); printf("%zu\n" ,tx3); printf("%zu\n" ,tx7); printf("%zu\n",tx4); printf("%zu\n",tx5); printf("%zu\n" ,tx6); printf("%zu\n" ,tx8); printf("%zu\n",tx9); printf("%d\n",tx9); printf("%zu\n",tx10); printf("%p\n" , &p2); printf("%p\n" , &p4); printf("tet"); // exit(0); } int main () { signal(SIGABRT,fun); while(1){ int *p; p = malloc (10); printf("%d\n",&p); //free(p); //free(p); ; } } ``` 一開始本來想從攔截malloc fucntion 的地方下手 https://webcache.googleusercontent.com/search?q=cache:cACnJ40Ng00J:https://blog.csdn.net/qq_41453285/article/details/97135257+&cd=3&hl=zh-TW&ct=clnk&gl=tw https://www.itread01.com/content/1546763613.html https://www.jianshu.com/p/231b2047fbc5 https://www.codenong.com/cs109525810/ 本來要從 chunk 下手,透過 mem2chunk 透過訪問 fd 確實可以得到用戶所存的data 對應下列程式碼 ![](https://i.imgur.com/dGR0ScX.png) ```c= int main () { signal(SIGABRT,fun); while(1){ int *p; mchunkptr22 p2; p = malloc (10); p2 = mem2chunk(p); printf("%d\n",&p[0]); printf("%d\n",&p2->fd); //free(p); //free(p); ; } } ``` 到這邊螢幕上顯示的就是p 和 p2->fd 記憶體位置,這是相同的這意味著 malloc 回傳的記憶體就是這個位置 所以釋放記憶體也可以寫成 ```c= free (p2->fd ) ``` 如果要在發生中斷或著coredump 去把這些記憶體addr 做導出 前提是我們要得到我們的addr 看一下memleak.py可以提供到什麼程度 sudo python3 memleak.py -p ![](https://i.imgur.com/nFhIF7H.png) ![](https://i.imgur.com/aqr4o5E.png) 假設沒有釋放記憶體其實可以顯示call stack 可以觀察到其中的 main+0x21,那麼實際上對應到的是哪一些程式碼片段的 記得在編譯要加 -g flag > gcc test -o test -g 可以看到對應的內容是 ![](https://i.imgur.com/yk8iG6a.png) 400896 可以看到其實就是malloc 回傳的 位置其實就是 point p objdump -S test ![](https://i.imgur.com/FzOWGHT.png) 那麼有這些資訊我們的步驟可以重新整理為 從 memleak.py可以攔截到 malloc 申請到的記憶體位置,我們可以把它導出這些記憶體位置 進行sigle 做中斷處理 http://myblog-maurice.blogspot.com/2011/12/linux-signal.html https://www.itread01.com/content/1510064533.html 一般 coredump 都走,當程式 ```c= signal(SIGABRT,fun); ``` 最後一個thread 結束 return 0 的時候 ```c= atexit(fun); ``` 這樣我們的 c 語言發生中斷程式攔截就完成了, 接下了就是 int 能不能轉成相對應的 address https://blog.csdn.net/cs_zhanyb/article/details/16973379 uintptr_t == unsigned long int ```c= void *p3 = &p2->fd ; uintptr_t value =(int) &p2->fd; p3 = (void * ) value; free(p3); ``` 也就意味著釋放掉一開始p 從malloc 得到的記憶體塊 嘗試了蠻多方法,去看能不能去取得fucmtion return 在 reg的value https://elixir.bootlin.com/linux/v4.9/source/samples/bpf/bpf_helpers.h#L101 pointer 實際上在address 操作可能不太清楚 目前嘗試了這個方向 ```c= #if defined(__x86_64__) #define PT_REGS_PARM1(x) ((x)->di) #define PT_REGS_PARM2(x) ((x)->si) #define PT_REGS_PARM3(x) ((x)->dx) #define PT_REGS_PARM4(x) ((x)->cx) #define PT_REGS_PARM5(x) ((x)->r8) #define PT_REGS_RET(x) ((x)->sp) #define PT_REGS_FP(x) ((x)->bp) #define PT_REGS_RC(x) ((x)->ax) #define PT_REGS_SP(x) ((x)->sp) #define PT_REGS_IP(x) ((x)->ip) ``` ![](https://i.imgur.com/NE4ie6O.png) 還有後者從objdump 得到實際的偏移位置,但是還是不能得到原先pointer 的address ```c= #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <stdint.h> #include <malloc.h> #include <pthread.h> //#include <malloc-internal.h> struct malloc_chuck{ size_t mchunk_prev_size; size_t mchunk_size; struct malloc_chunk* fd; struct malloc_chunk* bk; struct malloc_chunk* fd_nextsize; struct malloc_chunk* bk_nextsize; }; typedef struct malloc_chuck* mchunkptr22; #define PREV_INUSE 0x1 #define IS_MMAPPED 0x2 #define NON_MAIN_ARENA 0x4 //#define SIZE_BITS(PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) __attribute__((space(prog),aligned(2))) #define chunksize (p) (chunksize_nomask(p) & ~(SIZE_BITS)) #define chunklsize_nomask(p) ((p)->mchunk_size) #define chunk2mem(p) ((void*) ((char*) (p) +2*sizeof(size_t) )) #define chunk2mem2(p) ((void*) ((char*) (p) )) #define mem2chunk(mem) ((mchunkptr22) ((char*)(mem) - 2* sizeof (size_t))) void fun (int signal){ mchunkptr22 p; mchunkptr22 p3; mchunkptr22 p5; mchunkptr22 p6; int *p4 = malloc (64); int *p2 = malloc (32); p4[0] =10; p2 [0]=10; p= mem2chunk(p2); p3 = mem2chunk(p4); p5 = (size_t) (p->bk); p6 = (size_t) (p3->bk); size_t tx =(size_t) (p->mchunk_prev_size); size_t tx2 = chunk2mem(p); size_t tx3 =(size_t) (p->mchunk_size); size_t tx4 =(size_t) (p3->mchunk_prev_size); size_t tx5 = chunk2mem(p3); size_t tx6 =(size_t) (p3->mchunk_size); size_t tx7 =chunk2mem2(p); size_t tx8 =chunk2mem2(p3); size_t tx9 =(size_t) (p->fd); size_t tx10 = (size_t) (p->bk); // size_t tx3 =(size_t) (p3->mchunk_size); //size_t tx4 = chunk2mem(p2); printf("%zu\n",tx); printf("%zu\n",tx2); printf("%zu\n" ,tx3); printf("%zu\n" ,tx7); printf("%zu\n",tx4); printf("%zu\n",tx5); printf("%zu\n" ,tx6); printf("%zu\n" ,tx8); printf("%zu\n",tx9); printf("%d\n",tx9); printf("%zu\n",tx10); printf("%p\n" , &p2); printf("%p\n" , &p4); printf("tet"); // exit(0); } void *child (){ int *k = malloc(20); printf("pointer 2 %x\n", &k); k[1]=10; } int main () { int *test = malloc(22); test[0] =10; signal(SIGABRT,fun); atexit(fun); while(1){ int *p; p = malloc (10); p[0]=10; p[1]=20; //printf("%p\n" ,&p[0]); mchunkptr22 p5; p5= mem2chunk(p); while(p5->fd == NULL ){ // printf("123\n"); if(p5->fd != NULL) printf("%x\n",p5->fd ); printf("%x\n",&(p5->fd) ); // mchunkptr22 p6 = (int)&(p5->fd) + (int)p5->fd_nextsize; void *test = &(p5->fd); void *p3 =(int)&(p5->fd) +sizeof(p5->fd); //uintptr_t value =(int) &p2->fd; //p3 = (void * ) value; printf("%x\n",((mchunkptr22)p3)->fd); printf("%p\n", ((uint8_t* )test)); printf("%x\n", *((uint8_t* )test)); printf("%x\n", *((uint8_t* )test+1)); printf("%x\n", *((uint8_t* )test+2)); printf("%x\n", *((uint8_t* )test+3)); printf("%x\n", *((uint8_t* )test+4)); printf("%p\n", ((uint8_t* )test+4)); *(test) ++; } mchunkptr22 p2; p2 = mem2chunk(p); size_t tx =(size_t) (p2->fd); //printf("%zu\n" , tx); printf("pointer %p\n",p); printf("pointer %x\n",&p); printf("size %d\n ",sizeof(*(&p[0]))) ; printf("size %d \n",sizeof(p2->fd)) ; printf("%p\n",&p[0]); printf("%p\n",&p2->fd ); void *p3 = &p2->fd ; uintptr_t value =(int) &p2->fd; p3 = (void * ) value; printf("size %d \n",sizeof(*(&p2->fd ))) ; printf("%x\n", value); printf("%x\n", p3); //free(p3); pthread_t t; pthread_create(&t,NULL,child,NULL); // free(&p2->fd ); //free(p); sleep(2); // break; } } ``` ![](https://i.imgur.com/2qUxa7X.png) 我們從memleak.py確實可以得到真正的釋放記憶體位置 下列程式碼 ```c= printf("pointer %p\n",p); printf("pointer %x\n",&p); printf("size %d\n ",sizeof(*(&p[0]))) ; printf("size %d \n",sizeof(p2->fd)) ; ``` 看到3 4 行 size 雖然指向的address 一樣但是 從pointer 可以得到type size 為4 從fd 卻是得到 type size 為 8 還是沒辦法,所以才有前者的方式,我要看reg有沒有存pointer 的 address 這樣就可以得到 type size ,有的話,我從 memleak.py 可以獲得 (總size) / sizeof (原本pointer 的 type) 就可以把這個陣列存下來了。 不過沒關係,有總size的話哪麼,實際上 我們申請的記憶體區塊也就是從fd + 總size 當發生程式中斷時,我們也可以即時做出malloc 備份。 ![](https://i.imgur.com/N50J67J.png) 釋放記憶體的話,我們從 https://nakryiko.com/posts/bpf-tips-printk/ > sudo cat /sys/kernel/debug/tracing/trace_pipe > 得到 bpf_trace_printk () log檔案 所以我們只要把 addr 重新 用個 void 指過去在free ,這樣我們就可以在退出的時候,釋放加備份囉 ```c= void *p3 = &p2->fd ; uintptr_t value =(int) &p2->fd; p3 = (void * ) value; free(p3) ``` 透過register 得到 address ,我們知道最後的call stack 可以對硬objdump 的實際位置 ![](https://i.imgur.com/ZLOmy08.png) 會有這個想法的產生是因為,我既然可以攔截fucntion 那麼,fucntion 的進入點和退出點都攔截的到,return value 最後賦予值在哪裡呢,也就是我前面提到的register裡我們看一下數據結構 https://elixir.bootlin.com/linux/v4.9/source/samples/bpf/bpf_helpers.h#L101 觀看原始碼PT_REGS ctx 這個就對應到說,當前呼叫malooc fucntion進入的時候 register的狀態,透過 PT_REGS_SP (ctx) 我可以得到 register fucntion 要返回到 user space 的 address 實際proc maps 記憶體分配情形 這邊不是取自於 當前的pid 的 maps 可能數據對不起來。 ![](https://i.imgur.com/Ua6HV28.png)