内存泄漏检测方案

虚拟内存

线上内存泄漏,产生的原因是malloc/new没有与之匹配的free/delete,导致htop/top虚拟内存一直在涨,只有整个进程退出的时候,才会回收内存

malloc和new在堆上分配内存,而堆栈都是虚拟地址空间,因此内存泄漏指定的是虚拟内存泄漏,4G的物理内存,泄漏的虚拟内存可能达到9个G

tip:栈会出现内存覆盖问题,比如函数中临时数组太长了,把函数返回地址覆盖了.

解决:首先通过htop/top检查是否有大片内存泄漏,然后要确定哪一行内存泄漏

检测方法

首先需要知道malloc的实现:malloc首先会检查是否需要执行钩子函数,然后调用__libc_malloc(内部调用__init_malloc)分配内存。

据此,检测内存泄漏的根本思路就是:调用malloc时能打印调用时的行信息:

  1. 我们可以重写malloc函数,其最核心的 __init_malloc 部分保留,但可以额外打印内存信息。(类似的就是在C++中对new进行重载,然后调用到内存池)
  2. 由于sprintf,printf,fclose等打印函数会调用malloc或free,直接重写malloc会导致临时指针free时,出现double free的多余打印信息,出现多余的打印信息,因此,可以将malloc/free定义处改成__malloc/__free,并在最后重定义 maolloc 为 __malloc。(相当于对malloc多做了一层内存分配打印信息的封装)
  3. 重写钩子函数,在钩子函数内打印内存信息,并用一个函数指针保存原钩子函数地址,以实现钩子函数替换的开启和关闭。(最为安全灵活的方法)
  4. 直接使用mtrace组件
#define _GNU_SOURCE // 使用dlsym,MAP_ANON,要先定义 _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <malloc.h>   // 系统自带的钩子函数__malloc_hook
#include <mcheck.h>   // mtrace组件
#include <fcntl.h>

#if 0
// 方案一:重写malloc
// malloc底层就是调用的__lib_malloc,它又调用了__int_malloc函数(真正分配内存的函数)
// 这里不extern的话,编译会warning:未定义的函数
// extern表示外部的,在此处不分配空间,用于链接时查找
int enable_malloc_hook = 1;        
extern void *__libc_malloc(size_t size);
 
int enable_free_hook = 1;
extern void *__libc_free(void *p);
 
void *malloc(size_t size) {
 
    if (enable_malloc_hook) {   // Tip1: 用于打破malloc递归调用
        enable_malloc_hook = 0;
 
        void *p = __libc_malloc(size);

        // 查看地址所在行:addr2line -f -e memleak -a 0x4006e6 
        void *caller = __builtin_return_address(0);  // Tip2: 返回函数第n层调用处地址

        char buff[128] = {0};
        sprintf(buff, "./mem/%p.mem", p);   // 会调用free(),打印double free信息,空指针
 
        FILE *fp = fopen(buff, "w");
        fprintf(fp, "[+%p]malloc --> addr:%p size:%lu\n", caller, p, size);
        fflush(fp);
        fclose(fp);    // 会调用free(),打印double free信息
 
        enable_malloc_hook = 1;
        return p;
    } else {
 
        return __libc_malloc(size);
 
    }
    return NULL;
}
 
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);
        }
 
        __libc_free(p);
 
        enable_free_hook = 1;
    } else {
        __libc_free(p);
    }
 
}

#elif 0
// 方案二:利用宏定义
void *_malloc(size_t size, const char *file, int line)
{

    void *p = malloc(size);

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

    FILE *fp = fopen(buff, "w");
    fprintf(fp, "[+%s:%d]malloc --> addr:%p size:%lu\n", file, line, p, size);
    fflush(fp);

    fclose(fp);

    return p;
}

void _free(void *p, const char *file, int line)
{

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

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

    free(p);
}

#define malloc(size) _malloc(size, __FILE__, __LINE__)

#define free(p) _free(p, __FILE__, __LINE__)

#elif 0
// 方案三:使用malloc.h中提供的hook: __malloc_hook, __free_hook.
typedef void *(*malloc_hoot_t)(size_t size, const void *caller);
malloc_hoot_t malloc_f;

typedef void (*free_hook_t)(void *p, const void *caller);
free_hook_t free_f;

void mem_trace(void);
void mem_untrace(void);

void *malloc_hook_f(size_t size, const void *caller)
{

    mem_untrace();
    // Tip3: malloc前后调用mem_untrace()和mem_trace();也是为了打破递归调用(钩子函数的递归调用)
    // 调用malloc_hook_f时,会调用malloc,然后又调用我们的malloc_hook_f,造成递归调用
    // 因此,在maolloc前需要开启mem_untrace
    void *ptr = malloc(size);
    // printf("+%p: addr[%p]\n", caller, ptr);
    char buff[128] = {0};
    sprintf(buff, "./mem/%p.mem", ptr);

    FILE *fp = fopen(buff, "w");
    fprintf(fp, "[+%p]malloc --> addr:%p size:%lu\n", caller, ptr, size);
    fflush(fp);

    fclose(fp);

    mem_trace(); // 下次malloc继续调我们的钩子
    return ptr;
}

void free_hook_f(void *p, const void *caller)
{
    mem_untrace();
    // printf("-%p: addr[%p]\n", caller, p);

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

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

    free(p);
    mem_trace();
}

void mem_trace(void)
{
    // 更改系统的malloc指向,调用malloc时调用我们的malloc_f
    malloc_f = __malloc_hook; // 保存__malloc_hook地址
    free_f = __free_hook;

    __malloc_hook = malloc_hook_f; // malloc调用我们的malloc_hook_f
    __free_hook = free_hook_f;
}

void mem_untrace(void)
{
    __malloc_hook = malloc_f;
    __free_hook = free_f;
}

// 也可以使用dlsym对malloc,free进行hook,实现类似的效果。
#elif 0
// 方案四: mtrace() 组件
// 生成一个日志文件,记录哪些内存被申请,哪些内存被释放

#endif

int main()
{
#if 0
    // 方案一、二
    void *p1 = malloc(10);
    // free(p1);
    void *p2 = malloc(20); // 如果p1在p2申请之前释放了,系统会吧p1的地址块分给p2
    free(p1);
    free(p1);      // double free
#elif 0
    // 方案三
    mem_trace();
    void *p1 = malloc(10);
    // free(p1);
    void *p2 = malloc(20);
    free(p1);
    mem_untrace();
#elif 1
    // 方案四:组件
    mtrace();
    void *p1 = malloc(10);
    void *p2 = malloc(20);

    free(p1);
    void *p3 = malloc(30);
    void *p4 = malloc(40);

    free(p2);
    free(p4);
    muntrace();
#endif
    return 0;
}

编译,运行:gcc -o memleak memleak.c -ldl -g,./memleak

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值