使用valgrind检测ATS插件中的内存泄露

一.内存错误出现的场景

这几天在重构ATS插件代码的过程中遇到了烦人的内存泄露问题, 周五周六连续两天通过走查代码的方法,未能看出明显的导致内存错误的代码, 同时也觉得C和C++混合编程得到一个动态库, 在一个.cpp主文件中,即用new又用malloc来动态分配内存, 可能会导致内存错误.后来网上调研和查资料发现, new和malloc混用还是允许的,因为C++兼容C.虽然这种混用方式不提倡, 但绝对不会导致内存堆栈错乱的情况.

这里之所以采用C和C++混合编程,是因为业务需要. ATS插件目前设计为一个transform主框架, 外加若干子模块, 一个子模块一个动态库,后者可以根据不同业务需要进行扩展,而transform类型的插件框架一般不变, 它只完成通常的transform变换流程就可以了. 这个主框架是使用C++开发的, 它提供了业务实现接口, 供每个业务动态库实现.因为接口入参中用到了stl的map结构来在主框架和子模块之间传递参数,所以接口的实现必须是C++方式的. 但是我的一个业务插件原来是使用纯c编写的, 引用了几个第三方库,比如JSON, libz, pcre库等, 它们原来已经用C编写好了,并且经过测试发现是可靠的, 如果再换为C++实现一般不太现实, 也没有必要. 这样造成的局面就是, 目前只能C和C++进行混合编程, 对C提供的接口, 要在h文件中使用 extern "C"声明, 并将实现存放在对应的c文件中.(这里必须要分离成两个文件, 如果以前只有一个h文件就搞定的, 现在要分为h和c文件).在编译出一个so时, 同时要外链其他的第三方基础库时, Makefile的编写通常有些技巧的, 请参见我的博文

C和C++混合编程的Makefile的编写!

这种情况下, 对C那部分的调用,我采用malloc和free来分配和释放内存, 对上层接口那部分, 我在主cpp文件中使用new和delete来分配内存.这里明显使得内存的使用更加复杂化了,但是不管怎样,按理说这种做法还是行得通的.

但是在调试过程中,我却发现了一个内存越界的错误, 每次提示的错误都不一样, 但是显示出错的位置又没有明显的错误, 真是让人摸不到头脑, 搞不定了, 郁闷, 失败至极.....

二.内存错误出现的表象

两天的搜索无果, 让我只能寻求内存检测软件的帮助. 我以前用过tcmalloc, 就是google的gperftools里面的一个工具,它会将系统默认的内存分配释放函数替换为自己的函数再进行检测,但是在这里不方便施展, 因为这里new和malloc都用上了, 所以我决定使用valgrind来检测. 具体安装使用方法,请参见

在Ubuntu 14.04 64bit上安装Valgrind并检查内存泄露

下面是ATS插件运行中崩溃时valgrind检测的几个错误截图

使用valgrind检测ATS插件中的内存泄露_第1张图片

该截图告诉我们, pool_alloc(在mem_manage.c第17行)调用malloc分配了1813字节内存, find_replace_html(位于main.cpp第484行)函数使用memcpy从1808处开始要复制8个字节, 而事实上只能复制4字节,这会内存越界,导致非法的8字节写入(越界写入).

使用valgrind检测ATS插件中的内存泄露_第2张图片

该截图告诉我们,  pool_alloc(在mem_manage.c第17行)调用malloc分配了1813字节内存,pcre_exec(在 regex_lookup.h第45行)在1813字节后面越界读取了1个字节.


该截图告诉我们, 在分配的1813字节内部的1808字节处, 要非法读取8字节内存(越界读取)

三.内存错误的分析和确定

上面的截图分析, 很明确地传递出内存越界读取和越界写入的错误, 但是我查看内存分配的地方, 没有看到明显的错误.但是多次调试显示的内存错误大多显示到memset这样的地方, 将部分memset代码注释掉后, 程序能运行, 但是运行一段时间后,它还是会有段错误, 看来找到的位置不对, 而且上面截图显示的都是造成错误的表现, 不是真正造成内存出错的地方.最后经过认真理解valgrind的错误提示, 和仔细地分析代码, 终于定位到内存错误的真正地方不是malloc长度那里, 而是memset那里, 初始化内存的那个指针只能是一个void*或char*, 不能是一个结构体, 这就是内存错误的真正地方.

参见下面的截图, 有内存错误的源码截图

使用valgrind检测ATS插件中的内存泄露_第3张图片

改正内存错误的源码截图

使用valgrind检测ATS插件中的内存泄露_第4张图片

改正后, 重新编译调试, 一切正常, 运行很长时间, 都没有再崩掉, 再次印证修改是正确的.而这个错误, 是一个大家看起来很可笑的问题, 所要大家编写代码时还是要提高修养和注意力,千万不要马虎写代码,否则会害死自己的.

四.回顾反思

bug不可怕, 犯错不可耻, 但是仔细分析导致bug的原因, 吸取教训并避免再犯才是最重要的. 另外一点是, 排错的锻炼和快速定位是一项职业素养, 越修bug越有收获越能提高, 经历越丰富自信心越强, 所以bug来了,我们还是得从容面对.

你可能感兴趣的:(内存泄露,ATS插件)