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

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

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)}


相关文章
|
28天前
|
监控 JavaScript Java
Node.js中内存泄漏的检测方法
检测内存泄漏需要综合运用多种方法,并结合实际的应用场景和代码特点进行分析。及时发现和解决内存泄漏问题,可以提高应用的稳定性和性能,避免潜在的风险和故障。同时,不断学习和掌握内存管理的知识,也是有效预防内存泄漏的重要途径。
122 52
|
1月前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
49 6
|
1月前
|
Web App开发 JavaScript 前端开发
使用 Chrome 浏览器的内存分析工具来检测 JavaScript 中的内存泄漏
【10月更文挑战第25天】利用 Chrome 浏览器的内存分析工具,可以较为准确地检测 JavaScript 中的内存泄漏问题,并帮助我们找出潜在的泄漏点,以便采取相应的解决措施。
250 9
|
1月前
|
监控 JavaScript 前端开发
如何检测和解决 JavaScript 中内存泄漏问题
【10月更文挑战第25天】解决内存泄漏问题需要对代码有深入的理解和细致的排查。同时,不断优化和改进代码的结构和逻辑也是预防内存泄漏的重要措施。
57 6
|
1月前
|
Web App开发 缓存 JavaScript
如何检测和解决闭包引起的内存泄露
闭包引起的内存泄露是JavaScript开发中常见的问题。本文介绍了闭包导致内存泄露的原因,以及如何通过工具检测和代码优化来解决这些问题。
|
2月前
|
Web App开发 开发者
|
2月前
|
缓存 监控 Java
内存泄漏:深入理解、检测与解决
【10月更文挑战第19天】内存泄漏:深入理解、检测与解决
85 0
|
2月前
|
设计模式 Java Android开发
安卓应用开发中的内存泄漏检测与修复
【9月更文挑战第30天】在安卓应用开发过程中,内存泄漏是一个常见而又棘手的问题。它不仅会导致应用运行缓慢,还可能引发应用崩溃,严重影响用户体验。本文将深入探讨如何检测和修复内存泄漏,以提升应用性能和稳定性。我们将通过一个具体的代码示例,展示如何使用Android Studio的Memory Profiler工具来定位内存泄漏,并介绍几种常见的内存泄漏场景及其解决方案。无论你是初学者还是有经验的开发者,这篇文章都将为你提供实用的技巧和方法,帮助你打造更优质的安卓应用。
|
2月前
|
数据处理 Python
Python读取大文件的“坑“与内存占用检测
Python读取大文件的“坑“与内存占用检测
69 0
|
2月前
|
存储 算法 C语言
MacOS环境-手写操作系统-15-内核管理 检测可用内存
MacOS环境-手写操作系统-15-内核管理 检测可用内存
47 0