用内存管理器的钩子函数跟踪内存泄漏

内存管理器的钩子函数跟踪内存泄漏

载时请注明出处和作者联系方式
作者联系方式:李先静 <xianjimli at hotmail dot com>

作为Linux下的C程序员,我总是习惯在单元测试通过之后,再用valgrind把程序跑一下,看看有没有内存泄漏和内存越界等问题。可惜的是,有时valgrind并不能很好的工作,像基于DirectFB的多进程程序在valgrind下是跑不起的, 这时我们可以通过内存管理器的钩子函数来跟踪内存泄漏。

glibc提供的内存管理器的钩子函数让你可以监控/改变内存管理函数的行为。其实glibc已经利用这个机制实现了内存泄漏检测的功能,提供了mtrace/muntrace两个函数和mtrace工具,只是不太好用,一是速度慢,二是没有backtrace。更惨的是在Fedora 7上再也找不到它了,只好自己写一个:

 

先记录分配/释放操作:


/*memory_trace.c*/

#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文件:
/*mtrace.c*/
#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, 
00);

    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为输入,再指定个输出文件,内存泄漏会被写到
输出文件中,可以看到内存泄漏的地地址,大小和调用关系

~~end~~


原文地址:https://www.cnblogs.com/zhangyunlin/p/6167639.html