虚拟内存
线上内存泄漏,产生的原因是malloc/new没有与之匹配的free/delete,导致htop/top虚拟内存一直在涨,只有整个进程退出的时候,才会回收内存
malloc和new在堆上分配内存,而堆栈都是虚拟地址空间,因此内存泄漏指定的是虚拟内存泄漏,4G的物理内存,泄漏的虚拟内存可能达到9个G
tip:栈会出现内存覆盖问题,比如函数中临时数组太长了,把函数返回地址覆盖了.
解决:首先通过htop/top检查是否有大片内存泄漏,然后要确定哪一行内存泄漏
。
检测方法
首先需要知道malloc的实现:malloc首先会检查是否需要执行钩子函数,然后调用__libc_malloc(内部调用__init_malloc)分配内存。
据此,检测内存泄漏的根本思路就是:调用malloc时能打印调用时的行信息:
- 我们可以重写malloc函数,其最核心的 __init_malloc 部分保留,但可以额外打印内存信息。(类似的就是在C++中对new进行重载,然后调用到内存池)
- 由于sprintf,printf,fclose等打印函数会调用malloc或free,直接重写malloc会导致临时指针free时,出现double free的多余打印信息,出现多余的打印信息,因此,可以将malloc/free定义处改成__malloc/__free,并在最后重定义 maolloc 为 __malloc。(相当于对malloc多做了一层内存分配打印信息的封装)
- 重写钩子函数,在钩子函数内打印内存信息,并用一个函数指针保存原钩子函数地址,以实现钩子函数替换的开启和关闭。(最为安全灵活的方法)
- 直接使用mtrace组件
#define _GNU_SOURCE // 使用dlsym,MAP_ANON,要先定义 _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <malloc.h> // 系统自带的钩子函数__malloc_hook
#include <mcheck.h> // mtrace组件
#include <fcntl.h>
#if 0
// 方案一:重写malloc
// malloc底层就是调用的__lib_malloc,它又调用了__int_malloc函数(真正分配内存的函数)
// 这里不extern的话,编译会warning:未定义的函数
// extern表示外部的,在此处不分配空间,用于链接时查找
int enable_malloc_hook = 1;
extern void *__libc_malloc(size_t size);
int enable_free_hook = 1;
extern void *__libc_free(void *p);
void *malloc(size_t size) {
if (enable_malloc_hook) { // Tip1: 用于打破malloc递归调用
enable_malloc_hook = 0;
void *p = __libc_malloc(size);
// 查看地址所在行:addr2line -f -e memleak -a 0x4006e6
void *caller = __builtin_return_address(0); // Tip2: 返回函数第n层调用处地址
char buff[128] = {0};
sprintf(buff, "./mem/%p.mem", p); // 会调用free(),打印double free信息,空指针
FILE *fp = fopen(buff, "w");
fprintf(fp, "[+%p]malloc --> addr:%p size:%lu\n", caller, p, size);
fflush(fp);
fclose(fp); // 会调用free(),打印double 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) {
printf("double free: %p\n", p);
}
__libc_free(p);
enable_free_hook = 1;
} else {
__libc_free(p);
}
}
#elif 0
// 方案二:利用宏定义
void *_malloc(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]malloc --> addr:%p size:%lu\n", file, line, p, size);
fflush(fp);
fclose(fp);
return p;
}
void _free(void *p, const char *file, int line)
{
char buff[128] = {0};
sprintf(buff, "./mem/%p.mem", p);
if (unlink(buff) < 0)
{
printf("double free: %p\n", p);
return;
}
free(p);
}
#define malloc(size) _malloc(size, __FILE__, __LINE__)
#define free(p) _free(p, __FILE__, __LINE__)
#elif 0
// 方案三:使用malloc.h中提供的hook: __malloc_hook, __free_hook.
typedef void *(*malloc_hoot_t)(size_t size, const void *caller);
malloc_hoot_t malloc_f;
typedef void (*free_hook_t)(void *p, const void *caller);
free_hook_t free_f;
void mem_trace(void);
void mem_untrace(void);
void *malloc_hook_f(size_t size, const void *caller)
{
mem_untrace();
// Tip3: malloc前后调用mem_untrace()和mem_trace();也是为了打破递归调用(钩子函数的递归调用)
// 调用malloc_hook_f时,会调用malloc,然后又调用我们的malloc_hook_f,造成递归调用
// 因此,在maolloc前需要开启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]malloc --> addr:%p size:%lu\n", caller, ptr, size);
fflush(fp);
fclose(fp);
mem_trace(); // 下次malloc继续调我们的钩子
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)
{
printf("double free: %p\n", p);
}
free(p);
mem_trace();
}
void mem_trace(void)
{
// 更改系统的malloc指向,调用malloc时调用我们的malloc_f
malloc_f = __malloc_hook; // 保存__malloc_hook地址
free_f = __free_hook;
__malloc_hook = malloc_hook_f; // malloc调用我们的malloc_hook_f
__free_hook = free_hook_f;
}
void mem_untrace(void)
{
__malloc_hook = malloc_f;
__free_hook = free_f;
}
// 也可以使用dlsym对malloc,free进行hook,实现类似的效果。
#elif 0
// 方案四: mtrace() 组件
// 生成一个日志文件,记录哪些内存被申请,哪些内存被释放
#endif
int main()
{
#if 0
// 方案一、二
void *p1 = malloc(10);
// free(p1);
void *p2 = malloc(20); // 如果p1在p2申请之前释放了,系统会吧p1的地址块分给p2
free(p1);
free(p1); // double free
#elif 0
// 方案三
mem_trace();
void *p1 = malloc(10);
// free(p1);
void *p2 = malloc(20);
free(p1);
mem_untrace();
#elif 1
// 方案四:组件
mtrace();
void *p1 = malloc(10);
void *p2 = malloc(20);
free(p1);
void *p3 = malloc(30);
void *p4 = malloc(40);
free(p2);
free(p4);
muntrace();
#endif
return 0;
}
编译,运行:gcc -o memleak memleak.c -ldl -g
,./memleak