对KERNRL在配置中打开DEBUG_KMEMLEAK即可, KMEMCHECK只支持x86
比较常见的内存错误一般分为两类:一类是申请释放相关的,一类是访问相关的。申请释放相关的又分为两种,即重复释放同一块内存和没有释放申请的内存,后者即通常所说的内存泄露。访问相关的是指访问了不应该被访问的地址,最常见的是缓冲区溢出。这些是自己的分类。按照书中的说法,内存错误包括下面这些:
1. 使用了未初始化的内存
2. 读写已经是放掉的内存
3. 之前分配了一段内存,访问了这段后面或者前面的内存
4. 读写栈上不正确的位置(通常意义上的栈缓冲区溢出的方式)
5. 内存泄露
对于内存检测有多种工具,在linux平台上,常见的比较典型的有
address-sanitizer
比较强大,可以检查栈及数组
MemWatch
如果让像我这样的菜鸟来实现内存泄露的检测,我会在每次malloc的时候记录下这次分配,然后在free的时候删除掉这次分配的记录。当程序运行结束之后,仍然被记录的则是泄露的内存。同理,对于重复free,因为第二次free没有对应的malloc记录,所以也会检测出来。
对于访问类错误,可以在真正分配的内存的前和后多分配一段内存,并且给这段特殊的内存以特殊的值,一些不容易出现的值。然后释放的时候,查看这些多分配的内存是否仍然是原来的默认值。如果不是,则说明被越界访问了。
MemWatch正式如此实现的。为了检测malloc和free,其使用宏替代实现了自己的malloc代替了gcc库的malloc。当然最后实际分配内存的时候仍然是使用gcc的malloc。在MemWatch的malloc中,其会记录下自己并且会在真正内存前后多分配几个字节。
具体而言,自己简单分析了一下MemWatch的代码。其对于每次分配都会多分配一些内存。会在需要的数据前后各多分配4个字节的数据,而且会填充"mEmwAtch"这8个字符中的四个,前后是相同的,只是一个big endian,一个litte endian。另外,其会使用atexit注册一个退出时调用的函数,其进行内存是否泄露的检查。
使用上很简单,只需要在需要内存检测的文件前面添加#include “memwatch.h”即可。之后在编译的时候添加编译选项-DMEMWATCH -DMW_STDIO,并且在编译的源文件中添加memwatch.c。编译之后的可执行文件就是支持内存检测的。运行之后会提示可能有内存错误。打开memwatch.log可以查看详细信息。其缺点是不支持c++,而且会对源代码造成污染。而且从原理看会产生内存访问错误的误报,当然情况极少。另外这种比较简单的方式估计在多线程,信号等环境下会有些问题,这是自己猜的。
YAMD
对于水平高一些的Linux程序员来说,他们设计内存泄露检查的话,至少会避免污染原有的源代码。其实YAMD就采用了一种很简单的方法,即采用环境变量LD_PRELOAD为某个指定的动态库。因为该库会提前于gcc的c库导入,所以使用该库中的函数替换掉标准c库中的函数。该库中至少重新实现了malloc和free。
YAMD就是采用这种方法来避免源代码污染的。当然其在内存访问错误检查方面也显示了高级程序员的素养。yamd.c是YAMD的主要源代码。其中函数do_malloc是重新实现的malloc函数。它对于实际内存的分配,并没有使用默认的堆空间,而是通过mmap重新映射了VMA进行使用。例如如果用户需要malloc(1000),则默认配置下yamd中的do_malloc会分配12k的内存。前4k作为前缀内存,中间7k-8k是实际用户内存,后面4k是后缀内存。这三页帧虚拟内存中的前缀和后缀的属性都是不可访问的(通过mprotect(**, PROT_NONE))。所以说如果访问越界,特别是向后越界,则程序马上会报告段错误。这样子向前越界不够敏感,所以YAMD中也可以设置中间4k-5k是实际用户申请的内存,这样子向前越界敏感,向后却不敏感了。
这样子就是尽早发现访问越界,而不像MemWatch直到程序结束才报告。也导致了一个问题,就是内存消耗特别大。即使只malloc一个字节的内存,也会分配3个页帧的虚拟内存。所以YAMD的README中也提示用户要有足够大的虚拟内存才可以使用。而且个人觉得一般使用这些工具的时候,应该是测试环境中。所以没有必要那么及时报错。而且即使在真正环境中的话,这样大的内存消耗,估计也非用户所能忍受的。
至于内存泄露和重复释放的检测,也是通过一个hash表来维护每次的申请和释放。类似于MemWatch。
其使用方法是在run-yamd之后再接上真正需要运行的程序。然后就会在屏幕上显示出内存错误信息。遗憾的是其不像MemWatch可以直接显示错误出现的行数,而是显示了出错的物理地址。不过该工具附带了一个do-syms命令。运行do-syms+命令名就可以在其中输入物理地址,之后会查出对应的文件和行。只是不太方便。其实do-syms内部是调用了linux命令addr2line来做的实际操作,这可以参考YAMD包的do-syms.c文件。
ValGrind
Valgrind其使用起来倒是很简单,一般情况下需要执行“valgrind -v --leak-check=yes --show-reachable=yes命令名”,然后就会打印出各种内存访问和内存释放类错误。内存错误的具体文件和行数都可以显示,前提是被调试程序是-g编译的。
Valgrind也是编译产生一个valgrind.so文件,然后通过在valgrind脚本中设置LD_PRELOAD宏。之后的话当运行目标文件时,首先会运行so文件中的初始化代码,在初始化代码中会创建一个虚拟CPU。之后的所有代码包括目标程序代码都是在这个虚拟CPU上运行的,直到结束。
在虚拟CPU上会监测包括malloc,free这些申请释放内存的函数对应的二进制指令以及访问内存的机器指令,所以使用该方法会对目标代码具有完全的控制功能。
参考
MemWatch主页:http://www.linkdata.se/sourcecode.html
YAMD主页:http://www.cs.hmc.edu/~nate/yamd/
VALGRIND主页:http://valgrind.org/.
VALGRIND设计文档:http://valgrind.org/docs/manual/mc-tech-docs.html
关于LD_RRELOAD的使用:http://www.linuxjournal.com/article/7795