现在的位置: 首页 > 综合 > 正文

Memwatch

2014年03月22日 ⁄ 综合 ⁄ 共 5589字 ⁄ 字号 评论关闭

介绍
MemWatch
 Johan Lindh 编写,是一个开放源代码 C 语言内存错误检测工具。MemWatch支持 ANSI
C
它提供结果日志纪录,能检测双重释放(double-free)、错误释放(erroneous free)、内存泄漏(unfreed
memory
)、溢出(Overflow)、下溢(Underflow)
等等。


1.1 MemWatch
的内存处理
MemWatch
将所有分配的内存用0xFE填充,所以,如果你看到错误的数据是用0xFE填充的,那就是你没有初始化数据。例外是calloc(),它会直接把分配的内存用0填充。
MemWatch
将所有已释放的内存用0xFD填充(zapped with 0xFD).如果你发现你使用的数据是用0xFD填充的,那你就使用的是已释放的内存。在这种情况,注意MemWatch会立即把一个"释放了的块信息"填在释放了的数据前。这个块包括关于内存在哪儿释放的信息,以可读的文本形式存放,格式为"FBI<counter>filename(line)"。如:"FBI<267>test.c(12)".使用FBI会降低free()的速度,所以默认是关闭的。使用mwFreeBufferInfo(1)开启。 
为了帮助跟踪野指针的写情况,MemWatch能提供no-mans-landNML)内存填充。no-mans-land将使用0xFC填充.no-mans-land开启时,MemWatch转变释放的内存为NML填充状态。


1.2
初始化和结束处理
一般来说,在程序中使用MemWatch的功能,需要手动添加mwInit()进行初始化,并用对应的mwTerm ()进行结束处理。
当然,如果没有手动调用mwInit()MemWatch能自动初始化.如果是这种情形,memwatch会使用atext()注册mwTerm()用于atexit-queue. 对于使用自动初始化技术有一个告诫;如果你手动调用atexit()以进行清理工作,memwatch可能在你的程序结束前就终止。为了安全起见,请显式使用mwInit()mwTerm().
涉及的函数主要有:

mwInit()mwTerm() mwAbort()


1.3 MemWatch
I/O 操作
对于一般的操作,MemWatch创建memwatch.log文件。有时,该文件不能被创建;MemWatch会试图创建memwatNN.log文件,NN01~99之间。
如果你不能使用日志,或者不想使用,也没有问题。只要使用类型为"void func(int c)"的参数调用mwSetOutFunc(),然后所有的输出都会按字节定向到该函数.
ASSERT或者VERIFY失败时,MemWatch也有Abort/Retry/Ignore处理机制。默认的处理机制没有I/O操作,但是会自动中断程序。你可以使用任何其他Abort/Retry/Ignore的处理机制,只要以参数"void
func(int c)"
调用mwSetAriFunc()。后面在1.2使用一节会详细讲解。

涉及的函数主要有:
mwTrace() mwPuts()    mwSetOutFunc() mwSetAriFunc()

mwSetAriAction() mwAriHandler() mwBreakOut() 


1.4 MemWatch
C++的支持
可以将MemWatch用于C++,但是不推荐这么做。请详细阅读memwatch.h中关于对C++的支持。

使用
2.1 
为自己的程序提供MemWatch功能
在要使用MemWatch.c文件中包含头文件"memwatch.h"
使用GCC编译(注意:不是链接)自己的程序时,加入
-DMEMWATCH
-DMW_STDIO
如:gcc -DMEMWATCH -DMW_STDIO o test.o 
  test1.c 

2.2 
使用MemWatch提供的功能

1
)在程序中常用的MemWatch功能有:
mwTRACE ( const char* format_string, ... );
TRACE ( const char* format_string, ... );
mwASSERT ( int, const char*, const char*, int )
ASSERT ( int, const char*, const char*, int )
mwVERIFY ( int, const char*, const char*, int )
VERIFY ( int, const char*, const char*, int )
mwPuts ( const char* text )
ARI
机制( mwSetAriFunc(int (*func)(const char *))

mwSetAriAction(int action)

mwAriHandler ( const char* cause )

mwSetOutFunc (void (*func)(int))

mwIsReadAddr(const void *p, unsigned len )

mwIsSafeAddr(void *p, unsigned len )

mwStatistics ( int level )

mwBreakOut ( const char* cause)


2
mwTRACEmwASSERTmwVERIFYmwPuts顾名思义,就不再赘述。仅需要注意的是,Memwatch定义了宏TRACE, ASSERT  VERIFY.如果你已使用同名的宏,memwatch2.61及更高版本的memwatch不会覆盖你的定义。MemWatch2.61及以后,定义了mwTRACE,
mwASSERT 
 mwVERIFY宏,这样,你就能确定使用的是memwatch的宏定义。2.61版本前的memwatch会覆盖已存在的同名的TRACE,
ASSERT 
 VERIFY定义。

当然,如果你不想使用MemWatch的这几个宏定义,可以定义MW_NOTRACE, MW_NOASSERT  MW_NOVERIFY宏,这样MemWatch的宏定义就不起作用了。所有版本的memwatch都遵照这个规则。
3
ARI机制即程序设置的“Abort, Retry, Ignore选择陷阱。
mwSetAriFunc

设置“Abort, Retry, Ignore”发生时的MemWatch调用的函数.当这样设置调用的函数地址时,实际的错误消息不会打印出来,但会作为一个参数进行传递。
如果参数传递NULLARI处理函数会被再次关闭。当ARI处理函数关闭后, meewatch会自动调用有mwSetAriAction()指定的操作。
正常情况下,失败的ASSERT() or VERIFY()会中断你的程序。但这可以通过mwSetAriFunc()改变,即通过将函数"int
myAriFunc(const char *)"
传给它实现。你的程序必须询问用户是否中断,重试或者忽略这个陷阱。返回2用于Abort 1用于Retry,或者0对于Ignore。注意retry时,会导致表达式重新求值
.
MemWatch
有个默认的ARI处理器。默认是关闭的,但你能通过调用mwDefaultAri()开启。注意这仍然会中止你的程序除非你定义MEMWATCH_STDIO允许MemWatch使用标准CI/O流。

同时,设置ARI函数也会导致MemWatch不将ARI的错误信息写向标准错误输出,错误字符串而是作为'const
char *'
参数传递到ARI函数
.

mwSetAriAction

如果没有ARI处理器被指定,设置默认的ARI返回值。默认是MW_ARI_ABORT

mwAriHandler

这是个标准的ARI处理器,如果你喜欢就尽管用。它将错误输出到标准错误输出,并从标准输入获得输入。
mwSetOutFunc

将输出转向调用者给出的函数(参数即函数地址)。参数为NULL,表示把输出写入日志文件memwatch.log.

mwIsReadAddr:
检查内存是否有读取的权限

mwIsSafeAddr:
检查内存是否有读、写的权限
mwStatistics:
设置状态搜集器的行为。对应的参数采用宏定义。
#define MW_STAT_GLOBAL 0 /* 
仅搜集全局状态信息 */

#define MW_STAT_MODULE 1     /* 
搜集模块级的状态信息
 */

#define MW_STAT_LINE 2 /* 
搜集代码行级的状态信息
 */

#define MW_STAT_DEFAULT 0 /* 
默认状态设置
 */

mwBreakOut: 
当某些情况MemWatch觉得中断(break into)编译器更好时,就调用这个函数.如果你喜欢使用MemWatch,那么可以在这个函数上设置执行断点。

其他功能的使用,请参考源代码的说明。

2.3
分析日志文件
日志文件memwatch.log中包含的信息主要有以下几点:
测试日期
状态搜集器的信息
使用MemWatch的输出函数或宏(如TRACE等)的信息。
MemWatch
捕获的错误信息
内存使用的全局信息统计,包括四点:1)分配了多少次内存 2)最大内存使用量3)分配的内存总量 4)为释放的内存总数
MemWatch
捕获的错误记录在日志文件中的输出格式如下:

2.4 
注意事项
mwInit()
mwTerm()是对应的.所以使用了多少次mwInit(),就需要调用多少次
mwTerm()
用于终止MemWatch.
如果在流程中捕获了程序的异常中断,那么需要调用mwAbort()而不是

mwTerm()
。即使有显示的调用mwTerm()mwAbort()也将终止MemWatch
size="3"

MemWatch
不能确保是线程安全的。如果你碰巧使用Wind32或者你使用了线程,作为2.66,是初步支持线程的。定义WIN32或者MW_PTHREADS以明确支持线程。这会导致一个全局互斥变量产生,同时当访问全局内存链时,MemWatch会锁定互斥变量,但这远不能证明是线程安全的。
结论

MemWatch的使用可以得知,无法用于内核模块。因为MemWatch自身就使用了应用层的接口,而不是内核接口。但是,对于普通的应用层程序,还是比较有用,并且是开源的,可以自己修改代码实现;它能方便地查找内存泄漏,特别是提供的接口函数简单易懂,学习掌握很容易,对应用层程序的单元测试会较适用。

memwatch输出結果

memwatch 的输出文件名称为memwatch.log,而且在程序执行期间,所有错误提示都会显示在stdout上,如果memwatch未能写入以上文件,它会尝试写入memwatchNN.log,而NN介于01至99之间,若它仍未能写入memwatchNN.log,则会放弃写入文件。

我们引用第一篇(mtrace)中所使用过的有问题的代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <memwatch.h>
    int main() {
        char *hello;

        setenv("MALLOC_TRACE", "output", 1);
        mtrace();
        if ((hello = (char *) malloc(sizeof(char))) == NULL) {
            perror("Cannot allocate memory.");
            return -1;
        }

        return 0;
    }

然后在shell中输入以下编译指令:

    gcc -DMEMWATCH -DMW_STDIO test.c memwatch.c -o test

memwatch.log的內容如下:

    ============= MEMWATCH 2.71 Copyright (C) 1992-1999 Johan Lindh =============

    Started at Sat Jun 26 22:48:47 2004

    Modes: __STDC__ 32-bit mwDWORD==(unsigned long)
    mwROUNDALLOC==4 sizeof(mwData)==32 mwDataSize==32

    Stopped at Sat Jun 26 22:48:47 2004

        unfreed: <1> test.c(9), 1 bytes at 0x805108c    {FE .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .}

    Memory usage statistics (global):
     N)umber of allocations made: 1
     L)argest memory usage      : 1
     T)otal of all alloc() calls: 1
     U)nfreed bytes totals      : 1

文件指出,在test.c被执行到第9行时所分配的内存仍未被释放,该段内存的大小为1 byte。

Memwatch使用注意

Memwatch 的优点是无需特別配置,不需安装便能使用,但缺点是它会拖慢程序的运行速度,尤其是释放内存时它会作大量检查。但它比mtrace和dmalloc多了 一项功能,就是能模拟系统内存不足的情況,使用者只需用mwLimit(long num_of_byte)函数来限制程式的heap memory大小(以byte单位)。

抱歉!评论已关闭.