手写内存泄漏检测组件

  • 前言
  • 内存泄漏
    • 内存泄漏的现象与危害
    • 内存泄漏检测组件的两个核心需求点
  • 第一版:__libc_malloc, __libc_malloc 与 __builtin_return_address,addr2line
    • hook malloc与free出现的问题
    • 使用addr2line定位代码
    • 检测内存泄漏的最佳方案
  • 第二版:采用宏定义
    • 巧用宏机制
    • 优雅的代码
  • 第三版:借助 malloc.h 里面 __malloc_hook
    • malloc.h里面的__malloc_hook
    • trace机制
    • 定位代码
  • 第四版:第三方库mtrace的使用与我们的meme_trace差异
  • 全文总结

前言

  本文介绍内存泄漏检测的核心需求以及注意点,一共4个版本的代码层层迭代。本文4个版本源码git地址:内存泄漏检测组件

  常用的内存泄漏检测工具有valgrind和mtrace。我们使用这两个工具的时候一般是已经发现了内存泄漏的现象了再去检测,那么有没有一种方法在内存使用的时候,就发现内存泄漏呢。

  本专栏知识点是通过零声教育的线上课学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接 C/C++后台高级服务器课程介绍 详细查看课程的服务。

内存泄漏

内存泄漏的现象与危害

  内存泄漏只有不带gc垃圾回收机制的语言才有,比如go和java,它们都自带gc,所以它们不会有内存泄漏。 但是像c和c++是不带gc的,所以很可能发生内存泄漏的情况。这里我们已c语言举例,c在分配内存时调用malloc/calloc/realloc(本文全部以malloc举例),释放内存时调用free。

  那么内存泄漏的核心原因就很简单了,内存分配与内存释放没有做到匹配。换言之,调用了多少次malloc,就free多少次,那么就不会产生内存泄漏。如果malloc和free的次数不对等,那么一定是有问题的。

int main() {void *p1 = malloc(10);void *p2 = malloc(20);free(p1);
}

  以上的代码,分配了两块内存,只释放了p1,p2没有被释放,那么这个程序就产生了内存泄漏。

  内存泄漏的危害:随着工程代码量越来越多,自然内存泄漏的排查就成为了一个很头疼的问题。有分配没有释放,自然会使得进程堆的内存会越来越少,直到耗尽。会造成后面的运行时代码不能成功分配内存。分配失败我们的程序就不能继续的往下执行。

内存泄漏检测组件的两个核心需求点

  现在知道了内存泄漏和危害和内存泄漏的原因,那么内存泄漏如何解决?内存泄漏是没有自动 gc 的编程语言所产生的,解决方案一,引入 gc。这是根治内存泄漏的最好的方案。但是这样的方案有失去了 c/c++语言的优势。方案二,当发生内存泄漏的时候,能够精准的定位代码哪一行所引起的。这也是我们实现内存泄漏检测的如何核心实现需求。那么本文就是围绕着下面两个需求展开的。

  1. 能够检测出来发生了内存泄漏
  2. 能够判断定位代码哪一行引起内存泄漏

  对于第一个需求,我们在下文中介绍。对于定位代码这里提前介绍两个方案。

# 宏
__FILE__,__FUNC__,__LINE__
# 编译器提供的函数,返回第N层调用函数地址, addr2line是一个工具
builtin_return_address(N)  +  addr2line
func1->func2->func3->func4{ cnt= builtin_return_address(0) }   cnt=func3
func1->func2->func3->func4{ cnt= builtin_return_address(1) }   cnt=func2
func1->func2->func3->func4{ cnt= builtin_return_address(2) }   cnt=func1

第一版:__libc_malloc, __libc_malloc 与 __builtin_return_address,addr2line

hook malloc与free出现的问题

  我们运行下面的代码,发现出现段错误,并不符合我们的预期

//
// Created by 68725 on 2022/8/13.
//
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>typedef void *(*malloc_t)(size_t);malloc_t malloc_f;typedef void (*free_t)(void *);free_t free_f;static int init_hook() {malloc_f = dlsym(RTLD_NEXT, "malloc");free_f = dlsym(RTLD_NEXT, "free");
}void *malloc(size_t size) {printf("In malloc\n");return NULL;
}void free(void *ptr) {printf("In free\n");}int main() {init_hook();void *p1 = malloc(10);void *p2 = malloc(20);free(p1);
}
root@wxf:/tmp/tmp.d4vz2dOyJP# gcc -o first first.c -ldl
root@wxf:/tmp/tmp.d4vz2dOyJP# ./first
Segmentation fault (core dumped)

  我们的代码看起来明明这么合理,为什么会段错误?我们进入gdb看一看,我们打印23行看一看,发现程序确确实实是走到了23行。但是我们发现,它递归的进入了printf这个函数,并且第一次malloc(size=10),而后面malloc (size=1024),这说明什么?说明printf里面也调用了malloc函数,而这个malloc函数被我们hook了,导致递归进入我们hook的函数里面了。那么我们下面就要去破坏这个递归。

root@wxf:/tmp/tmp.d4vz2dOyJP# gcc -o first first.c -ldl -g
root@wxf:/tmp/tmp.d4vz2dOyJP# gdb ./first
Reading symbols from ./first...done.
(gdb) b 23
Breakpoint 1 at 0x741: file first.c, line 23.
(gdb) r
Starting program: /tmp/tmp.d4vz2dOyJP/first Breakpoint 1, malloc (size=10) at first.c:23
23      printf("In malloc\n");
(gdb) c
Continuing.Breakpoint 1, malloc (size=1024) at first.c:23
23      printf("In malloc\n");
(gdb) c
Continuing.Breakpoint 1, malloc (size=1024) at first.c:23
23      printf("In malloc\n");
(gdb) c
Continuing.Breakpoint 1, malloc (size=1024) at first.c:23
23      printf("In malloc\n");
(gdb) 

  如何破坏递归呢?我们让第一次进入函数的部分执行我们的流程,而递归进去的算第二次进入函数,那么我们直接调用系统原来的函数即可。这里介绍两个函数__libc_malloc和__libc_free,它们是malloc和free底层调用的函数。可以看到下面代码注释的地方,我们直接改成这两个函数效果是一样的。不过既然我们都用hook了,那有何必再调用别的函数呢?这里讲__libc_malloc和__libc_free是为了引出malloc底层调用__libc_malloc,free底层调用__libc_free。

//
// Created by 68725 on 2022/8/13.
//
#define _GNU_SOURCE#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>extern void *__libc_malloc(size_t size);extern void __libc_free(void *ptr);typedef void *(*malloc_t)(size_t);int enable_malloc_hook = 1;malloc_t malloc_f;typedef void (*free_t)(void *);int enable_free_hook = 1;free_t free_f;static int init_hook() {malloc_f = dlsym(RTLD_NEXT, "malloc");free_f = dlsym(RTLD_NEXT, "free");
}void *malloc(size_t size) {if (enable_malloc_hook) {enable_malloc_hook = 0;void *p = malloc_f(size);//void *p = __libc_malloc(size);printf("malloc--->ptr:%p size:%zu\n", p, size);enable_malloc_hook = 1;return p;}else {return malloc_f(size);//return __libc_malloc(size);}
}void free(void *ptr) {if (enable_free_hook) {enable_free_hook = 0;printf("free  --->ptr:%p\n", ptr);free_f(ptr);//__libc_free(ptr);enable_free_hook = 1;}else {return free_f(ptr);//return __libc_free(ptr);}
}int main() {init_hook();void *p1 = malloc(10);void *p2 = malloc(20);free(p1);
}

  我们现在就能正常执行程序了,并且我们肉眼可见的能够分析出哪个指针没有被释放。我们现在只知道是哪个指针没有被释放,但是我们并不能定位到是代码的哪一行。所以我们接着再进行优化。

root@wxf:/tmp/tmp.d4vz2dOyJP# gcc -o first first.c -ldl -g
root@wxf:/tmp/tmp.d4vz2dOyJP# ./first
malloc--->ptr:0x55e80ef45260 size:10
malloc--->ptr:0x55e80ef45690 size:20
free  --->ptr:0x55e80ef45260

  下面三个宏在try-catch那篇文章我们其实已经用过了,所以现在我们来使用builtin_return_address看看。上面__libc_malloc和__libc_free相关的代码我就删掉了,其作用与malloc_f和free_f一致。

# 宏
__FILE__,__FUNC__,__LINE__
# 编译器提供的函数,返回第N层调用函数
builtin_return_address(N)

使用addr2line定位代码

  我们在malloc函数里面加上caller的打印,来看看它的值是什么。这里先提出一点,我在__builtin_return_address函数外面套了一层ConvertToVMA。目的是把返回的内存地址转换成VMA地址。至于什么是VMA如果有机会再拎起一篇详细介绍,这里不做这介绍,原因主要是这里写builtin_return_address的目的是,介绍builtin_return_address函数和addr2line工具,其实我是不推荐使用这种方案的,因为用上面三个宏去定位文件函数行号更方便,也不需要借助工具去分析。另一方面是有些linux系统返回的内存地址就是VMA地址,有些不是,比如我的机器就不是,具体原理我也不是很懂,如果有知道的读者可以在评论区说一下。

void *caller = ConvertToVMA(__builtin_return_address(0));
printf("[+%p]--->ptr:%p size:%zu\n", caller, p, size);
//
// Created by 68725 on 2022/8/13.
//
#define _GNU_SOURCE#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <link.h>typedef void *(*malloc_t)(size_t);int enable_malloc_hook = 1;malloc_t malloc_f;typedef void (*free_t)(void *);int enable_free_hook = 1;free_t free_f;static int init_hook() {malloc_f = dlsym(RTLD_NEXT, "malloc");free_f = dlsym(RTLD_NEXT, "free");
}void *ConvertToVMA(void *addr) {Dl_info info;struct link_map *link_map;dladdr1((void *) addr, &info, (void **) &link_map, RTLD_DL_LINKMAP);return addr - link_map->l_addr;
}void *malloc(size_t size) {if (enable_malloc_hook) {enable_malloc_hook = 0;void *p = malloc_f(size);void *caller = ConvertToVMA(__builtin_return_address(0));printf("[+%p]--->ptr:%p size:%zu\n", caller, p, size);char command[256];Dl_info info;dladdr(malloc, &info);snprintf(command, sizeof(command), "addr2line -f -e %s -a %p >1.txt", info.dli_fname, caller);printf("%s\n", command);system(command);enable_malloc_hook = 1;return p;}else {return malloc_f(size);}
}void free(void *ptr) {if (enable_free_hook) {enable_free_hook = 0;void *caller = __builtin_return_address(0);printf("[-%p]--->ptr:%p\n", caller, ptr);free_f(ptr);enable_free_hook = 1;}else {return free_f(ptr);}
}int main() {init_hook();void *p1 = malloc(10);void *p2 = malloc(20);free(p1);
}

  我们可以看到,__builtin_return_address返回的第一个0xb44,它其实是代码段上的一个地址,通过这个地址,我们使用addr2line可以计算出来是在哪个函数,哪个文件,哪行。只不过我这里偷懒在代码里用system直接执行了,一般来说我们是在bash里面通过log记录的地址,再去使用addr2line的。至于addr2line的用法,直接百度搜好了,很简单。

root@wxf:/tmp/tmp.d4vz2dOyJP# gcc -o first first.c -ldl -g
root@wxf:/tmp/tmp.d4vz2dOyJP# ./first
[+0xb44]--->ptr:0x558bf404f260 size:10
addr2line -f -e ./first -a 0xb44
0x0000000000000b44
main
/tmp/tmp.d4vz2dOyJP/first.c:76
[+0xb52]--->ptr:0x558bf404f690 size:20
addr2line -f -e ./first -a 0xb52
0x0000000000000b52
main
/tmp/tmp.d4vz2dOyJP/first.c:77
[-0x558bf1ea0b62]--->ptr:0x558bf404f260
root@wxf:/tmp/tmp.d4vz2dOyJP# addr2line -f -e ./first -a 0xb44
0x0000000000000b44
main
/tmp/tmp.d4vz2dOyJP/first.c:76

  那么我们现在已经也解决了定位的问题,下面我们再来看看怎么做内存检测。从上面我们其实已经可以看到第一个+的prt和最后-的ptr地址是一样的,也就是说malloc的地址被free掉了,而第二个没有被free(-),因为我们没有看到减号。

root@wxf:/tmp/tmp.d4vz2dOyJP# gcc -o first first.c -ldl -g
root@wxf:/tmp/tmp.d4vz2dOyJP# ./first
[+0xb44]--->ptr:0x558bf404f260 size:10
[+0xb52]--->ptr:0x558bf404f690 size:20
[-0x558bf1ea0b62]--->ptr:0x558bf404f260

  那也就意味着,我们现在需要设计一种方案,在malloc的时候把ptr加进去,free的时候把对应的ptr去掉,在程序结束之后我们可以看到有哪些ptr还存在,这些存在的ptr就是没有被free的,如此一来,就能检测到内存泄漏了。

检测内存泄漏的最佳方案

  读者在这里可以思考一下上面说的方案怎么做最好,其实如果用map,用链表,我个人感觉都不好,因为在程序中用这两个数据结构,那么数据还是保存在堆栈上的,在程序结束之前需要打印出来。那如果用文件的方法呢?malloc的时候,以ptr内存地址为文件名,把<文件,函数,行号>写入文件,free的时候,把对应的文件删除。那么我们只需要通过ls即可清楚的看到哪些内存被泄露了,用cat看一下文件就能定位。下面我们对代码再次优化。

  还记得上面我们偷懒使用的system吗?这里就派上用场了,在拼接字符串的时候,我们用> 将标准输出定位到文件即可,在删除的时候用unlink删除。

//malloc
snprintf(command, sizeof(command), "addr2line -f -e %s -a %p > ./mem/%p.mem", info.dli_fname, caller, p);
system(command);
//free
char buff[128] = {0};
sprintf(buff, "./mem/%p.mem", ptr);
if (unlink(buff) < 0) {printf("double kill:%p\n",ptr);
}
root@wxf:/tmp/tmp.d4vz2dOyJP# gcc -o first first.c -ldl -g
root@wxf:/tmp/tmp.d4vz2dOyJP# ./first
[+0xc3a]--->ptr:0x55f613d27260 size:10
[+0xc48]--->ptr:0x55f613d27690 size:20
[-0xc58]--->ptr:0x55f613d27260
root@wxf:/tmp/tmp.d4vz2dOyJP# ls
first  first.c  mem
root@wxf:/tmp/tmp.d4vz2dOyJP# cd mem/
root@wxf:/tmp/tmp.d4vz2dOyJP/mem# ls
0x55f613d27690.mem
root@wxf:/tmp/tmp.d4vz2dOyJP/mem# cat 0x55f613d27690.mem
0x0000000000000c48
main
/tmp/tmp.d4vz2dOyJP/first.c:82

  至此,我们的第一版内存泄漏检测组件的代码就完成了,完整代码可去前言的源码超链接中获取。现在再来回顾一下两个核心需求

1. 能够检测出来发生了内存泄漏
2. 能够判断定位代码哪一行引起内存泄漏

  我们现在通过mem文件夹里面的文件就可以看出来有没有发生内存泄漏,因为只有没有被free的地址才会有文件。有文件就说明发生了内存泄漏。怎么定位代码呢,我们这里用的是__builtin_return_address 和 addr2line。其实内存泄漏检测没有想象中的这么恐怖,在我初知内存泄漏的时候,我感觉那些内存泄漏的检测软件很厉害,那么在本文抽丝剥茧之后,这种对于未知的恐惧就消失了,甚至于本文下面还能接着优化。万变不离其宗,在写内存泄漏组件的时候,围绕着上面两个需求去做就好了。

  使用__builtin_return_address 和 addr2line的第一版代码不知道读者有没有感觉到这里十分的麻烦,下面第二版代码我们使用简洁的宏定义来做。

第二版:采用宏定义

巧用宏机制

  在我们的第一版代码实现中,更多的是想向读者介绍一些函数。而且我们在使用hook的时候还遇到了递归的问题,以及__builtin_return_address的VMA的问题,对于后面这个函数,我们使用系统提供的三个宏即可解决。现在我们想一下,这个hook用在这里真的合适吗?

  我们知道函数预编译的时候,会把对应宏下面的内容全部替换掉,那么我们是否可以定义malloc的宏呢?例如下面两段代码,main中的malloc被替换成了malloc_def,而malloc_def中的malloc却没有被替换。我们使用这个机制,就可以避开hook的风险。并且我们可以在宏定义的上下加个开关,如果代码想要进行内存泄漏检测就打开,不想就走原来的系统调用即可。

void *malloc_def(size_t size, const char *file, const char *func, int line) {void *p = malloc(size);
}#define malloc(size) malloc_def(size,__FILE__,__FUNCTION__ ,__LINE__)int main() {void *p1 = malloc(10);
}
void *malloc_def(size_t size, const char *file, const char *func, int line) {void *p = malloc(size);
}#define malloc(size) malloc_def(size,__FILE__,__FUNCTION__ ,__LINE__)int main() {void *p1 = malloc_hook(10,second.c,main,15);
}
#define check_mem_leak#ifdef check_mem_leak
#define malloc(size) malloc_def(size,__FILE__,__FUNCTION__ ,__LINE__)
#define free(p) free_def(p,__FILE__,__FUNCTION__ ,__LINE__)
#endif

  短短50行,我们就实现了比第一版更为优雅的内存泄漏检测组件。可以看到,在第一个需求如何检测内存泄漏,我们使用的都是统一的一个方案,malloc的时候创建一个文件,free的时候删除一个文件。在定位代码的时候有两个解决方法,这里比较推荐的就是宏的方法。

优雅的代码

//
// Created by 68725 on 2022/8/13.
//
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <unistd.h>#define check_mem_leakvoid *malloc_def(size_t size, const char *file, const char *func, int line) {void *p = malloc(size);char buff[128] = {0};sprintf(buff, "./mem/%p.mem", p);FILE *fp = fopen(buff, "w");fprintf(fp, "[+%s:%s:%d] --> addr:%p, size:%ld\n", file, func, line, p, size);fflush(fp);fclose(fp);return p;
}void free_def(void *p, const char *file, const char *func, int line) {char buff[128] = {0};sprintf(buff, "./mem/%p.mem", p);if (unlink(buff) < 0) { // no existprintf("double free: %p\n", p);return;}free(p);
}#ifdef check_mem_leak
#define malloc(size) malloc_def(size,__FILE__,__FUNCTION__ ,__LINE__)
#define free(p) free_def(p,__FILE__,__FUNCTION__ ,__LINE__)
#endifint main() {void *p1 = malloc(10);void *p2 = malloc(20);void *p3 = malloc(30);void *p4 = malloc(40);free(p1);free(p2);free(p4);free(p4);
}
root@wxf:/tmp/tmp.d4vz2dOyJP# gcc -o second second.c
root@wxf:/tmp/tmp.d4vz2dOyJP# ./second
double free: 0x5571e5c55500
root@wxf:/tmp/tmp.d4vz2dOyJP# cd mem
root@wxf:/tmp/tmp.d4vz2dOyJP/mem# ls
0x5571e5c554d0.mem
root@wxf:/tmp/tmp.d4vz2dOyJP/mem# cat 0x5571e5c554d0.mem
[+second.c:main:48] --> addr:0x5571e5c554d0, size:30

第三版:借助 malloc.h 里面 __malloc_hook

malloc.h里面的__malloc_hook

  我们需要知道malloc的调用流程malloc->__libc_malloc->__malloc_hook,而这个__malloc_hook是个函数指针,所以我们可以在这上面做手脚。

/* Hooks for debugging and user-defined versions. */
extern void (*__MALLOC_HOOK_VOLATILE __free_hook)(void *__ptr, const void *)__MALLOC_DEPRECATED;extern void *(*__MALLOC_HOOK_VOLATILE __malloc_hook)(size_t __size, const void *)__MALLOC_DEPRECATED;extern void *(*__MALLOC_HOOK_VOLATILE __realloc_hook)(void *__ptr,size_t __size,const void *)__MALLOC_DEPRECATED;extern void *(*__MALLOC_HOOK_VOLATILE __memalign_hook)(size_t __alignment,size_t __size,const void *)__MALLOC_DEPRECATED;extern void (*__MALLOC_HOOK_VOLATILE __after_morecore_hook)(void);

trace机制

  在main中我们启动trace,那么就代表着我们要将malloc内部调用的流程,变成我们写的函数,也就是说让malloc执行我们写的函数,在我们写的函数之后,我们再untrace,随后执行malloc让它分配内存,在结尾再进入trace。如此一来,即解决了第一版代码中hook的递归问题,提供了两个api接口给用户,交由用户自己判断是否需要内存泄漏检测。

typedef void *(*malloc_hook_t)(size_t size, const void *caller);malloc_hook_t malloc_f;typedef void (*free_hook_t)(void *p, const void *caller);free_hook_t free_f;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);mem_trace();return ptr;
}void free_hook_f(void *p, const void *caller) {mem_untrace();printf("-%p: addr[%p]\n", caller, p);free(p);mem_trace();
}//replaced=1代表正在trace,__malloc_hook走我们自己写的函数
//replaced=0代表没有trace,__malloc_hook走系统自己的函数
void mem_trace(void) { //mtracereplaced = 1;//让malloc_f指向系统的malloc_f = __malloc_hook;//free_ffree_f = __free_hook;//让系统的hook指向我们写的函数__malloc_hook = malloc_hook_f;__free_hook = free_hook_f;
}void mem_untrace(void) {//让系统的hook指向系统的malloc和free__malloc_hook = malloc_f;__free_hook = free_f;replaced = 0;
}
int main() {mem_trace();void *p1 = malloc(10);void *p2 = malloc(20);void *p3 = malloc(30);void *p4 = malloc(40);free(p1);free(p2);free(p4);free(p4);mem_untrace();
}

定位代码

  但是这里我们就不能使用宏了,那么我们就接着使用addr2line的方法

//
// Created by 68725 on 2022/8/16.
//
#define _GNU_SOURCE#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <link.h>
#include <unistd.h>
#include <malloc.h>typedef void *(*malloc_hook_t)(size_t size, const void *caller);malloc_hook_t malloc_f;typedef void (*free_hook_t)(void *p, const void *caller);free_hook_t free_f;int replaced = 0;void mem_trace(void);void mem_untrace(void);const void *ConvertToVMAToSystem_addr2line(const void *addr, const void *ptr) {Dl_info info;struct link_map *link_map;dladdr1((void *) addr, &info, (void **) &link_map, RTLD_DL_LINKMAP);const void *caller = addr - link_map->l_addr;char command[256];snprintf(command, sizeof(command), "addr2line -f -e %s -a %p > ./mem/%p.mem", info.dli_fname, caller, ptr);system(command);return caller;
}void *malloc_hook_f(size_t size, const void *caller) {mem_untrace();void *ptr = malloc(size);caller = ConvertToVMAToSystem_addr2line(caller, ptr);printf("[+%p]--->ptr:%p size:%zu\n", caller, ptr, size);mem_trace();return ptr;
}void free_hook_f(void *ptr, const void *caller) {mem_untrace();caller = ConvertToVMAToSystem_addr2line(caller, ptr);printf("[-%p]--->ptr:%p\n", caller, ptr);char buff[128] = {0};sprintf(buff, "./mem/%p.mem", ptr);if (unlink(buff) < 0) {printf("double kill:%p\n", ptr);}free(ptr);mem_trace();
}//replaced=1代表正在trace,__malloc_hook走我们自己写的函数
//replaced=0代表没有trace,__malloc_hook走系统自己的函数
void mem_trace(void) { //mtracereplaced = 1;//让malloc_f指向系统的malloc_f = __malloc_hook;//free_ffree_f = __free_hook;//让系统的hook指向我们写的函数__malloc_hook = malloc_hook_f;__free_hook = free_hook_f;
}void mem_untrace(void) {//让系统的hook指向系统的malloc和free__malloc_hook = malloc_f;__free_hook = free_f;replaced = 0;
}int main() {mem_trace();void *p1 = malloc(10);void *p2 = malloc(20);void *p3 = malloc(30);void *p4 = malloc(40);free(p1);free(p2);free(p4);mem_untrace();
}
root@wxf:/tmp/tmp.d4vz2dOyJP# gcc -o three three.c -ldl -g
root@wxf:/tmp/tmp.d4vz2dOyJP# ./three
[+0xc34]--->ptr:0x55b41c3ad260 size:10
[+0xc42]--->ptr:0x55b41c3ad690 size:20
[+0xc50]--->ptr:0x55b41c3ad6b0 size:30
[+0xc5e]--->ptr:0x55b41c3ad6e0 size:40
[-0xc6e]--->ptr:0x55b41c3ad260
[-0xc7a]--->ptr:0x55b41c3ad690
[-0xc86]--->ptr:0x55b41c3ad6e0
root@wxf:/tmp/tmp.d4vz2dOyJP# cd mem/
root@wxf:/tmp/tmp.d4vz2dOyJP/mem# cat 0x55b41c3ad6b0.mem
0x0000000000000c50
main
/tmp/tmp.d4vz2dOyJP/three.c:93

第四版:第三方库mtrace的使用与我们的meme_trace差异

  我们使用第三方库mtrace看看与我们第三版代码使用上有什么区别,我们发现其实没什么区别,只不过它是把所有的malloc和free写到一个文件,而我们的方案是malloc就创建一个文件,free就删除一个文件,大同小异。

//
// Created by 68725 on 2022/8/16.
//
#include <malloc.h>
#include <mcheck.h>int main() {mtrace();void *p1 = malloc(10);void *p2 = malloc(20);void *p3 = malloc(30);void *p4 = malloc(40);free(p1);free(p2);free(p4);muntrace();
}
root@wxf:/tmp/tmp.d4vz2dOyJP# export MALLOC_TRACE=./mtrace.log
root@wxf:/tmp/tmp.d4vz2dOyJP# gcc -o mtrace mtrace.c
root@wxf:/tmp/tmp.d4vz2dOyJP# ./mtrace
root@wxf:/tmp/tmp.d4vz2dOyJP# ls
first     mem        memleak_self.c  mtrace.c    readme.md  second.c  three
cmake-build-debug  CMakeLists.txt    first.c  memleak.c  mtrace      mtrace.log  second     test.c    three.c
root@wxf:/tmp/tmp.d4vz2dOyJP# cat mtrace.log
= Start
@ ./mtrace:[0x558e233f3731] + 0x558e240bf6a0 0xa
@ ./mtrace:[0x558e233f373f] + 0x558e240bf6c0 0x14
@ ./mtrace:[0x558e233f374d] + 0x558e240bf6e0 0x1e
@ ./mtrace:[0x558e233f375b] + 0x558e240bf710 0x28
@ ./mtrace:[0x558e233f376b] - 0x558e240bf6a0
@ ./mtrace:[0x558e233f3777] - 0x558e240bf6c0
@ ./mtrace:[0x558e233f3783] - 0x558e240bf710
= End

全文总结

  本文一共介绍了__libc_malloc, __libc_malloc ,__builtin_return_address,addr2line, __malloc_hook,mtrace。更多的是了解以下这些函数,那么对于内存泄漏检测来说,最核心的两个需求我们也解决了。

  1. 能够检测出来发生了内存泄漏
  2. 能够判断定位代码哪一行引起内存泄漏
  • 如果检测内存泄漏?我们采取malloc创建一个文件,在free的时候删除对应文件

  • 如果定位代码?我们可以使用__builtin_return_address + addr2line,但是更推荐3个宏定义的方法。

手写内存泄漏检测组件相关推荐

  1. android内存泄漏原因分析,Android Studio3.6的内存泄漏检测功能 VS LeakCanary

    2020年2月,谷歌发布了Android Studio 3.6版.它包括一个新的"内存泄漏检测"功能.这是否意味着我们不再需要流行的内存泄漏检测库"Leak Canary ...

  2. Android内存泄漏检测工具使用手册

    Android内存泄漏检测工具使用手册 前言 LeakCanary 在Android中接入LeakCanary LeakCanary内存泄漏分析 内存泄漏上报到服务端 Shark Shark分析当前应 ...

  3. 内存泄漏检测工具:Deleaker 2022

    用于 C++.C#..NET 和 DELPHI 的分析器 与 Visual Studio 完全集成 • 发现任何泄漏:内存.GDI.句柄和其他 • 配置非托管和 .NET 代码 • 支持 32 位和 ...

  4. 内核对象句柄泄漏检测

    自制工具   翰华Box:https://hanhuabox.lanzous.com/b00zjq9uf 翰华Box - 开发日志:https://blog.csdn.net/qq_41517936/ ...

  5. 图文结合纯c手写内存池

    图文结合纯c手写内存池 前言 为什么要用内存池 内存池的使用场景 设计一个内存池 总体介绍 小块内存的分配与管理 大块内存的分配与管理 内存池代码实现 向外提供的api 相关结构体的定义 内存对齐 创 ...

  6. Android 内存泄漏检测开源库LeakCanary 研究

    1. Android 内存空间不足会引发的问题 1.1 异常 1.2 卡顿 1.3 从 Java 堆内存超限这个问题开始 2. 内存优化着手点 2.1 检测 RAM usage 2.2 进程 2.3 ...

  7. Unix下C程序内存泄漏检测工具Valgrind安装与使用

    Valgrind是一款用于内存调试.内存泄漏检测以及性能分析的软件开发工具. Valgrind的最初作者是Julian Seward,他于2006年由于在开发Valgrind上的工作获得了第二届Goo ...

  8. OpenCV中的内存泄漏检测

    转自:http://chaishushan.blog.163.com/blog/static/130192897200911685559809/ 内存泄漏时程序开发中经常遇到的问题. 而且出现内存泄漏 ...

  9. JVM内存泄漏检测与处理

    JVM内存泄漏检测与处理(JVM Memory Leak detection and handling) JVM垃圾回收机制的原则和方法 JVM垃圾回收中一个基本原则是对象没有被引用或则引用其它对象, ...

最新文章

  1. linux 从行查看文件,linux 查看文件内容
  2. tomcat的work目录作用
  3. 什么是java?为什么大家都学习java技术?
  4. 9行代码满分 【C语言】 L1-062 幸运彩票 (15分)
  5. 红橙Darren视频引申 第一次写NDK项目(Android studio 4.1.1)
  6. Java比较两个实体属性值是否相同,将不同的属性输出
  7. KVM详解(一)——KVM基础知识
  8. 淺談auto_ptr
  9. JUC锁框架——ReadWriteLock
  10. python launcher下载_Python flauncher包_程序模块 - PyPI - Python中文网
  11. 家谱族谱软件用云码宗谱
  12. matlab help函数用法,MATLAB函数用法
  13. ioccc_konno
  14. GBT 31000-2015 社会治安综合治理基础数据规范 数据项 编码
  15. html5接金币游戏源码,利用HTML5实现Canvas聚宝盆接金币游戏
  16. Python如何进行语法检查
  17. 电子琴节奏包制作_MIDI音乐制作基础必备
  18. c语言转义字符空格符号,C语言 转义符\t占用几个空格
  19. 深入浅出解析AR/VR/MR三者之间的联系和区别
  20. 为什么博图中放置按下按钮无反应_为什么点击按钮毫无反应

热门文章

  1. qbo web接口分析
  2. 【Linux】linux 查看服务器配置:核数和内存
  3. 【CTF WriteUp】2023数字中国创新大赛网络数据安全赛道决赛WP(1)
  4. 逆变器阻抗扫描 扫频法 阻抗扫描 阻抗建模验证
  5. 5.5.2指令流水线 影响因素分类
  6. 星星之火-38:20M的LTE带宽,为什么是1200个子载波?
  7. 交友盲盒源码h5开发浅谈
  8. OSPF ISIS RIP实现总公司与分公司等的互联
  9. windows下vue项目启动步骤
  10. Rancher 2.x 搭建及管理 Kubernetes 集群