c++ 死锁检测与内存泄露

admin2024-04-03  1

死锁检测与内存泄露
死锁检测
死锁

死锁是多线程并发执行时,竞争对方持有的资源而无法向前继续执行;一般线程进程长时间处于BLOCKED状态可能存在死锁的情况;

预防死锁

​ 1. 避免循环等待条件:按照固定顺序申请资源、确保不会形成环形等待

​ 2. 设置超时:在尝试获取锁时设置一个较为合理的超时时间;超时后释放已经获取的资;

死锁检测

对于正在运行时的程序或者进程,可以采用外部的工具进行观察进程状态和系统调用情况、检查是否有线程卡在lock操作上无法继续执行:

gdb调试:

	1. 使用 info threads命令,显示所有线程状态以及其堆栈跟踪,找到阻塞在互斥量上的线程
	2. 使用thread thread_id进入线程,bt或者where查看当前堆栈信息
	3. 查找等待锁或者条件变量的线程,若有多个线程在彼此等待对方持有的资源就是死锁

pstack命令:

	1. 可以打印当前所有线程的堆栈信息,可以等大一下多次获取堆栈信息,查看是否存在某几个线程的堆栈信息么有变化,查看时候在相互等待获取对方持有的资源
	2. 使用pstack id可以打印指定线程的堆栈信息

strace命令:

	1. strace时一个强大的系统调用追踪工具,它可以跟踪进程执行时的系统调用和信号
	2. 使用是trace检查死锁时,关注fatex系统调用,它通常用于实现pthread_mutex_lock等多线程同步原语
	3. strace -p pid 查看输出,若看到大量的线程反复尝试获取某个futex而失败,且没有线程释放,可能存在死锁

perf监控:

perf时linux系统自带的系统性能分析工具,主要用来分析系统的性能瓶颈,可以通过监控线程状态和cpu占用情况开间接判断是否存在死锁

valgrind + helgrind:

  1. valgrind是一款内存错误检测工具,而helgrind是valgrind工具集成的一款工具,用于检测多线程程序中的竞争条件和死锁;

  2. helgrind通过获取模拟死锁的获取和释放来检测线程之间是否存在非法的资源访问序列,从而查找可能出现死锁的编程错误

  3. valgrind --tool=helgrind ./your_program
    

编程通过hook函数将锁的获取和释放情况管理,分析出现死锁的线程函数

// build: gcc -o deadlock deadlock.c -lpthread -ldl

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <stdint.h>

#if 1
typedef unsigned long int uint64;


#define MAX		100

enum Type {PROCESS, RESOURCE};

struct source_type {

	uint64 id;
	enum Type type;

	uint64 lock_id;
	int degress;
};

struct vertex {

	struct source_type s;
	struct vertex *next;

};

struct task_graph {

	struct vertex list[MAX];
	int num;

	struct source_type locklist[MAX];
	int lockidx; //

	pthread_mutex_t mutex;
};

struct task_graph *tg = NULL;
int path[MAX+1];
int visited[MAX];
int k = 0;
int deadlock = 0;

struct vertex *create_vertex(struct source_type type) {
	struct vertex *tex = (struct vertex *)malloc(sizeof(struct vertex ));
	tex->s = type;
	tex->next = NULL;
	return tex;
}

int search_vertex(struct source_type type) {
	int i = 0;
	for (i = 0;i < tg->num;i ++) {
		if (tg->list[i].s.type == type.type && tg->list[i].s.id == type.id) {
			return i;
		}
	}
	return -1;
}

void add_vertex(struct source_type type) {

	if (search_vertex(type) == -1) {

		tg->list[tg->num].s = type;
		tg->list[tg->num].next = NULL;
		tg->num ++;
	}
}

int add_edge(struct source_type from, struct source_type to) {
	add_vertex(from);
	add_vertex(to);
	struct vertex *v = &(tg->list[search_vertex(from)]);

	while (v->next != NULL) {
		v = v->next;
	}
	v->next = create_vertex(to);
}

int verify_edge(struct source_type i, struct source_type j) {

	if (tg->num == 0) return 0;

	int idx = search_vertex(i);
	if (idx == -1) {
		return 0;
	}

	struct vertex *v = &(tg->list[idx]);
	while (v != NULL) {

		if (v->s.id == j.id) return 1;

		v = v->next;	
	}

	return 0;
}

int remove_edge(struct source_type from, struct source_type to) {

	int idxi = search_vertex(from);
	int idxj = search_vertex(to);

	if (idxi != -1 && idxj != -1) {

		struct vertex *v = &tg->list[idxi];
		struct vertex *remove;

		while (v->next != NULL) {

			if (v->next->s.id == to.id) {

				remove = v->next;
				v->next = v->next->next;

				free(remove);
				break;
			}
			v = v->next;
		}
	}
}


void print_deadlock(void) {
	int i = 0;
	printf("cycle : ");
	for (i = 0;i < k-1;i ++) {
		printf("%ld --> ", tg->list[path[i]].s.id);
	}

	printf("%ld\n", tg->list[path[i]].s.id);
}

int DFS(int idx) {
	struct vertex *ver = &tg->list[idx];
	if (visited[idx] == 1) {

		path[k++] = idx;
		print_deadlock();
		deadlock = 1;
		
		return 0;
	}
	visited[idx] = 1;
	path[k++] = idx;

	while (ver->next != NULL) {

		DFS(search_vertex(ver->next->s));
		k --;
		
		ver = ver->next;
	}
	return 1;
}

int search_for_cycle(int idx) {
	struct vertex *ver = &tg->list[idx];
	visited[idx] = 1;
	k = 0;
	path[k++] = idx;

	while (ver->next != NULL) {
		int i = 0;
		for (i = 0;i < tg->num;i ++) {
			if (i == idx) continue;
			
			visited[i] = 0;
		}
		for (i = 1;i <= MAX;i ++) {
			path[i] = -1;
		}
		k = 1;
		DFS(search_vertex(ver->next->s));
		ver = ver->next;
	}

}

#endif

#if 1
int search_lock(uint64 lock) {
	int i = 0;
	for (i = 0;i < tg->lockidx;i ++) {
		
		if (tg->locklist[i].lock_id == lock) {
			return i;
		}
	}
	return -1;
}

int search_empty_lock(uint64 lock) {
	int i = 0;
	for (i = 0;i < tg->lockidx;i ++) {
		
		if (tg->locklist[i].lock_id == 0) {
			return i;
		}
	}
	return tg->lockidx;
}

void lock_before(uint64_t tid, uint64_t lockaddr) {
	/*
	1. 	if (lockaddr) {
			tid --> lockaddr.tid;
	   	}
	*/

	int idx = 0;

	for (idx = 0;idx < tg->lockidx;idx ++) {

		if (tg->locklist[idx].lock_id == lockaddr) { // 

			struct source_type from;
			from.id = tid;
			from.type = PROCESS;
			add_vertex(from);

			struct source_type to;
			to.id = tg->locklist[idx].id;
			to.type = PROCESS;
			add_vertex(to);

			tg->locklist[idx].degress ++;
			if (!verify_edge(from, to))
				add_edge(from, to);
		}
	}
}

void lock_after(uint64_t tid, uint64_t lockaddr) {
	/*
		if (!lockaddr) {

			tid --> lockaddr;

		} else {

			lockaddr.tid = tid;
			tid -> lockaddr;

		}
		
	 */
	int idx = 0;
	if (-1 == (idx = search_lock(lockaddr))) {// 
		int eidx = search_empty_lock(lockaddr);

		tg->locklist[eidx].id = tid;
		tg->locklist[eidx].lock_id = lockaddr;

		tg->lockidx ++;
	} else {
		struct source_type from;
		from.id = tid;
		from.type = PROCESS;
		add_vertex(from);

		struct source_type to;
		to.id = tg->locklist[idx].id;
		to.type = PROCESS;
		add_vertex(to);

		tg->locklist[idx].degress --;

		if (verify_edge(from, to))
			remove_edge(from, to);

		tg->locklist[idx].id = tid;
		
	}
}


void unlock_after(uint64_t tid, uint64_t lockaddr) {
	int idx = search_lock(lockaddr);
	if (tg->locklist[idx].degress == 0) {
		tg->locklist[idx].id = 0;
		tg->locklist[idx].lock_id = 0;
	}
}


void check_dead_lock(void) {
	int i = 0;
	deadlock = 0;
	for (i = 0;i < tg->num;i ++) {
		if (deadlock == 1) break;
		search_for_cycle(i);
	}
	if (deadlock == 0) {
		printf("no deadlock\n");
	}
}

static void *thread_routine(void *args) {
	while (1) {
		sleep(5);
		check_dead_lock();
	}
}


void start_check(void) {
	tg = (struct task_graph*)malloc(sizeof(struct task_graph));
	tg->num = 0;
	tg->lockidx = 0;
	pthread_t tid;
	pthread_create(&tid, NULL, thread_routine, NULL);
}


// hook
// define
typedef int (*pthread_mutex_lock_t)(pthread_mutex_t *mutex);
pthread_mutex_lock_t pthread_mutex_lock_f = NULL;

typedef int (*pthread_mutex_unlock_t)(pthread_mutex_t *mutex);
pthread_mutex_unlock_t pthread_mutex_unlock_f = NULL;


// implement
int pthread_mutex_lock(pthread_mutex_t *mutex) {
	pthread_t selfid = pthread_self();
	
	lock_before((uint64_t)selfid, (uint64_t)mutex);
	
	pthread_mutex_lock_f(mutex);

	lock_after((uint64_t)selfid, (uint64_t)mutex);
}

int pthread_mutex_unlock(pthread_mutex_t *mutex) {

	pthread_mutex_unlock_f(mutex);

	pthread_t selfid = pthread_self();
	unlock_after((uint64_t)selfid, (uint64_t)mutex);
}

// init
void init_hook(void) {
	if (!pthread_mutex_lock_f)
		pthread_mutex_lock_f = dlsym(RTLD_NEXT, "pthread_mutex_lock");

	if (!pthread_mutex_unlock_f)
		pthread_mutex_unlock_f = dlsym(RTLD_NEXT, "pthread_mutex_unlock");
}

#endif

#if 1 //sample

pthread_mutex_t r1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t r2 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t r3 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t r4 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t r5 = PTHREAD_MUTEX_INITIALIZER;

void *t1_cb(void *arg) {

	printf("t1: %ld\n", pthread_self());

	pthread_mutex_lock(&r1);
	sleep(1);
	pthread_mutex_lock(&r2);

	pthread_mutex_unlock(&r2);

	pthread_mutex_unlock(&r1);

}

void *t2_cb(void *arg) {

	printf("t2: %ld\n", pthread_self());

	pthread_mutex_lock(&r2);
	sleep(1);
	pthread_mutex_lock(&r3);

	pthread_mutex_unlock(&r3);
	pthread_mutex_unlock(&r2);

}

void *t3_cb(void *arg) {

	printf("t3: %ld\n", pthread_self());

	pthread_mutex_lock(&r3);
	sleep(1);
	pthread_mutex_lock(&r4);

	pthread_mutex_unlock(&r4);
	pthread_mutex_unlock(&r3);

}

void *t4_cb(void *arg) {

	printf("t4: %ld\n", pthread_self());

	pthread_mutex_lock(&r4);
	sleep(1);
	pthread_mutex_lock(&r5);

	pthread_mutex_unlock(&r5);
	pthread_mutex_unlock(&r4);
}

void *t5_cb(void *arg) {

	printf("t5: %ld\n", pthread_self());

	pthread_mutex_lock(&r1);
	sleep(1);
	pthread_mutex_lock(&r5);

	pthread_mutex_unlock(&r5);
	pthread_mutex_unlock(&r1);

}


int main() {
	init_hook();
	start_check();

	pthread_t t1, t2, t3, t4, t5;

	pthread_create(&t1, NULL, t1_cb, NULL);
	pthread_create(&t2, NULL, t2_cb, NULL);
	
	pthread_create(&t3, NULL, t3_cb, NULL);
	pthread_create(&t4, NULL, t4_cb, NULL);
	pthread_create(&t5, NULL, t5_cb, NULL);
    
	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	pthread_join(t3, NULL);
	pthread_join(t4, NULL);
	pthread_join(t5, NULL);
	printf("complete\n");
}
#endif
内存泄露

内存泄露是由于没有释放堆上申请的内存导致,可以通过以下集中方法进行检测

  1. Valgrind工具:

    valgrind可以用例检测内存错误的工具,包括内存泄露等多种内存问题;

    valgrind --tool=memcheck --leak-check=yes your_program
    

    valgrind会报告所有未释放的内存以及其分配的位置;
    具体使用方法可参考博文https://blog.csdn.net/xiaofeilongyu/article/details/128538777?spm=1001.2014.3001.5502

  2. AddressSanitizer(ASan)

    AddressSanitizer是gcc和clang编译器提供的一个动态检测内存错误的工具,但是需要重新编译源码

    g++ -fsanitize=address -g your_program.cpp -o your_program
    ./your_program
    
  3. 通过hook函数管理malloc/free内存分配和释放函数,分析内存使用情况

    #define _GNU_SOURCE
    #include <dlfcn.h>
    #include <link.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int flag = 1;
    
    #if 0
    void *nMalloc(size_t size, const char *filename, int line) {
    	void *p = malloc(size);
    	if (flag) {
    		char buff[128] = {0};
    		snprintf(buff, 128, "./mem/%p.mem", p);
    		FILE *fp = fopen(buff, "w");
    		if (!fp) {
    			free(p);
    			return NULL;
    		}
    		fprintf(fp, "[+]%s:%d, addr: %p, size: %ld\n", filename, line, p, size);
    		fflush(fp);
    		fclose(fp);
    	}
    	//printf("nMalloc: %p, size: %ld\n", p, size);
    	return p;
    }
    
    void nFree(void *ptr) {
    	//printf("nFree: %p, \n", ptr);
    	if (flag) {	
    		char buff[128] = {0};
    		snprintf(buff, 128, "./mem/%p.mem", ptr);
    		if (unlink(buff) < 0) {
    			printf("double free: %p", ptr);
    			return ;
    		}
    	}
    	return free(ptr);
    }
    
    #define malloc(size) nMalloc(size, __FILE__, __LINE__)
    #define free(ptr) nFree(ptr)
    
    #else
    // hook 
    typedef void *(*malloc_t)(size_t size);
    malloc_t malloc_f = NULL;
    typedef void (*free_t)(void *ptr);
    free_t free_f = NULL;
    
    int enable_malloc = 1;
    int enable_free = 1;
    
    // 地址转换
    void *ConvertToELF(void *addr) {
    	Dl_info info;
    	struct link_map *link;
    	dladdr1(addr, &info, (void **)&link, RTLD_DL_LINKMAP);
    	return (void *)((size_t)addr - link->l_addr);
    }
    // main --> f1 --> f2 --> malloc
    void *malloc(size_t size) {
    	void *p = NULL;
    	if (enable_malloc) {
    		enable_malloc = 0;
    
    		p = malloc_f(size);
            // 获取上该函数调用的函数地址 参数0调试一级调用,参数1调试二级调用,参数2调试三级调用
    		void *caller = __builtin_return_address(0);
    		char buff[128] = {0};
    		sprintf(buff, "./mem/%p.mem", p);
    		FILE *fp = fopen(buff, "w");
    		if (!fp) {
    			free(p);
    			return NULL;
    		}
    
    		// fprintf(fp, "[+]%p, addr: %p, size: %ld\n", caller, p, size);
    		fprintf(fp, "[+]%p, addr: %p, size: %ld\n", ConvertToELF(caller), p, size);
    		fflush(fp);
    		enable_malloc = 1;
    	} else {
    		p = malloc_f(size);
    	}
    	return p;
    }
    
    // addr2line 
    void free(void *ptr) {
    
    	if (enable_free) {
    		enable_free = 0;
    
    		char buff[128] = {0};
    		snprintf(buff, 128, "./mem/%p.mem", ptr);
    
    		if (unlink(buff) < 0) {
    			printf("double free: %p", ptr);
    			return ;
    		}
    
    		free_f(ptr);
    
    		enable_free = 1;
    	} else {
    		free_f(ptr);
    	}
    
    	return ;
    }
    
    void init_hook(void) {
    	if (!malloc_f) {
            // RTLD_NEXT 表示获取下一个符号的地址
    		malloc_f = (malloc_t)dlsym(RTLD_NEXT, "malloc");
    	}
    	if (!free_f) {
    		free_f = (free_t)dlsym(RTLD_NEXT, "free");
    	}
    }
    #endif
    
    // __FILE__, __LINE__, __func__
    int main() {
    	init_hook();
    	void *p1 = malloc(5);
    	void *p2 = malloc(10);  
    	void *p3 = malloc(35);
    	void *p4 = malloc(10);
    
    	free(p1);
    	free(p3);
    	free(p4);
    	getchar();
    } 
    

    通过hook函数从写malloc/free函数,每次malloc都回创建一个.mem文件,free会将申请内存时创建的文件删除,当mem文件夹下存在文件时,说明会有内存泄露,查看文件中内存可以查询到具体在哪里发生内存泄露

    cxl@cxl:~/code/memleak$ cat ./mem/0x5557eff714b0.mem 
    [+]0x16c8, addr: 0x5557eff714b0, size: 10
    

    提示发10个字节的内存泄露函数指针地址为:0x5557eff714b0,发生泄露的地址为:0x16c8

    可以同通过addr2line命令查询具体发生的函数和具体位置,如下

    cxl@cxl:~/code/memleak$ addr2line -f -e ./memleak -a 0x16c8
    0x00000000000016c8
    main
    /home/cxl/code/3.2.6-memleak/memleak.c:176
    
    

内存泄露检测本人目前感觉较为好用的是使用valgrind工具进行检测

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明原文出处。如若内容造成侵权/违法违规/事实不符,请联系SD编程学习网:675289112@qq.com进行投诉反馈,一经查实,立即删除!