博客园  :: 首页  :: 新随笔  :: 管理

3.1.3 内存泄漏检测组件

Posted on 2023-03-12 04:12  wsg_blog  阅读(20)  评论(0编辑  收藏  举报

Linux C/C++服务器

内存泄漏检测组件

调用malloc或者new,没有调用free或delete
内存泄漏检测组件

  • 如何判断是否有内存泄漏
  • 到底在哪行代码泄漏

本文介绍三种内存泄漏检测的方法

  1. mtrace
  2. 宏定义
  3. hook malloc

mtrace

mtrace是系统自带的内存检测接口,引入mcheck.h头文件就可使用,使用也比较简单

#include <stdlib.h>
#include <stdio.h>
#include <mcheck.h>

int main() {
        mtrace();

	void *p1 = malloc(10);
	void *p2 = malloc(15);
	void *p3 = malloc(20);
	free(p2);
	free(p3);
	
	muntrace();
}

执行过程

gcc -o memleak memleak.c -g
export MALLOC_TRACE=./mem.txt
./memleak
cat mem.txt
addr2line -f -e memleak -a 0x400ad0

低版本可行,在高版本中不会生成mem.txt文件

宏定义

只适合在单文件内,把它放在文件的最前面

为什么main函数中的malloc调用的是宏定义,而void _malloc()内调用的却不是宏定义?
预编译:1.把代码的注释取消 2.宏定义展开,把宏定义直接搬过来,所以在打印行号的时候不会出问题
编译:把各个字符转代码文件换成各个二进制文件
链接:把各个文件的代码模块组装起来

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

void *_malloc(size_t size, const char *filename, 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", filename, line, p, size);
	fflush(fp);
	fclose(fp);
	//printf("[+]%s:%d, %p\n", filename, line, p);

	return p;
}

void _free(void *p, const char *filename, int line) {
	//printf("[-]%s:%d, %p\n", filename, line, p);

	char buff[128] = {0};
	sprintf(buff, "./mem/%p.mem", p);

	if (unlink(buff) < 0) {		//删除文件
		printf("double free: %p\n", p);
		return ;
	}
	
	return free(p);
}

#define malloc(size)   _malloc(size, __FILE__, __LINE__)
#define free(size)   _free(size, __FILE__, __LINE__)

int main() {
	void *p1 = malloc(10);  //_malloc(size, __FILE__, __LINE__)
	void *p2 = malloc(15);
	void *p3 = malloc(20);

	free(p2);
	free(p3);

	return 0;
}

执行过程

gcc -o memleak memleak.c -g
mkdir mem
./memleak

hook

hook接口在调试追踪代码中使用的较多,主要是通过dlsym()和typedef函数指针类型来实现,在之前死锁检测组件中我们也有使用和详细说明

#define _GNU_SOURCE
#include <dlfcn.h>

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

typedef void *(*malloc_t)(size_t size);
typedef void (*free_t)(void *ptr);

malloc_t malloc_f = NULL;
free_t free_f = NULL;

int enable_malloc_hook = 1;

void *malloc(size_t size) {
	void *p = NULL;
	if(enable_malloc_hook){
		enable_malloc_hook = 0;
		//printf内部也会调用malloc,如果不加enable_malloc_hook标志位终止条件,将导致递归出现段错误
		//printf("size: %ld\n", size);
		p = malloc(size);
		void *caller = __builtin_return_address(0);	//可返回是哪里调用的本函数
		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);
		fflush(fp);
		fclose(fp);
		enable_malloc_hook = 1;
	}else{
		p = malloc(size);
	}
	return p;
}

void free(void *ptr) {
	if(enable_malloc_hook){
		enable_malloc_hook = 0;
		//printf("ptr: %p\n", ptr);
		char buff[128] = {0};
		sprintf(buff, "./mem/%p.mem", ptr);

		if (unlink(buff) < 0) {
			printf("double free: %p\n", ptr);
			return ;
		}
		free_f(ptr);
		enable_malloc_hook = 1;
	}
}

static void init_hook(void) {
	if (malloc_f == NULL) {
		malloc_f = (malloc_t)dlsym(RTLD_NEXT, "malloc");
	}	
	if (free_f == NULL) {
		free_f = (free_t)dlsym(RTLD_NEXT, "free");
	}
}

int main() {
	init_hook();	//3.hook

	void *p1 = malloc(10);
	void *p2 = malloc(15);
	void *p3 = malloc(20);
	free(p2);
	free(p3);

	return 0;
}

执行过程

gcc -o memleak memleak.c -g -ldl
mkdir mem
./memleak
cat mem/0x55818bb172a0.mem
addr2line -f -e memleak -a 0x1651

以上只是给出了检测内存泄漏的方法,但实际工程中检测内存泄漏要复杂的多,而且并不能100%的检测出来