动手实现内存泄漏检测组件

简介: 动手实现内存泄漏检测组件

c/c++没有垃圾回收机制,因此可能会出现内存泄漏

出现原因:内存分配与内存释放,没有做到匹配

如何知道内存泄漏:

每次malloc/calloc/realloc 就 +1

每次free 就 -1

如果正常退出,不为0,说明存在内存泄漏

如何定位哪一行引起内存泄漏

1.c自带的宏,__FILE___ FUNCTION _和、__LINE__,可以定位到具体文件,函数,哪一行

2.builtin_return_address(),返回函数在哪个地方调用的代码段地址(后续通过addr2line命令解析出现的位置)

如何去实现

通过hook的方法,可以在不修改原来代码的情况下,进行检测。

方法一:dlsym实现hook

如何记录呢,每次malloc都创建一个文件,每次free就删除文件,运行完后,看有多少文件就行了。

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);
    void *caller = __builtin_return_address(1); // 1
    char buff[128] = {0};//用于存放文件名
    sprintf(buff, "./mem/%p.mem", p);
    FILE *fp = fopen(buff, "w");
    fprintf(fp, "[+%p] --> addr:%p, size:%ld\n", caller, p, size);//将调用malloc的位置信息 写入到 文件流中
    fflush(fp);//强迫将缓冲区内的数据写回参数stream指定的文件中
    //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  删除该文件,如果小于0就代表不存在
      printf("double free: %p\n", p);
    }
    __libc_free(p);
    // rm -rf p.mem
    enable_free_hook = 1;
  } else {
    __libc_free(p);
  }
}

编译前要加上-g

因此可以通过查看目录下有没有 ./mem/xxxxxxx.mem的文件,有的话就说明内存泄漏了。

void *caller = __builtin_return_address(1);的返回值caller就是 代码段的地址(文章最后有介绍参数)。

然后通过以下命令(addr2line)(-e表示execute -a表示代码段地址)可以查看 内存泄漏的行数

addr2line -f -e memleak -a 0x4009f7

注意点1:

自己实现一个malloc,里面执行printf,发现一直在malloc里面循环,因为printf本身内部就实现了一个malloc,这样就会陷入一个循环

size=10是我们调用的,1024是printf内部调用的

malloc内部调用__libc_malloc这个函数

因此通过enable_malloc_hook来标识,是否使用自己定义的malloc,在printf的前enable_malloc_hook置为0,printf后enable_malloc_hook=1,来避免这个问题

注意点2:

这边不能添加fclose(); 因为fclose()内部就调用了free

如果执行fclose(fp)这样导致会free(fclose内部调用了free)掉一个系统指针,而这个指针本身就是不存在的,因此会报double free的错误。

方法二:宏定义实现hook

通过宏定义的方式,实现hook。

实现的方式是一样的,还是通过文件保存与删除的方式来记录是否有内存泄漏,只是换成了__FILE__, __LINE__(系统提供的宏定义),获取当前的文件和行数。

void *malloc_hook(size_t size, const char *file, int line) {
  void *p = malloc(size);
  char buff[128] = {0};
  sprintf(buff, "./mem/%p.mem", p);
  FILE *fp = fopen(buff, "w");
  fprintf(fp, "[+%s:%d] --> addr:%p, size:%ld\n", file, line, p, size);
  fflush(fp); 
  fclose(fp);//这种方法里面是可以用fclose的
  return p;
}
void free_hook(void *p, const char *file, int line) {
  char buff[128] = {0};
  sprintf(buff, "./mem/%p.mem", p);
  if (unlink(buff) < 0) { // no exist
    printf("double free: %p\n", p);
    return ;
  }
  free(p);
}
//宏定义,这样子就很方便,想要调试,想知道内存泄漏的时候,可以开启debug,不测试可以关掉
//不能放在前面,因为printf会调用malloc,会导致陷入死循环
#if 1
#define malloc(size)  malloc_hook(size, __FILE__, __LINE__)
#define free(p)     free_hook(p, __FILE__, __LINE__)
#endif

方式三:修改__malloc_hook和 __free_hook

其中caller就是代码段的地址,通过addr2line命令可以获取 出现内存泄漏的行号。

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;//为0代表指向系统自带的,如果为1就代表指向自己实现的hook
void mem_trace(void);
void mem_untrace(void);
void *malloc_hook_f(size_t size, const void *caller) {
  mem_untrace();//解除hook,不然下面会继续执行自己的malloc_hook会导致死循环
  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:%ld\n", caller, ptr, size);//caller就是代码段的地址 用addr2line命令查看行数
  fflush(fp);
  fclose(fp); //free
  mem_trace();//开启hook
  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: %p\n", p);
    return ;
  }
  free(p);
  mem_trace();
}
void mem_trace(void) { //mtrace
  replaced = 1;
  malloc_f = __malloc_hook; //将 系统调用的malloc_hook存储起来
  free_f = __free_hook;//将 系统调用的free_hook存储起来
  __malloc_hook = malloc_hook_f;//用自己实现的malloc_hook  取代
  __free_hook = free_hook_f;//用自己实现的free_hook 取代
}
void mem_untrace(void) {
  __malloc_hook = malloc_f;//恢复
  __free_hook = free_f;
  replaced = 0;
}

测试

mem_trace();
void *p1 = malloc(10);
void *p2 = malloc(20); //calloc, realloc
free(p1);
void *p3 = malloc(20);
void *p4 = malloc(20);
free(p2);
free(p4);
mem_untrace();

方式四:mtrace

使用mtrace进行内存泄漏检测

补充:

1. __builtin_return_address()使用

返回的是函数代码段的地址

__builtin_return_address()中的参数,0代表调用的地方,1代表调用的上一级栈,2代表调用的上两级栈

func--->maloc() {__builtin_return_address(0)}
main-->func--->maloc() {__builtin_return_address(1)}


相关文章
|
2月前
|
缓存 监控 Python
在Python中,如何检测和处理内存泄漏?
【2月更文挑战第7天】【2月更文挑战第18篇】在Python中,如何检测和处理内存泄漏?
|
3月前
|
安全 Linux 编译器
内存泄漏检测组件的分析与实现(linux c)-mtrace工具使用
内存泄漏产生原因 在堆上使用malloc/remalloc/calloc分配了内存空间,但是没有使用free释放对应的空间。
75 0
|
3月前
使用mtrace进行内存泄漏检测
使用mtrace进行内存泄漏检测
76 1
|
1月前
|
IDE Linux 开发工具
内存泄漏检测工具Valgrind:C++代码问题检测的利器(一)
内存泄漏检测工具Valgrind:C++代码问题检测的利器
90 0
|
1月前
|
缓存 Linux iOS开发
【C/C++ 集成内存调试、内存泄漏检测和性能分析的工具 Valgrind 】Linux 下 Valgrind 工具的全面使用指南
【C/C++ 集成内存调试、内存泄漏检测和性能分析的工具 Valgrind 】Linux 下 Valgrind 工具的全面使用指南
66 1
|
1月前
|
缓存 测试技术 开发工具
内存泄漏检测工具Valgrind:C++代码问题检测的利器(二)
内存泄漏检测工具Valgrind:C++代码问题检测的利器
35 0
|
1月前
|
Python
在Python中,如何检测和修复内存泄漏?
在Python中,如何检测和修复内存泄漏?
103 0
|
3月前
|
Web App开发 前端开发 JavaScript
JavaScript 内存泄漏的检测与防范:让你的程序更稳定
JavaScript 内存泄漏的检测与防范:让你的程序更稳定
JavaScript 内存泄漏的检测与防范:让你的程序更稳定
|
3月前
|
缓存 算法 Linux
Linux 内存泄漏检测的基本原理
Linux 内存泄漏检测的基本原理
112 0
|
1月前
|
存储 JSON 监控
Higress Controller**不是将配置信息推送到Istio的内存存储里面的**。
【2月更文挑战第30天】Higress Controller**不是将配置信息推送到Istio的内存存储里面的**。
14 1