背景
C语言中没有成对使用malloc和free会导致内存溢出。以下介绍查找内存溢出的方法。
方法一:重写malloc和free
在标准的glibc中,malloc和free都是弱函数,通过重写mall和free的方式来打印内存的申请和释放。
#include <stdio.h>
#include <malloc.h>
extern void *__libc_malloc(size_t size);
extern void *__libc_free(void *ptr);
int malloc_hook_active = 1;
int free_hook_active = 1;
void* my_malloc_hook (size_t size, void *caller)
{
void *result;
// deactivate hooks for logging
malloc_hook_active = 0;
result = malloc(size);
// do logging
printf("+malloc(%ld) caller is %p return %p\n", size, caller, result);
// reactivate hooks
malloc_hook_active = 1;
return result;
}
void my_free_hook(void *ptr, void *caller)
{
// deactivate hooks for logging
free_hook_active = 0;
free(ptr);
// do logging
printf("-free() caller is %p free %p\n", caller, ptr);
// reactivate hooks
free_hook_active = 1;
}
void* malloc (size_t size)
{
void *caller = __builtin_return_address(0);
if (malloc_hook_active)
return my_malloc_hook(size, caller);
return __libc_malloc(size);
}
extern char __executable_start[];
void free(void *ptr)
{
void *caller = __builtin_return_address(0);
if (free_hook_active)
my_free_hook(ptr, caller);
else
__libc_free(ptr);
}
int main()
{
char *p = malloc(1);
free(p);
}
编译执行结果如下:
$ gcc -g main.c && ./a.out
+malloc(1) caller is 0x55fa948a9858 return 0x55fa96a37260
-free() caller is 0x55fa948a9868 free 0x55fa96a372
如上打印了申请的内存地址和释放的内存地址,通过对比所有的申请是否都释放了来确定泄露位置。
通过addr2line -e a.out 0x55fa948a9858
命令定位到调用位置。在某些系统中直接打印的不是真实地址,这样不能直接得出调用位置,此时可以使用gdb工具,使用如下:
$ gcc -g main.c
$ gdb a.out
(gdb) run
Starting program: /malloc_hook/a.out
+malloc(1) caller is 0x555555554858 return 0x555555756260
-free() caller is 0x555555554868 free 0x555555756260
[Inferior 1 (process 17366) exited normally]
(gdb)
(gdb)
(gdb) l*0x555555554858
0x555555554858 is in main (main.c:64).
59 __libc_free(ptr);
60 }
61
62 int main()
63 {
64 char *p = malloc(1);
65 free(p);
66 }
如此就可以定位出malloc是在main函数 (main.c:64)行处分配的内存
注意:使用gdb确定位置在编译时需要使用-g
参数
方法二:使用钩子函数
使用钩子函数在调用malloc时调用实际调用钩子函数,输出调用地址。通过地址查找源码位置和方法一一致
#include <stdio.h>
#include <malloc.h>
/* Prototypes for our hooks. */
static void my_init_hook(void);
static void *my_malloc_hook(size_t, const void *);
static void *my_free_hook(void *, const void *);
/* Variables to save original hooks. */
static void *(*old_malloc_hook)(size_t, const void *);
static void *(*old_free_hook)(void *, const void *);
/* Override initializing hook from the C library. */
void (*__malloc_initialize_hook) (void) = my_init_hook;
void (*__free_initialize_hook) (void) = my_init_hook;
static void my_init_hook(void)
{
old_malloc_hook = __malloc_hook;
__malloc_hook = my_malloc_hook;
old_free_hook = __free_hook;
__free_hook = my_free_hook;
}
static void * my_malloc_hook(size_t size, const void *caller)
{
void *result;
/* Restore all old hooks */
__malloc_hook = old_malloc_hook;
/* Call recursively */
result = malloc(size);
/* Save underlying hooks */
old_malloc_hook = __malloc_hook;
/* printf() might call malloc(), so protect it too. */
printf("malloc(%u) called from %p returns %p\n",
(unsigned int) size, caller, result);
/* Restore our own hooks */
__malloc_hook = my_malloc_hook;
return result;
}
static void * my_free_hook(void *ptr, const void *caller)
{
void *result;
/* Restore all old hooks */
__free_hook = old_free_hook;
/* Call recursively */
free(ptr);
/* Save underlying hooks */
old_free_hook = __free_hook;
/* printf() might call free(), so protect it too. */
printf("-free() called from %p free %p\n",
caller, ptr);
/* Restore our own hooks */
__free_hook = my_free_hook;
return result;
}
int main()
{
my_init_hook();
char *p = (char*)malloc(1);
free(p);
return 0;
}
方法三:使用mtrace进行内存调试
#include <mcheck.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
int j;
mtrace();
for (j = 0; j < 2; j++)
malloc(100); /* Never freed--a memory leak */
calloc(16, 16); /* Never freed--a memory leak */
exit(EXIT_SUCCESS);
}
$ export MALLOC_TRACE=`pwd`/log.txt
$ gcc -g main.c && ./a.out
$ mtrace a.out log.txt
Memory not freed:
-----------------
Address Size Caller
0x000055739e5406a0 0x64 at 0x55739dd8f741
0x000055739e540710 0x64 at 0x55739dd8f741
0x000055739e540780 0x100 at 0x55739dd8f75a
这些地址在Ubuntu中不能直接转换,其他部分系统可以