内存泄漏原因
- 代码不带垃圾回收机制,此问题解决方式参考内存池应用。
- 动态分配(malloc、new)了内存没用释放(free、delete)
危害
- 导致某些内存被系统强制回收
- 进程可能被强制kill
- malloc或者new等动态内存分配失败
解决方案
如何知道内存泄漏
如上述内存泄漏原因中所说的第二点,动态分配释放未能够成对出现。
那么我们能否对内存分配以及释放,进行监管。
监管内存分配或者释放的两大类方式
- 自己定制mlloc、free方案(宏定义)。
file、func、LINE - 使用builtin_return_address函数进行定位,该接口是系统接口,可以得到当前运行的环境,调用该接口是在地址跳转过去寻找定位,或者前X段调用接口的地址。 随后可以通过addr2line -f -e exe -a address将该地址对应的代码路径读取出来。 ==值得注意的是:==为了通过addr2line将地址对于的代码段读出来,需要做两件事
2.1 将环境中的随机栈地址disable了
2.2 gcc编译时候加上参数 -g
手动实现工具
方案一
- 使用宏定义,对malloc以及free进行重新定义
- 在自定义的malloc 中记录调用malloc的信息,包括调用malloc的当前文件当前行,开辟出来的地址以及空间大小
- 在free中将释放掉的空间对应的文件进行删除
void *malloc_hook(size_t size, const char *file, const char *func, int line)
{
void *p = malloc(size);
char str[256] = {
0};
sprintf(str, "./mem/%p.mem", p);
FILE *fp = fopen(str, "w");
fprintf(fp, "[info:]file:%s,func:%s,line:%d, addr:%p,size:%ld\n", file, func, line, p, size);
fflush(fp);
fclose(fp);
return p;
}
void free_hook(void *p, const char *file, const char *func, int line)
{
free(p);
char str[256] = {
0};
sprintf(str, "./mem/%p.mem", p);
if (unlink(str) < 0)
{
printf("double free.\n");
}
}
#define malloc(size) malloc_hook(size, __FILE__, __FUNCTION__, __LINE__)
#define free(p) free_hook(p, __FILE__, __FUNCTION__, __LINE__);
方案二
- 使用钩子函数将malloc、free的内容重新定义
- 第一步,先将系统函数的malloc、free的实现函数地址进行保存(mem_trace)
- 第二步,将为我们自己实现的malloc、free函数地址替换进去
- 第三步,将malloc与free的实现函数换回系统自定义的接口(mem_untrace)
- 第四步, 由于自己实现的函数中可能会存在开辟空间的动作,为了防止无限回调,需要在进入的时候,就调用mem_untrace切换回系统接口,退出时候,调用mem_trace切换回自己的接口
- 第五步,记得在需要debug的那部分位置处加上mem_trace以及mem_untrace
typedef void *(*malloc_hook_t)(size_t size, const void *caller); //系统的__malloc_hook实际的函数类型
malloc_hook_t malloc_f; //用于保存系统默认的__malloc_hook函数地址
typedef void (*free_hook_t)(void *p, const void *caller); //系统的__free_hook的实际函数类型
free_hook_t free_f; //用于保存系统默认的__free_hook函数指针地址
int replaced = 0; //如果为1,malloc/free指向我们自定义的函数
void mem_trace(void); //让其malloc指向我们自己定义的函数
void mem_untrace(void); //让其free指向我们自己定义的函数
//自定义的malloc函数,与系统的__malloc_hook保持一致
//caller参数代表调用该函数的地址(__builtin_return_address(0)返回的地址就是这个地址)
void *malloc_hook_f(size_t size, const void *caller)
{
//防止递归-如果不加这句,会让下面的malloc继续执行malloc_hook_f,从而造成递归
//我们只要得到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:%ld\n", caller, ptr, size);
fflush(fp);
fclose(fp); //free
mem_trace(); //保证下次malloc还是用我们自定义的
return ptr;
}
void free_hook_f(void *p, const void *caller)
{
mem_untrace(); //防止free函数递归
//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是系统本身提供的函数指针(会在malloc调用时初始化)
free_f = __free_hook; //__free_hook是系统本身提供的(free调用时会初始化)
__malloc_hook = malloc_hook_f; //指向我们自定义函数,malloc会调用我们定义的函数
__free_hook = free_hook_f; //指向我们自定义函数,free会调用我们自定义的函数
}
//
void mem_untrace(void)
{
__malloc_hook = malloc_f;
__free_hook = free_f;
replaced = 0;
}
测试代码
mem_trace();
void *p1 = malloc(10);
void *p2 = malloc(20);
free(p1);
mem_untrace();
方案三
与方案二类似,只是不使用钩子函数,而是自己重新创建malloc与free接口
由于malloc与free的实际开辟释放动作是由==__libc_malloc==、__libc_free完成的,所以我们建立的malloc与free接口只需要实际调用这两个接口就可以了
extern void *__libc_malloc(size_t size); //malloc.h里面定义的
int enable_malloc_hook = 1; //终止递归的变量(具体参考malloc函数说明)
extern void __libc_free(void *p);
int enable_free_hook = 1;
void *malloc(size_t size)
{
if (enable_malloc_hook)
{
//调用系统
enable_malloc_hook = 0;
void *p = __libc_malloc(size); //分配内存,系统的malloc实际上也是调用的这个api进行内存分配。
//返回malloc调用完成时的地址,可以结合addr2line命令定位到哪一行内存泄露。
void *caller = __builtin_return_address(0);
char buff[128] = {
0};
sprintf(buff, "./mem/%p.mem", p); //使用malloc返回的地址作为文件名(p.mem)
FILE *fp = fopen(buff, "w");
fprintf(fp, "[+%p] --> addr:%p, size:%ld\n", caller, p, size);
fflush(fp);
//fclose(fp);//注意不能close文件
//printf函数内部会调用malloc,如果不用enable_malloc_hook变量会导致递归malloc的使用
printf("malloc :%p\n", p);
//保证下次调用malloc进入到if,注意,多线程不是线程安全的
enable_malloc_hook = 1;
return p;
}
else
{
//如果是其他API(比如printf)调用了malloc,会直接调用__lib_malloc(size_t size)进行分配,从而使得递归得以退出
return __libc_malloc(size);
}
}
void free(void *p)
{
if (enable_free_hook)
{
//调用free
enable_free_hook = 0;
__libc_free(p);
char buff[128] = {
0};
sprintf(buff, "./mem/%p.mem", p);
if (unlink(buff) < 0) //删除文件,如果返回小于0,说明释放了2次
{
printf("double free:%p\n", p);
}
printf("free:%p\n", p);
enable_free_hook = 1; //保证下次free,还走if
}
else
{
//其他系统API调用会直接调用__libc_free.
__libc_free(p);
}
}
测试代码
enable_malloc_hook = 1;
void *p1 = malloc(10);
void *p2 = malloc(20);
int d = ccccc + ccccc;
free(p1);
现有工具介绍
- valgrind
- mtrace
其中mtrace的使用
mtrace();
void *p1 = malloc(10);
void *p2 = malloc(20); //
free(p1);
muntrace();