内存泄漏检测组件的实现

发布时间:2024年01月19日

内存泄漏是在没有自动GC的编程语言里面,经常发生的一些问题。要实现一个内存泄露的检测组件,有两个需求:

  1. 能够检测出来内存泄漏
  2. 能够判断出来哪一个地方的申请没有释放(哪一行引起的泄漏)

方案1:借助mtrace

mtrace是一个Linux系统下的内存泄漏检测工具,它可以跟踪程序中的内存分配和释放操作,并记录每个内存块的分配和释放位置。通过分析mtrace的输出,我们可以找到内存泄漏的地方。

下面是一个简单的示例程序,演示如何使用mtrace来检测内存泄漏:

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

int main() {
    setenv("MALLOC_TRACE", "trace.log", 1);
    mtrace();
    int* p = malloc(sizeof(int));
    free(p);
    muntrace();
    return 0;
}

在这个程序中,我们使用了mcheck.h头文件中的mtracemuntrace函数来启用和禁用内存跟踪功能。在程序开始时,我们通过调用setenv函数设置环境变量MALLOC_TRACE,将内存跟踪日志输出到文件trace.log中。然后,我们调用mtrace函数启用内存跟踪。在程序结束时,我们调用muntrace函数禁用内存跟踪。

运行程序后,mtrace会记录程序中的内存分配和释放操作,并将跟踪日志输出到文件trace.log中。我们可以使用mtrace命令行工具来分析这个日志文件,找到内存泄漏的地方。例如,假设trace.log文件的内容如下:

Memory not freed:
-----------------
         Address     Size     Caller
0x0000000000602010     0x4  at /home/user/test.c:8

这个日志文件告诉我们,程序在文件test.c的第8行分配了一个4字节的内存块,但是没有释放它。通过查看日志文件中的Caller字段,我们可以找到这个内存块的分配位置。在这个例子中,我们可以看到,这个内存块是在test.c文件的第8行分配的。

需要注意的是,mtrace只能检测到使用mallocfree函数分配和释放的内存块,对于其他的内存分配和释放操作,mtrace可能无法正确地跟踪和记录。此外,mtrace会增加程序的运行时间和内存占用,因此在生产环境中应该谨慎使用。

方案2

实现自定义的内存分配和释放函数,它会在每次内存分配和释放时将分配和释放的地址、大小、文件名和行号等信息输出到一个以分配地址为名的文件中。这样可以方便地跟踪内存分配和释放的位置和大小,以便检测内存泄漏和其他内存相关的问题。

这个实现的原理是,在每次内存分配时,先调用系统的malloc函数分配内存,然后根据分配的地址生成一个文件名,并将分配的地址、大小、文件名和行号等信息输出到文件中。在每次内存释放时,先根据释放的地址生成文件名,然后从文件系统中删除对应的文件,并调用系统的free函数释放内存。

需要注意的是,这个实现并不是线程安全的,如果在多线程环境中使用,可能会出现竞争条件。此外,这个实现也会增加程序的运行时间和磁盘占用,因此做测试用就好。


void *_malloc(size_t size, char *filename, int line)
{
    void *p = malloc(size);
    // printf("[+]%s:%d, %p\n", filename, line, p);

    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);

    return p;
}

void _free(void *p, char *filename, int line)
{
    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__)

方案3

这段代码在C语言中实现了对mallocfree函数的钩子(hook)。

malloc函数用于分配指定大小的内存块,free函数用于释放先前分配的内存块。

代码中定义了两个函数指针类型 malloc_tfree_t,分别代表了指向 mallocfree 函数的指针类型。

然后定义了两个全局变量 malloc_ffree_f,用于保存实际的 mallocfree 函数的地址。

接下来是两个整型变量 enable_malloc_hookenable_free_hook,用于控制是否启用钩子功能。

ConvertToELF 函数用于将传入的地址转换为 ELF(可执行和可链接格式)的地址。(这一步能让我们可以通过后面的addr2line找到泄露位置)

malloc 函数中,如果钩子功能被启用(enable_malloc_hook 为真),则会记录调用 malloc 的函数地址、分配的内存地址和大小,并将这些信息写入一个文件中。

free 函数中,如果钩子功能被启用(enable_free_hook 为真),则会删除之前记录的文件,并调用实际的 free 函数释放内存。

最后,init_hook 函数用于初始化钩子,通过 dlsym 函数获取实际的 mallocfree 函数的地址。

总的来说,这段代码通过钩子机制,在每次调用 mallocfree 函数时记录相关信息,并可以在分配和释放内存时执行额外的操作。

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;
int enable_free_hook = 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);


}

void *malloc(size_t size)
{
    void *p = NULL;

    if (enable_malloc_hook)
    {
        enable_malloc_hook = 0;
        // printf("malloc size: %ld\n", size);

        p = malloc_f(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", ConvertToELF(caller), p, size);

        fflush(fp);

        enable_malloc_hook = 1;

    }
    else
    {
        p = malloc_f(size);
    }

    return p;
}

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

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

        enable_free_hook = 1;
    }
    else
    {
        free_f(p);
    }
    // retu/rn;
}

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");
    }
}

对于上面的方案,内存分配释放的信息被写入文件后,我们以方案3举例,打开文件可以看到如下信息在这里插入图片描述
然后我们借助addr2line工具,直接定位带泄漏位置,如下示例:
在这里插入图片描述
综上,我们即可找到内存泄露位置。

文章来源:https://blog.csdn.net/axyw_/article/details/135706437
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。