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

动手实现内存泄漏检测组件,第1张

动手实现class="superseo">内存泄漏检测组件
  • 方法一:dlsym实现hook
  • 方法二:宏定义实现hook
  • 方式三:修改__malloc_hook和 __free_hook
  • 方式四:mtrace
  • 补充:
    • 1. __builtin_return_address()使用

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

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/662742.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-18
下一篇 2022-04-18

发表评论

登录后才能评论

评论列表(0条)

保存