Linux C语言memory leak的调试方法

2023-09-30  本文已影响0人  CodingCode

使用Linux的PRELOAD重载malloc/free的函数实现,然后可以记录下来所有的malloc/free操作。

定义PRELOAD库的源文件

$ cat mymalloc.c
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dlfcn.h>

#include <string.h>
#include <stdarg.h>
#include <pthread.h>

static pthread_mutex_t mymem_lock = PTHREAD_MUTEX_INITIALIZER;

//typedef void *(*malloc_t)(size_t size);
//static malloc_t malloc_f = NULL;
  static void * (*malloc_f)(size_t) = NULL;
  static void * (*realloc_f)(void *, size_t) = NULL;

//typedef void (*free_t)(void* p);
//static free_t free_f = NULL;
  static void (*free_f)(void *) = NULL;

void *malloc(size_t sz) {
    pthread_mutex_lock(&mymem_lock);

    if (malloc_f == NULL) {
        malloc_f = dlsym(RTLD_NEXT, "malloc");
    }

    void * mem = malloc_f(sz);

    pthread_mutex_unlock(&mymem_lock);
    return mem;
}

void *realloc(void *ptr, size_t sz) {
    pthread_mutex_lock(&mymem_lock);

    if (realloc_f == NULL) {
        realloc_f = dlsym(RTLD_NEXT, "realloc");
    }

    void * mem = realloc_f(ptr, sz);

    pthread_mutex_unlock(&mymem_lock);
    return mem;
}

void free(void *mem) {
    if (mem != NULL) {
        pthread_mutex_lock(&mymem_lock);
        if (free_f == NULL) {
            free_f =  dlsym(RTLD_NEXT, "free");
        }

        free_f(mem);

        pthread_mutex_unlock(&mymem_lock);
    }
}

编译运行:

$ gcc -o libmymalloc.so -g -shared -fPIC -ldl mymalloc.c

$ LD_PRELOAD=/path/to/libmymalloc.so ./yourprogram

这样yourprogram就会使用mymalloc里面定义的malloc和free,而不会使用系统库的malloc和free。

可以增强的地方:

  1. 把malloc和free的请求打印出来
static void logmallocfree(const char * fmt, ...) {
    va_list args;
    char fmtbuffer[80] = { '\0' };
    va_start(args, fmt);
    vsnprintf(fmtbuffer, 80, fmt, args);
    va_end(args);

    char logbuffer[120] = { '\0' };
    snprintf(logbuffer, 120, "ts=%d, pid=%d, %s\n", time(NULL), getpid(), fmtbuffer);

    int fd = open("mymalloc.log", O_CREAT | O_WRONLY | O_APPEND, S_IRUSR | S_IWUSR);
    write(fd, logbuffer, strlen(logbuffer));
    close(fd);
}

void *malloc(size_t sz) {
    ...
    void * mem = malloc_f(sz);
    logmallocfree("mymalloc=%p, size=%d", mem, sz);
    ...
}

这里把每次请求的时间戳,进程号,以及分配的地址和大小都打印到日志文件中。

另一可以增强的地方,对于满足条件(现在只是基于大小一致)的malloc把调用栈打出来,遗憾的是找不到办法打印出栈,只能让进程sleep一段时间,然后这期间用pstack来dump出来。

static int monitoralloc() {
    int sz = 0;
    char buffer[80] = { '\0' };

    int fd = open("mymalloc.cfg", O_RDONLY);
    if (fd != -1) {
        if (read(fd, buffer, sizeof(buffer)) != -1) {
           sz = atoi(buffer);
        }
        close(fd);
    }

    return sz;
}


void *malloc(size_t sz) {
    ...
    if (sz == monitoralloc()) {
        logmallocfree("mymalloc sleep when size=%d", sz);
        sleep(10);
    }
    ...
}

monitoralloc从配置文件读取一个数字,表示要monitor如果请求大小是这个值,那么就sleep(10),并打印出一句提示,当用户看到提示出来后,就调用pstack。

另外这里要注意的是,因为重载了malloc/free,所以在mymalloc.c里面就不能再直接或者间接调用malloc/free了,所以想访问文件的很多libc函数都不能用了,必须用最基础的系统IO函数,而不是libc的函数来访问文件,因为libc的函数也会使用malloc/free倒是嵌套循环。

上一篇下一篇

猜你喜欢

热点阅读