内存泄漏定位工具

1、原理

在编写复杂代码的时候,有时一大意就会忘了释放申请的内存;或是调试前人代码时,发现有内存泄漏,这些情况排查起来相当麻烦。这里基于RT-Thread写了一个内存泄漏定位工具(实际和RTT无关,什么系统都可以用,要适当修改),原理非常简单:申请内存时,记录申请的内存地址、大小,以及申请内存这行代码所在的文件名和行号,当释放内存时,根据内存地址找到之前的记录并删除,最后留下的记录就极有可能是发生内存的代码(当然有些内存是常驻的,需要使用者自己辨别)。
linux下有mtrace库,它的原理也是类似,利用钩子函数(hook)在内存申请的地方插入一段代码,这段代码记录了内存申请的信息,包含了申请时的PC指针等,在回溯时可以根据这些地址反推出申请的代码位置;而本文的实现则是靠__func__和__LINE__宏直接记录调用的位置信息,虽然方便,但也有一定的局限。

2、使用方法

demo程序如下,使用时,需要做两件事:

  1. 将原先使用的内存申请和释放接口分别复制给MEM_USR_MALLOC和MEM_USR_FREE,demo里使用的是rt_malloc,根据情况而定,并包含头文件memtrace.h。
  2. 将要待排查的内存接口换成MEM_TRACE_MALLOC和MEM_TRACE_FREE。
#include <rtthread.h>

#define MEM_USR_MALLOC  rt_malloc
#define MEM_USR_FREE    rt_free
#include "memtrace.h"

int main(void)
{
    void *p = RT_NULL;
        
    p = MEM_TRACE_MALLOC(1024);
    MEM_TRACE_FREE(p);
    
    p = MEM_TRACE_MALLOC(568);
    
    rt_thread_mdelay(100);
    
    p = MEM_TRACE_MALLOC(8);
    
    return 0;
}

demo里申请了三段内存,第一段内存及时释放了,第二、三段内存申请后没有释放,造成了内存泄漏。此时用memtrace命令可以看到泄漏的位置:

msh />memtrace
   --------------------Memory Trace Info-------------------------
           file          line            addr            size  
          main.c           14         0x80d264a8         568
          main.c           18         0x80b6d158         8

3、源码

memtrace.c,这里有两个可调的变量MEM_TRACE_MAX和MEM_TRACE_FILE_NAME_LEN,MEM_TRACE_MAX是最大可记录的内存记录数目,默认值100,记录所用的空间是编译时预先分配的静态内存,所以这个组件只适合出问题的时候调试用。申请内存时会记录代码所在的文件名,MEM_TRACE_FILE_NAME_LEN就是文件名的最大长度,最终记录的只有最后级的文件名,不包含路径,所以16字节应该是够用的。

#include "rtthread.h"
#include "memtrace.h"

#define MEM_TRACE_MAX               100
#define MEM_TRACE_FILE_NAME_LEN     16

#ifndef RT_USING_MUTEX
#error "please define RT_USING_MUTEX"
#endif

typedef struct
{
    rt_list_t           node;
    char                file[MEM_TRACE_FILE_NAME_LEN];
    rt_uint16_t         line;
    rt_uint8_t          res[2];
    rt_uint32_t         size;
    void                *addr;
}mem_record_t;

typedef struct
{
    rt_uint32_t         init;
    rt_uint32_t         used;
    rt_uint32_t         total;
    struct rt_mutex     lock;
}mem_info_t;

static mem_info_t mem_info;
static mem_record_t mem_node[MEM_TRACE_MAX];
static rt_list_t free_list;
static rt_list_t used_list;

rt_err_t mem_trace_add(char *file, rt_uint16_t line, rt_uint32_t size, void *addr)
{
    mem_record_t *record = RT_NULL;
    rt_uint32_t index = 0, len = 0;

    rt_mutex_take(&mem_info.lock, RT_WAITING_FOREVER);
    
    if(rt_list_isempty(&free_list))
    {
        rt_kprintf("please increase MEM_TRACE_MAX\n");
        rt_mutex_release(&mem_info.lock);
        return -RT_EEMPTY;
    }
    
    record = (mem_record_t*)free_list.next;
    rt_list_remove((rt_list_t*)record);

    /* find last '/' */
    len = rt_strlen(file);
    for(index = len - 1; index != 0; index--)
    {
        if(file[index] == '/')
        {
            index++;
            break;
        }
    }
    
    rt_strncpy(record->file, &file[index], len - index);
    record->line = line;
    record->size = size;
    record->addr = addr;

    rt_list_insert_before(&used_list, (rt_list_t*)record);

    rt_mutex_release(&mem_info.lock);

    return RT_EOK;
}

rt_err_t mem_trace_del(void *addr)
{
    rt_list_t *list_node = RT_NULL;
    mem_record_t *record = RT_NULL;
    int find = 0;

    if(rt_list_isempty(&used_list))
    {
        rt_kprintf("no memory in use!\n");
        return -RT_EEMPTY;
    }

    rt_mutex_take(&mem_info.lock, RT_WAITING_FOREVER); 
    
    rt_list_for_each(list_node, (&used_list))
    {
        record = (mem_record_t*)list_node;
        if(record->addr == addr)
        {
            find = 1;
            break;
        } 
    }

    if(find)
    {
        rt_list_remove(&(record->node));
        rt_list_insert_before(&free_list, &(record->node));
    }
    else
    {
        rt_kprintf("mem_trace_del err,can't find addr:0x%p\n", addr);
    }

    rt_mutex_release(&mem_info.lock);

    return find ? RT_EOK : -RT_ERROR;
}

static int cmd_print_memtrace(int argc, char **argv)
{   
    rt_list_t *list_node = RT_NULL;
    mem_record_t *record = RT_NULL;

    if(rt_list_isempty(&used_list))
    {
        rt_kprintf("no memory in use!\n");
        return 0;
    }
    
    rt_kprintf("   --------------------Memory Trace Info-------------------------\n");
    rt_kprintf("           file          line            addr            size  \n");
    
    rt_mutex_take(&mem_info.lock, RT_WAITING_FOREVER);
    
    rt_list_for_each(list_node, (&used_list))
    {
        record = (mem_record_t*)list_node;
        rt_kprintf("%16.16s        %5d         0x%08p         %d\n", 
                    record->file, record->line, record->addr, record->size);
    }

    rt_mutex_release(&mem_info.lock);
    return 0;
}
MSH_CMD_EXPORT_ALIAS(cmd_print_memtrace, memtrace, print memory trace info);

static int mem_trace_init(void)
{
    rt_uint32_t i = 0;
    rt_err_t err = 0;

    rt_memset(mem_node, 0 , sizeof(mem_node));
    rt_memset(&mem_info, 0 , sizeof(mem_info));
    
    err = rt_mutex_init(&mem_info.lock, "mem_trace", RT_IPC_FLAG_PRIO);
    if(err)
    {
        rt_kprintf("mem_trace_init fail!\n");
        return -1;
    }
    
    mem_info.init = 1;
    mem_info.used = 0;
    mem_info.total = MEM_TRACE_MAX;

    rt_list_init(&free_list);
    rt_list_init(&used_list);

    for(i = 0; i < MEM_TRACE_MAX; i++)
    {
        rt_list_insert_before(&free_list, &mem_node[i].node);
    }

    return 0;
}
INIT_ENV_EXPORT(mem_trace_init);

memtrace.h,没错,这个头文件没有用“#ifndef …… #define”这样的写法,这样有个好处是MEM_TRACE_MALLOC宏可以随着MEM_USR_MALLOC宏变化,即这个组件能用同时监控不同的内存接口,例如test_a.c里用的是malloc,test_b.c里用的是rt_malloc.c,都可以添加到内存监控中去。

#define MEM_TRACE_MALLOC(size)                          \
({                                                      \
    void *addr = RT_NULL;                               \
    addr = MEM_USR_MALLOC(size);                        \
    if(addr != RT_NULL)                                 \
        mem_trace_add(__FILE__, __LINE__, size, addr);  \
    addr;                                               \
})

#define MEM_TRACE_FREE(addr)                            \
({                                                      \
    MEM_USR_FREE(addr);                                 \
    mem_trace_del(addr);                                \
})

extern rt_err_t mem_trace_add(char *file, rt_uint16_t line, rt_uint32_t size, void *addr);
extern rt_err_t mem_trace_del(void *addr);

4、局限性

有些模块可能会进一步封装内存申请的接口,而memtrace只支持void* malloc(size_t size)这样的形式,如果是这种情况,可能需要使用者将待排查的内存接口转成宏定义,会比较麻烦。文章来源地址https://uudwc.com/A/EyZZ4

原文地址:https://blog.csdn.net/qq_27575841/article/details/132540870

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

h
上一篇 2023年08月28日 17:04
系统学习Linux-LVS集群
下一篇 2023年08月28日 17:04