扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
作为Linux下的C程序员,我总是习惯在单元测试通过之后,再用valgrind把程序跑一下,看看有没有内存泄漏和内存越界等问题。可惜的是,有时valgrind并不能很好的工作,像基于DirectFB的多进程程序在valgrind下是跑不起的, 这时我们可以通过内存管理器的钩子函数来跟踪内存泄漏。
glibc提供的内存管理器的钩子函数让你可以监控/改变内存管理函数的行为。其实glibc已经利用这个机制实现了内存泄漏检测的功能,提供了mtrace/muntrace两个函数和mtrace工具,只是不太好用,一是速度慢,二是没有backtrace。更惨的是在Fedora 7上再也找不到它了,只好自己写一个:
先记录分配/释放操作:
#include <execinfo.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <malloc.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> static void memory_trace_init(void); static void memory_trace_deinit(void); static void *my_malloc_hook (size_t size, const void* ptr); static void my_free_hook (void* ptr, const void* caller); static void *my_realloc_hook (void *ptr, size_t size, const void *caller); static void *my_malloc_hook (size_t size, const void* ptr); static void my_free_hook (void* ptr, const void* caller); static void *my_realloc_hook (void *ptr, size_t size, const void *caller); static void *(*old_malloc_hook)(size_t size, const void* ptr); static void (*old_free_hook)(void* ptr, const void* caller); static void *(*old_realloc_hook)(void *ptr, size_t size, const void *caller); #define BACK_TRACE_DEPTH 8 #define CACHE_SIZE 512 static FILE* g_memory_trace_fp = NULL; static int g_memory_trace_cache_used = 0; /**//*additional 3 items: alloc/free addr size*/ static void* g_memory_trace_cache[CACHE_SIZE][BACK_TRACE_DEPTH + 3]; static void memory_trace_flush(void); static void memory_trace_write(int alloc, void* addr, int size); static void memory_trace_backup(void) { old_malloc_hook = __malloc_hook; old_free_hook = __free_hook; old_realloc_hook = __realloc_hook; return; } static void memory_trace_hook(void) { __malloc_hook = my_malloc_hook; __free_hook = my_free_hook; __realloc_hook = my_realloc_hook; return; } static void memory_trace_restore(void) { __malloc_hook = old_malloc_hook; __free_hook = old_free_hook; __realloc_hook = old_realloc_hook; return; } static void memory_trace_init(void) { if(g_memory_trace_fp == NULL && getenv("MALLOC_TRACE") != NULL) { char file_name[260] = {0}; snprintf(file_name, sizeof(file_name), "/tmp/%d_memory.log", getpid()); if((g_memory_trace_fp = fopen(file_name, "wb+")) != NULL) { memory_trace_backup(); memory_trace_hook(); } atexit(memory_trace_deinit); } return; } static void memory_trace_deinit(void) { if(g_memory_trace_fp != NULL) { memory_trace_restore(); memory_trace_flush(); fclose(g_memory_trace_fp); g_memory_trace_fp = NULL; } return; } void (*__malloc_initialize_hook) (void) = memory_trace_init; static void * my_malloc_hook (size_t size, const void *caller) { void *result = NULL; memory_trace_restore(); result = malloc (size); memory_trace_write(1, result, size); memory_trace_hook(); return result; } static void my_free_hook (void *ptr, const void *caller) { memory_trace_restore(); free (ptr); memory_trace_write(0, ptr, 0); memory_trace_hook(); return; } static void *my_realloc_hook (void *ptr, size_t size, const void *caller) { void* result = NULL; memory_trace_restore(); memory_trace_write(0, ptr, 0); result = realloc(ptr, size); memory_trace_write(1, result, size); memory_trace_hook(); return result; } static void memory_trace_flush_one_entry(int index) { int offset = 0; char buffer[512] = {0}; int fd = fileno(g_memory_trace_fp); int alloc = (int)g_memory_trace_cache[index][BACK_TRACE_DEPTH]; void* addr = g_memory_trace_cache[index][BACK_TRACE_DEPTH+1]; int size = (int)g_memory_trace_cache[index][BACK_TRACE_DEPTH+2]; void** backtrace_buffer = g_memory_trace_cache[index]; snprintf(buffer, sizeof(buffer), "%s %p %d ", alloc ? "alloc":"free", addr, size); if(!alloc) { write(fd, buffer, strlen(buffer)); return; } char** symbols = backtrace_symbols(backtrace_buffer, BACK_TRACE_DEPTH); if(symbols != NULL) { int i = 0; offset = strlen(buffer); for(i = 0; i < BACK_TRACE_DEPTH; i++) { if(symbols[i] == NULL) { break; } char* begin = strchr(symbols[i], '('); if(begin != NULL) { *begin = ' '; char* end = strchr(begin, ')'); if(end != NULL) { strcpy(end, " "); } strncpy(buffer+offset, begin, sizeof(buffer)-offset); offset += strlen(begin); } } write(fd, buffer, offset); free(symbols); } return; } static void memory_trace_flush(void) { int i = 0; for(i = 0; i < g_memory_trace_cache_used; i++) { memory_trace_flush_one_entry(i); } g_memory_trace_cache_used = 0; return; } static void memory_trace_write(int alloc, void* addr, int size) { if(g_memory_trace_cache_used >= CACHE_SIZE) { memory_trace_flush(); } int i = 0; void* backtrace_buffer[BACK_TRACE_DEPTH] = {0}; backtrace(backtrace_buffer, BACK_TRACE_DEPTH); for(i = 0; i < BACK_TRACE_DEPTH; i++) { g_memory_trace_cache[g_memory_trace_cache_used][i] = backtrace_buffer[i]; } g_memory_trace_cache[g_memory_trace_cache_used][BACK_TRACE_DEPTH] = (void*)alloc; g_memory_trace_cache[g_memory_trace_cache_used][BACK_TRACE_DEPTH+1] = addr; g_memory_trace_cache[g_memory_trace_cache_used][BACK_TRACE_DEPTH+2] = (void*)size; g_memory_trace_cache_used++; return; } #ifdef MEMORY_TRACE_TEST void test(void) { char* p = malloc(100); p = malloc(123); free(p); return; } int main(int argc, char* argv[]) { malloc(100); test(); malloc(100); test(); char* p = malloc(100); free(p); return 0; } #endif/*MEMORY_TRACE_TEST*/
把以上代码编译成动态库,或者直接编译到功能代码一起。如果设置了MALLOC_TRACE环境变量,分配/释放会被记录到/tmp/$PID_memory.log下。
再写个程序来分析log文件:
#include <stdio.h> #define MAX_ENTRY 1024*1024 int main(int argc, char* argv[]) { if(argc != 3) { printf("usage: %s [log file] [out file] ", argv[0]); return 0; } FILE* fp = fopen(argv[1], "r"); FILE* fp_out = fopen(argv[2], "wb+"); if(fp == NULL || fp_out == NULL) { printf("open file failed "); if(fp != NULL) { fclose(fp); } if(fp_out != NULL) { fclose(fp_out); } return; } int i = 0; int n = 0; int skip = 0; int line_index = 0; void* addr = 0; char line[260] = {0}; void* addrs_array[MAX_ENTRY] = {0}; int lines_array[MAX_ENTRY] = {0}; while(fgets(line, sizeof(line), fp) != NULL) { if(line[0] != 'a' && line[0] != 'f') { line_index++; continue; } addr = NULL; if(strncmp(line, "alloc", 5) == 0 && n < MAX_ENTRY) { sscanf(line, "alloc %p", &addr); addrs_array[n] = addr; lines_array[n] = line_index; n++; printf("a"); } else if(strncmp(line, "free", 4) == 0) { sscanf(line, "free %p", &addr); for(i = 0; i < n; i++) { if(addrs_array[i] == addr) { lines_array[i] = -1; break; } } printf("f"); } line_index++; } printf(" "); fseek(fp, 0, 0); i = 0; line_index = 0; while(fgets(line, sizeof(line), fp) != NULL) { if(strncmp(line, "alloc", 5) == 0) { if(lines_array[i] == line_index) { printf("leak %s", line); fprintf(fp_out, "*"); skip = 0; i++; } else { skip = 1; } } else if(strncmp(line, "free", 4) == 0) { skip = 1; } if(!skip) { fputs(line, fp_out); } line_index++; } fclose(fp); fclose(fp_out); return 0; }
把这个段代码编译成一个可执行文件mtrace,以前面记录的LOG为输入,再指定个输出文件,内存泄漏会被写到输出文件中,可以看到内存泄漏的地地址,大小和调用关系。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者