【原创】小方法对付内存泄露

最近忙于处理一个项目,项目使用capwap进行同步多台设备的配置和固件。使用了开源的 opencapwap0.93.3 进行开发,但是遇到了遇到了内存泄露的问题。本想内存泄露使用工具应该比较好检查出来。

但是期间一波三折,这里作为经验部分记录下来。同时编写了小工具处理该类问题,分享在 Github 点我直达

环境介绍

由于程序运行于我们的一个特殊设备中(可以理解为路由器),设备可用资源很有限,采用C语言进行代码编写。
部分细节:

  1. 设备采用SPI FLASH存放只读文件系统,大小仅16MB。
  2. 设备运行的linux极为精简,编译后固件只有10MB。
  3. 设备可供调试、运行程序使用的分区为 var 目录,可用大小约为100MB。
  4. ssh为精简版,丢掉了sftp等。
  5. 常用软件全来自busybox。

一、绝望的尝试

总共尝试了好几种工具进行内存泄露的检测,结局不是失败就是半失败状态。

Valgrind:虽然说这工具是神器,但是无可奈何我们系统不支持 LD_PRELOAD,结果也想得到,没法检测。而且因为链接了openssl,导致了很多报错,辣眼睛没法看。

Dmalloc:尝试了好几种工具都失败了后看到程序编写者曾经使用Dmalloc进行内存泄露的检查。通过开启该方式,成功找到了几个内存问题点。但是解决掉这几个点之后,干脆Dmalloc连log文件都不生成了。但是内存依然还有问题,必须想办法其他解决。

二、柳暗花明的曙光

经过好长一段时间的挣扎看源码,忽然发现了一个规律。代码中将malloc 和free 进行封装在一个宏定义中,这让我看到了曙光。我总结了以下一种简单的查找内存泄露的办法,尤其在代码量大,不好直接看代码或逻辑异常绕且调试受限的场合。

2.1 处理 malloc 函数

利用宏定义将malloc函数封装一遍,之后调用malloc的地方全部换成该宏进行代替。如下代码所示,如果启用 JET_DEBUG 宏,那么将会在console 中打印出相关内存申请与释放消息。可以根据自己系统实际情况将其打印到文件或者其他console口中。

#ifdef JET_DEBUG

/*
 *  jet malloc description
 *  only print information to console 
 *  example :
 *          assert_memory(__FILE__,__func__,__LINE__,obj_name,1);  //malloc
 *          assert_memory(__FILE__,__func__,__LINE__,obj_name,0);  //free
 */
void static const assert_memory( const char * file_name, const char * func_name, unsigned int line_no ,void *obj_name , char * mode)
{
    if( mode == 1){
        printf("jet_assert malloc:%s, func: %s,line %u\n", file_name, func_name, line_no );
        printf("jet_malloc:0x%08x\n",(unsigned int)obj_name);
    }else{
        printf("jet_assert free  :%s, func: %s,line %u\n", file_name, func_name, line_no );
        printf("jet_free:0x%08x\n",(unsigned int)obj_name);
    }
}


#define    CW_FREE_OBJECT(obj_name)        {if(obj_name){assert_memory(__FILE__,__func__,__LINE__,(void *)obj_name,0);free((obj_name)); (obj_name) = NULL;}}

#define    CW_CREATE_OBJECT_ERR(obj_name, obj_type, on_err)    {obj_name = (obj_type*) (malloc(sizeof(obj_type))); assert_memory(__FILE__,__func__,__LINE__,(void *)obj_name,1);if(!(obj_name)) {on_err}}

#else

#define    CW_FREE_OBJECT(obj_name)        {if(obj_name){free((obj_name)); (obj_name) = NULL;}}

#define    CW_CREATE_OBJECT_ERR(obj_name, obj_type, on_err)    {obj_name = (obj_type*) (malloc(sizeof(obj_type))) ;if(!(obj_name)) {on_err}}

#endif

目的就是希望发生内存泄露的时候能够根据console口中打印出来的信息进行判断哪里申请了内存,哪里释放了内存。

2.2 日志格式

因为程序比较大,相印申请和释放内存将极为频繁。所以建议将以上log信息存放在一个文件中,这里我们将其命名为 memory.log。

同时我们的程序应当是循环的(这里说循环是指类似于:接受指令->完成任务->接受指令->完成任务)。所以我们找到接受指令/完成任务 的代码,修改添加打印出标记信息。最终结果应像下面范例文本:

memory.log

======>>start
jet_assert malloc:ACInterface.c, func: CWInterface,line 717
jet_malloc:0x015af878
jet_assert free  :CWThread.c, func: CWHandleTimer,line 720
jet_free:0x015b1f60
jet_assert free  :CWThread.c, func: CWHandleTimer,line 721
jet_free:0x015ae480
jet_assert malloc:CWThread.c, func: CWTimerRequest,line 733
jet_malloc:0x015b1c08
jet_assert malloc:CWThread.c, func: CWTimerRequest,line 734
jet_malloc:0x015d32e8
======>>end

三、小工具助力起飞

得到以上日志文件后,可能一段log就有几千上万行。如果毅力好的人可以手动找到malloc申请到的地址和free释放的地址匹配,并删除相关条目,一般来说存在内存泄露的泄露最后都会留下那么几行 malloc 信息。按照指示去一步步修改应该就没有问题了。

但是,我是一个懒人,一行一行的手动删除去除一对malloc和free的,我可能要疯掉。

所以我写了一下一个python小工具,按照上面所列举出的日志格式进行处理。极大的提高了我的效率。

注意

  1. 该python代码适合处理windows文本类型的字符串,如果直接从console中复制出来的文件,请使用 notepad++ 这类工具转换一下,在 编辑->文档格式转换->转为Windows格式。
  2. log文件名字为 memory.log,且应该与python脚本在同一个目录下。
  3. 脚本会剔除匹配的 malloc 与 free 项目,留下没有匹配的项目。
  4. 建议准备连续两段的log日志进行分析,因为程序时常这一轮申请的资源在下一轮中进行释放,这样子更易于分析。
  5. python新手,代码辣眼睛,用不了的根据自己实际情况修改。
    原文作者:爪爪熊
    原文地址: https://www.jianshu.com/p/b0b4e59dc947
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞