C语言内存溢出调试

背景

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中不能直接转换,其他部分系统可以

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值