Home Memory Leakage Check Mechanism
Post
Cancel

Memory Leakage Check Mechanism

基于下面网站内容分析

https://blog.csdn.net/lqxandroid2012/article/details/79799844

一、libumem内存检查

https://www.codenong.com/8287649/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
环境变量
UMEM_DEBUG
此变量包含逗号分隔的选项列表。忽略无法识别的选项。可能的选项包括:

审计【=帧】
此选项启用记录审计信息,包括线程ID、高分辨率时间戳和每次分配上最后一个操作(分配或空闲)的堆栈跟踪。如果启用了事务日志记录(请参阅UMEM_LOGGING),则也会记录此审计信息。

frames参数设置审计结构中记录的堆栈帧数。帧的上限由实现定义。如果请求较大的值,则使用上界。

如果不指定frames或者frames不是整数,则使用默认值15。

此选项还启用“guards”选项。

内容【=计数】
如果启用了审计和内容记录(请参见UMEM_LOGGING),则每个缓冲区的第一个计数字节在释放时将被记录。如果缓冲区小于计数字节,则将其全部记录。

如果不指定count或者count不是整数,则默认为256。

违约
此选项等同于audit、contents、guards。

守卫
此选项允许用特殊模式填充已分配和释放的缓冲区,以帮助检测未初始化数据和以前释放的缓冲区的使用。它还在每个包含0xfeedfacefeedfaceULL的缓冲区之后启用一个8字节的红区。

当对象被释放时,它被填充为0xdeadbeef。分配对象时,校验0xdeadbeef模式,替换为0xbadcafe。每次分配或释放缓冲区时,都会检查红区。

对于具有构造函数或析构函数或析构函数的缓存,umem_cache_alloc(3MALLOC)和umem_cache_free(3MALLOC)分别应用缓存的构造函数和析构函数。而不是缓存构造对象。析构函数中是否存在断言( 3C )来验证缓冲区是否处于构造状态,可以用来检测任何在错误状态下返回的对象。详情请参见umem_cache_create(3MALLOC)。

冗长的
库在中止之前将错误描述写入标准错误。这些消息没有本地化。

UMEM_LOGGING
要启用,应将此变量设置为以逗号分隔的内存日志列表。可用的日志包括:

事务[=size]
如果设置了审计调试选项(请参阅UMEM_DEBUG),则先前事务的审计结构将输入到此日志中。

内容【=大小】
如果设置了审计调试选项,则对象的内容将在释放时记录到此日志中。

如果未设置“contents”调试选项,则每个释放的缓冲区将保存256字节。

失败【=大小】
每次失败的分配都会记录在此日志中。

对于这些选项中的任何一种,如果未指定size,则使用默认值64k。size参数必须是整数,可以与K、M、G或T限定,分别指定千字节、兆字节、千兆字节或TB。

未列出的日志或大小为0或无效的日志将被禁用。

如果在初始化期间无法分配请求的存储量,则禁用该日志。

二、glibc内存检测

1
2
3
reference:
https://www.cnblogs.com/arnoldlu/p/10827884.html
https://murphypei.github.io/blog/2019/01/linux-heap  glibc内存分配

Glibc中自带了一些Heap consistency checking机制。

** (1) MALLOC_CHECK_环境变量(double free)**

1
	检测内容:重复释放、头覆盖、尾覆盖。
  1. MALLOC_CHECK_=3 ./test

    1
    2
    3
    4
    
    0 - 不产生错误信息,也不中止这个程序
    1 - 产生错误信息,但是不中止这个程序
    2 - 不产生错误信息,但是中止这个程序
    3 - 产生错误信息,并中止这个程序
    
    不能检测内存未释放

    验证:double free场景

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
            int *p = (int *)malloc(10);
            int a;
            free(p);
            a = 0;
            free(p);
            return 0;
    }
    

    结果:

    1
    2
    3
    4
    5
    6
    
    [root@localhost mem_leak_way]# LIBC_FATAL_STDERR_=1 MALLOC_CHECK_=3 ./test
    free(): invalid pointer
    Aborted
    [root@localhost mem_leak_way]# LIBC_FATAL_STDERR_=1 MALLOC_CHECK_=4 ./test
    free(): double free detected in tcache 2
    Aborted
    

(2) mcheck

​ 与MALLOC_CHECK环境量功能差不多,但是需要重新编译代码。

1
	检测内容:重复释放、头覆盖、尾覆盖。
  1. mcheck是Glibc中的堆内存一致性检查机制.

    不能检测内存未释放

    验证:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <mcheck.h>  ///// 头文件
    int main()
    {
            //int *p = (int *)malloc(10);
            int *p1 = (int *)malloc(10);
            ///////再在要开始检查的地方加上////////////
            if (mcheck(NULL) != 0) {   
                    fprintf(stderr, "mcheck() failed\n");
                    exit(EXIT_FAILURE);
            }
            int a;
            //free(p);
            a = 0;
            //free(p);
            return 0;
    }
    

    结果:

    double free场景:

    1
    2
    3
    
    [root@localhost mem_leak_way]# ./bug 
    memory clobbered before allocated block
    Aborted
    

(3)mprobe

1
检查:double free、 头覆盖、尾覆盖;与mcheck一致;
1
2
#include <mcheck.h> //函数模型
enum mcheck_status mprobe(void *ptr);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <malloc.h>
#include <mcheck.h>
#include <errno.h>
#include <string.h>
void abortfun(enum mcheck_status mstatus)
{
    switch(mstatus) {
		case MCHECK_FREE: fprintf(stderr, "Block freed twice.\n"); break;
		case MCHECK_HEAD: fprintf(stderr, "Memory before the block was clobbered.\n"); break;
		case MCHECK_TAIL: fprintf(stderr, "Memory after the block was clobbered.\n"); break;
		default: fprintf(stderr, "Block is fine.\n");
    }
}
void main(void)
{
    char *s = NULL;
    if(mcheck(abortfun) != 0) //使用时,注册处理函数
    {
        fprintf(stderr, "mcheck:%s\n", strerror(errno));
        return;
    }
    s = malloc(32);
    mprobe(s);------------------------------正确
    mprobe(s-1);----------------------------错误,返回MCHECK_HEAD错误类型。
    mprobe(s+32);---------------------------错误,返回MCHECK_HEAD错误类型。
    free(s);
}

(4)-lmcheck编译选项

在编译的时候加上-lmcheck,不需要修改代码就可以对malloc()/free()进行检查; 但是需要重新编译代码!!!!

1
检查:double free、 头覆盖、尾覆盖。 与mcheck一致;

(5)mtrace (mtrace、muntrace、MALLOC_TRACE)

1
检查: 重复释放、泄漏

mtrace()和muntrace()函数分别在程序中打开和关闭对内存分配调用进行跟踪的功能。

这两个函数要与环境变量MALLOC_TRACE搭配使用,该变量定义了写入跟踪信息的文件名。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdlib.h>
#include <stdio.h>
#include <mcheck.h> //需要的头文件

int main(int argc, char **argv)
{
        mtrace(); //开始trace
        char * p = malloc(100);
        p = malloc(1000); 
      	free(p);
        muntrace(); //结束trace
        return 0;
}
1
2
gcc -g test-mtrace.c -o test-mtrace
export MALLOC_TRACE=/home/test/mtrace.log

mtrace记录的申请释放流程:

1
2
3
4
5
= Start
@ ./test-mtrace:[0x400684] + 0xc076a0 0x64
@ ./test-mtrace:[0x400692] + 0xc07710 0x3e8
@ ./test-mtrace:[0x4006a2] - 0xc07710
= End

配套mtrace命令可以分析具体的泄漏位置。

1
2
3
4
5
6
[root@localhost test]# mtrace test-mtrace mtrace.log 

Memory not freed:
-----------------
           Address     Size     Caller
0x0000000000c076a0     0x64  at /home/test/test-mtrace.c:8

三、gperftools(tcmalloc)内存检查机制

1
2
3
4
reference:
http://goog-perftools.sourceforge.net/doc/tcmalloc.html
https://blog.csdn.net/breaksoftware/article/details/81234967
http://www.cppblog.com/markqian86/archive/2018/08/24/215870.aspx
1
2
检查内容: 内存泄漏 heap-check
增强部分: 可以找到程序的内存使用热点。 heap-porfiler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@localhost test]# gcc test.c  -ltcmalloc -g -o test-tcmalloc
[root@localhost test]# HEAPCHECK=normal ./test-tcmalloc &
[1] 42880
[root@localhost test]# No live heap object at 0x7f8366695168 to ignore
WARNING: Perftools heap leak checker is active -- Performance may suffer
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 100 bytes in 2 objects
The 2 largest leaks:
*** WARNING: Cannot convert addresses to symbols in output below.
*** Reason: Cannot find 'pprof' (is PPROF_PATH set correctly?)
*** If you cannot fix this, try running pprof directly.
Leak of 50 bytes in 1 objects allocated from:
	@ 400657 
	@ 7f83662a96a3 
	@ 40052e 
Leak of 50 bytes in 1 objects allocated from:
	@ 4005f8 
	@ 400660 
	@ 7f83662a96a3 
	@ 40052e 


If the preceding stack traces are not enough to find the leaks, try running THIS shell command:

pprof ./test-tcmalloc "/tmp/test-tcmalloc.42880._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv

If you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1
If the leak report occurs in a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=false, it might help find lea
Exiting with error code (instead of crashing) because of whole-program memory leaks
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    TCMALLOC_DEBUG=<level>       调试级别,取值为1-2
         MALLOCSTATS=<level>                    设置显示内存使用状态级别,取值为1-2
         HEAPPROFILE=<pre>                     指定内存泄露检查的数据导出文件
         HEAPCHECK=<type>                        堆检查类型,type=normal/strict/draconian
TcMalloc库还可以进行内存泄露的检查,使用这个功能有两种方法:
1)将tcmalloc库链接到程序中,注意应该将tcmalloc库最后链接到程序中;
2)设置LD_PRELOAD=”libtcmalloc.so”/HEAPCHECK=normal,这样就不需重新编译程序
打开检查功能,有两种方式可以开启泄露检查功能:
1)  使用环境变量,对整个程序进行检查: HEAPCHECK=normal /bin/ls
2)  在源代码中插入检查点,这样可以控制只检查程序的某些部分,代码如下:
HeapProfileLeakCheckerchecker("foo");        // 开始检查
Foo();                                                                         // 需要检查的部分
assert(checker.NoLeaks());                                 // 结束检查
调用checker建立一个内存快照,在调用checker.NoLeaks建立另一个快照,然后进行比较,如果内存有增长或者任意变化,NoLeaks函数返回false,并输出一个信息告诉你如何使用pprof工具来分析具体的内存泄露。
    执行内存检查:
         #LD_PRELOAD=libtcmalloc.so HEAPCHECK=strict HEAPPROFILE=memtm ./a.out
执行完成后会输出检查的结果,如果有泄露,pprof会输出泄露多少个字节,有多少次分配,也会输出详细的列表指出在什么地方分配和分配多少次。
         比较两个快照:
         #pprof --base=profile.0001.heap 程序名 profile.0002.heap
    已知内存泄漏时,关闭内存泄露检查的代码:
void *mark =HeapLeakChecker::GetDisableChecksStart();
<leaky code>           //不做泄漏检查的部分
HeapLeakChecker::DisableChecksToHereFrom(mark);
         注:在某些libc中程序可能要关闭检查才能正常工作。
         注:不能检查数组删除的内存泄露,比如:char *str = new char[100]; delete str;。

四、jemalloc内存检查机制

1
2
3
4
reference:
https://zhuanlan.zhihu.com/p/48957114
https://blog.intzero.net/tools/jemalloc.html
https://github.com/jemalloc/jemalloc/wiki/Use-Case:-Leak-Checking
1
2
MALLOC_CONF=prof_leak:true,lg_prof_sample:0,prof_final:true \
LD_PRELOAD=${JEMALLOC_PATH}/lib/libjemalloc.so.2 w
1
2
<jemalloc>: Leak summary: 267184 bytes, 473 objects, 20 contexts
<jemalloc>: Run jeprof on "jeprof.19678.0.f.heap" for leak detail
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
jeprof --show_bytes `which w` jeprof.19678.0.f.heap
    Using local file /usr/bin/w.
Using local file jeprof.19678.0.f.heap.
Welcome to jeprof!  For help, type 'help'.
(jeprof) top
Total: 267184 B
  258032  96.6%  96.6%   258032  96.6% _3_2_5
    3616   1.4%  97.9%     3616   1.4% _nl_intern_locale_data
    2048   0.8%  98.7%     2208   0.8% __tzfile_read
    1024   0.4%  99.1%     1024   0.4% getpwnam
    1024   0.4%  99.5%     1072   0.4% getpwuid
     448   0.2%  99.6%      448   0.2% __gconv_lookup_cache
     384   0.1%  99.8%      384   0.1% getutent
     224   0.1%  99.9%      224   0.1% strdup
     160   0.1%  99.9%      160   0.1% __tzstring
     128   0.0% 100.0%     3760   1.4% _nl_load_locale_from_archive
      48   0.0% 100.0%       48   0.0% get_mapping
1
jeprof --show_bytes --pdf `which w` jeprof.19678.0.f.heap > w.pdf

五、valgrind内存检查机制

https://blog.csdn.net/CODINGCS/article/details/50357263

六、ASan内存检查机制

1
2
reference:
https://blog.csdn.net/lyj22/article/details/109459428?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.control

export ASAN_OPTIONS=halt_on_error=0 //使进程检测出内存错误的时候别退出

export ASAN_OPTIONS=alloc_dealloc_mismatch=0 //不检测内存不匹配的情况,例如 new [] 与delete point 不匹配

asan的选项很多,可以根据需要设置ASAN_OPTIONS的选项,其他选项可以自行百度

export LD_PRELOAD=/usr/lib64/libasan.so.5.0.0 //预加载运行库,替换系统库libc中内存分配函数

LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库,这个环境变量是必须的,因为libasan.so.5.0.0会替换掉libc中malloc和free函数的实现,所以需要将该库进行预加载。

至此,所有工作完成,直接运行所需要测试的程序即可,程序会打印出检测出来的内存问题。

七、Memwatch内存检查机制

1
2
3
reference:
https://blog.csdn.net/shangguanyunlan/article/details/68951525
https://blog.csdn.net/embrace_forest/article/details/52048694?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control

memwatch根本是不需要安装的,因为它只是一组C程序代码。需要做的是:

1、在代码中加入 memwatch.c 和 memwatch.h,一起编译、链接

2、编译时定义宏 DMEMWATCH、DMW_STDIO,即在编译程序时加上选项-DMEMWATCH -DMW_STDIO

5.1 MemWatch的内存处理

memWatch将全部分配的内存用0xFE填充,所以,假设你看到错误的数据是用0xFE填充的,那就是你没有初始化数据。例外是calloc(),它会直接把分配的内存用0*填充。

MemWatch将全部已释放的内存用0xFD填充(zapped with 0xFD).假设你发现你使用的数据是用0xFD填充的,那你就使用的是已释放的内存。在这样的情况,注意MemWatch会马上把一个释放了的块信息填在释放了的数据前。这个块包含关于内存在哪儿释放的信息,以可读的文本形式存放,格式为*“FBIfilename(line)"*。如*:"FBI<267>test.c(12)".*使用*FBI*会减少*free()*的速度,所以默认是关闭的。使用*mwFreeBufferInfo(1)*开启。

为了帮助跟踪野指针的写情况,MemWatch能提供no-mans-landNML)内存填充。no-mans-land将使用0xFC填充.no-mans-land开启时,MemWatch转变释放的内存为NML填充状态。

5.2 初始化和结束处理

一般来说,在程序中使用MemWatch的功能,须要手动加入mwInit()进行初始化,并用相应的mwTerm ()进行结束处理。

当然,假设没有手动调用mwInit()MemWatch能自己主动初始化.假设是这样的情形,memwatch会使用atext()注冊mwTerm()用于atexit-queue.对于使用自己主动初始化技术有一个告诫;假设你手动调用atexit()以进行清理工作,memwatch可能在你的程序结束前就终止。为了安全起见,请显式使用mwInit()mwTerm().

涉及的函数主要有:

mwInit() mwTerm() mwAbort()

5.3 MemWatchI/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()

八、Dr.Memory内存检查机制

重量级内存监测工具之一,用于检测如未初始化内存访问,越界访问,已释放内存访问,double free,memory leak以及Windows上的handle leak, GDI API usage error等。它支持Windows, Linux和Mac操作系统, IA-32和AMD64平台,和其它基于binary instrumentation的工具一样,它不需要改目标程序的binary。有个缺点是目前只针对x86上的32位程序。貌似目前正在往ARM上port。其优点是对程序的正常执行影响小,和Valgrind相比,性能更好。官网为http://www.drmemory.org/。Dr. Memory基于DynamioRIO Binary Translator。原始代码不会直接运行,而是会经过translation后生成code cache,这些code cache会调用shared instrumentation来做内存检测。

1
2
3
4
5
6
7
下载后即可直接使用。首先编译要检测的测试程序: 
$ g++ -m32 -g -Wall problem.cpp -o bug -fno-inline -fno-omit-frame-pointer 
(在64位host上编译32位程序需要安装libc6-dev-i386和g++-multilib) 
然后把Dr.Memory的bin加入PATH,如: 
$ export PATH=/home/jzj/tools/DrMemory-Linux-1.8.0-8/bin:$PATH 
之后就可以使用Dr.Memory启动目标程序: 
\$ drmemory – ./bug 

九、Electric Fence内存检查机制

Electric Fence主要用于追踪buffer overflow的读和写。它利用硬件来抓住越界访问的指令。其原理是为每一次内存申请额外申请一个page或一组page,然后把这些buffer范围外的page设为不可读写。这样,如果程序访问这些区域,由于页表中这个额外page的权限是不可读写,会产生段错误。那些被free()释放的内存也会被设为不可访问,因此访问也会产生段错误。因为读写权限以页为单位,所以如果多的页放在申请内存区域后,可防止overflow。如果要防止underflow,就得用环境变量EF_PROTECT_BELOW在区域前加保护页。因为Electric Fence至少需要丙个页来满足内存分配申请,因此内存使用会非常大,好处是它利用了硬件来捕获非法访问,因此速度快。也算是空间换时间吧。

十、Dmalloc内存检查机制

比较经典的内存检测工具,虽然N年没更新了。dmalloc通过在分配区域增加padding magic number的做法来检测非法访问,因此它能够检测到问题但不能检测出哪条指令出的错。Dmalloc只能检测越界写,但不能检测越界读。另外,Dmalloc只检测堆上用malloc系函数(而不是sbrk()或mmap())分配的内存,而无法对栈内存和静态内存进行检测。 本质上它也是通过hook malloc(), realloc(), calloc(),free()等内存管理函数,还有strcat(), strcpy()等内存操作函数,来检测内存问题。它支持x86, ARM平台,语言上支持C/C++,并且支持多线程。

十一、各种内存检测的差异

This post is licensed under CC BY 4.0 by the author.