使用valgrind工具定位内存泄漏问题

1 valgrind基础知识

1.1 valgrind简介

Valgrind是一套Linux下,开放源代码(GPL V2)的仿真调试工具的集合。

Valgrind由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(framework),它模拟了一个CPU环境,并提供服务给其他工具;而其他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。

 1.2 valgrind体系结构

Valgrind的体系结构如下图所示:

The Valgrind distribution currently includes seven production-quality tools: a memory error detector, two thread error detectors, a cache and branch-prediction profiler, a call-graph generating cache and branch-prediction profiler, and two different heap profilers.

It also includes an experimental SimPoint basic block vector generator.

(1)Memcheck
最常用的工具,用来检测程序中出现的内存问题,所有对内存的读写都会被检测到,一切对malloc()/free()/new/delete的调用都会被捕获。

它能检测以下问题:

  • 对未初始化内存的使用;
  • 读/写释放后的内存块;
  • 读/写超出malloc分配的内存块;
  • 读/写不适当的栈中内存块;
  • 内存泄漏,指向一块内存的指针永远丢失;
  • 不正确的malloc/free或new/delete匹配;
  • memcpy()相关函数中的dst和src指针重叠。

(2)Callgrind
和gprof类似的分析工具,但它对程序的运行观察更是入微,能给我们提供更多的信息。和gprof不同,它不需要在编译源代码时附加特殊选项,但加上调试选项是推荐的。Callgrind收集程序运行时的一些数据,建立函数调用关系图,还可以有选择地进行cache模拟。在运行结束时,它会把分析数据写入一个文件。callgrind_annotate可以把这个文件的内容转化成可读的形式。
(3)Cachegrind
Cache分析器,它模拟CPU中的一级缓存I1,Dl和二级缓存,能够精确地指出程序中cache的丢失和命中。如果需要,它还能够为我们提供cache丢失次数,内存引用次数,以及每行代码,每个函数,每个模块,整个程序产生的指令数。这对优化程序有很大的帮助。
(4)Helgrind
它主要用来检查多线程程序中出现的竞争问题。Helgrind寻找内存中被多个线程访问,而又没有一贯加锁的区域,这些区域往往是线程之间失去同步的地方,而且会导致难以发掘的错误。Helgrind实现了名为“Eraser”的竞争检测算法,并做了进一步改进,减少了报告错误的次数。不过,Helgrind仍然处于实验阶段。
(5)Massif
堆栈分析器,它能测量程序在堆栈中使用了多少内存,告诉我们堆块,堆管理块和栈的大小。Massif能帮助我们减少内存的使用,在带有虚拟内存的现代系统中,它还能够加速我们程序的运行,减少程序停留在交换区中的几率。
此外,lackey和nulgrind也会提供。Lackey是小型工具,很少用到;Nulgrind只是为开发者展示如何创建一个工具。


1.3 Memcheck原理


Memcheck 能够检测出内存问题,关键在于其建立了两个全局表。Valid-Value 表与Valid-Address 表。

1.3.1 Valid-Value 表


对于进程的整个地址空间中的每一个字节(byte),都有与之对应的 8 个 bits;对于CPU的每个寄存器,也有一个与之对应的bit向量。这些bits负责记录该字节或者寄存器值是否具有有效的、已初始化的值。


1.3.2 Valid-Address 表


对于进程整个地址空间中的每一个字节(byte),还有与之对应的1个bit,负责记录该地址是否能够被读写。
检测原理:
当要读写内存中某个字节时,首先检查这个字节对应的 A bit。如果该A bit显示该位置是无效位置,memcheck则报告读写错误。

1.4 valgrind内核


内核(core)类似于一个虚拟的 CPU 环境,这样当内存中的某个字节被加载到真实的 CPU 中时,该字节对应的 V bit 也被加载到虚拟的 CPU 环境中。一旦寄存器中的值,被用来产生内存地址,或者该值能够影响程序输出,则 memcheck 会检查对应的V bits,如果该值尚未初始化,则会报告使用未初始化内存错误。

1.5 valgrind 可使用的平台

It runs on the following platforms: X86/Linux, AMD64/Linux, ARM/Linux, ARM64/Linux, PPC32/Linux, PPC64/Linux, PPC64LE/Linux, S390X/Linux, MIPS32/Linux, MIPS64/Linux, X86/Solaris, AMD64/Solaris, ARM/Android (2.3.x and later), ARM64/Android, X86/Android (4.0 and later), MIPS32/Android, X86/FreeBSD, AMD64/FreeBSD, X86/Darwin and AMD64/Darwin (Mac OS X 10.12). 

2 valgrind下载安装

下载地址:Valgrind Home 

(1)将下载的安装包放到环境中,解压缩
tar –jxvf valgrind-3.2.3.tar.bz2


(2)进入解压生成的目录
cd valgrind-3.2.3
 

(3)运行./autogen.sh设置环境
./autogen.sh


(4)./configure;配置Valgrind,生成MakeFile文件
./configure


(5)make;编译Valgrind
Make


(6)make install;安装Valgrind

 启动进程:

valgrind --log-file=./valgrind_report.log --leak-check=full /test

3 valgrind定位方法

3.1 Memcheck 工具定位方法

(1)数组越界/内存未释放

#include<stdlib.h>
void k(void)
{
    int *x = malloc(8 * sizeof(int));
    x[9] = 0; //数组下标越界
} //内存未释放

int main(void)
{
    k();
    return 0;
}

valgrind --tool=memcheck --leak-check=full ./test
#--leak-check=full 所有泄露检查
运行结果如下:
==2989== Memcheck, a memory error detector
==2989== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward
et al.
==2989== Using Valgrind-3.8.1 and LibVEX; rerun with -h for
copyright info
==2989== Command: ./test
==2989==
==2989== Invalid write of size 4
==2989== at 0x4004E2: k (test.c:5)
==2989== by 0x4004F2: main (test.c:10)
==2989== Address 0x4c27064 is 4 bytes after a block of size 32 alloc'd
==2989== at 0x4A06A2E: malloc (vg_replace_malloc.c:270)
==2989== by 0x4004D5: k (test.c:4)
==2989== by 0x4004F2: main (test.c:10)
==2989==
==2989==
==2989== HEAP SUMMARY:
==2989== in use at exit: 32 bytes in 1 blocks
==2989== total heap usage: 1 allocs, 0 frees, 32 bytes allocated
==2989==
==2989== 32 bytes in 1 blocks are definitely lost in loss record 1
of 1
==2989== at 0x4A06A2E: malloc (vg_replace_malloc.c:270)
==2989== by 0x4004D5: k (test.c:4)
==2989== by 0x4004F2: main (test.c:10)
==2989==
==2989== LEAK SUMMARY:
==2989== definitely lost: 32 bytes in 1 blocks
==2989== indirectly lost: 0 bytes in 0 blocks
==2989== possibly lost: 0 bytes in 0 blocks
==2989== still reachable: 0 bytes in 0 blocks
==2989==suppressed: 0 bytes in 0 blocks
==2989==
==2989== For counts of detected and suppressed errors, rerun with: -v
==2989== ERROR SUMMARY: 2 errors from 2 contexts
(suppressed: 6 from 6)

(2)内存释放后读写

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char *p = malloc(1); //分配
    *p = 'a';
    char c = *p;

    printf("\n [%c]\n",c);

    free(p); //释放
    c = *p; //取值
    return 0;
}

 valgrind --tool=memcheck --leak-check=full ./t2
运行结果如下:
==3058== Memcheck, a memory error detector
==3058== Copyright (C) 2002-2012, and GNU GPL'd, by Julian
Seward et al.
==3058== Using Valgrind-3.8.1 and LibVEX; rerun with -h
for copyright info
==3058== Command: ./t2
==3058==

[a]
==3058== Invalid read of size 1
==3058== at 0x4005A3: main (t2.c:14)
==3058== Address 0x4c27040 is 0 bytes inside a block of size
1 free'd
==3058== at 0x4A06430: free (vg_replace_malloc.c:446)
==3058== by 0x40059E: main (t2.c:13)
==3058==
==3058==
==3058== HEAP SUMMARY:
==3058== in use at exit: 0 bytes in 0 blocks
==3058== total heap usage: 1 allocs, 1 frees, 1 bytes allocated
==3058==
==3058== All heap blocks were freed -- no leaks are possible
==3058==
==3058== For counts of detected and suppressed errors, rerun with:
-v
==3058== ERROR SUMMARY: 1 errors from 1 contexts
(suppressed: 6 from 6)
从上输出内容可以看到,Valgrind检测到无效的读取操作然后输出“Invalid read of size 1”。

(3)内存泄露

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *p = malloc(1);
    *p = 'x';
    char c = *p;
    printf("%c\n",c); //申请后未释放
    return 0;
}

 valgrind --tool=memcheck --leak-check=full ./t4
运行结果如下:
==3221== Memcheck, a memory error detector
==3221== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==3221== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==3221== Command: ./t4
==3221==
==3221== Invalid write of size 4
==3221==at 0x40051E: main (t4.c:7)
==3221==Address 0x4c27040 is 0 bytes inside a block of size 1 alloc'd
==3221==at 0x4A06A2E: malloc (vg_replace_malloc.c:270)
==3221==by 0x400515: main (t4.c:6)
==3221==
==3221== Invalid read of size 4
==3221==at 0x400528: main (t4.c:8)
==3221==Address 0x4c27040 is 0 bytes inside a block of size 1 alloc'd
==3221==at 0x4A06A2E: malloc (vg_replace_malloc.c:270)
==3221==by 0x400515: main (t4.c:6)
==3221==
x
==3221==
==3221== HEAP SUMMARY:
==3221==in use at exit: 1 bytes in 1 blocks
==3221==total heap usage: 1 allocs, 0 frees, 1 bytes allocated
==3221==
==3221== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1
==3221==at 0x4A06A2E: malloc (vg_replace_malloc.c:270)
==3221==by 0x400515: main (t4.c:6)
==3221==
==3221== LEAK SUMMARY:
==3221==definitely lost: 1 bytes in 1 blocks
==3221==indirectly lost: 0 bytes in 0 blocks
==3221== possibly lost: 0 bytes in 0 blocks
==3221==still reachable: 0 bytes in 0 blocks
==3221== suppressed: 0 bytes in 0 blocks
==3221==
==3221== For counts of detected and suppressed errors, rerun with: -v
==3221== ERROR SUMMARY: 3 errors from 3 contexts
(suppressed: 6 from 6)

(4)内存多次释放

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    char *p;
    p=(char *)malloc(100);
    if(p)
        printf("Memory Allocated at: %s/n",p);
    else
        printf("Not Enough Memory!/n");
    free(p); //重复释放
    free(p);
    free(p);
    return 0;
}

valgrind --tool=memcheck --leak-check=full ./t5
 运行结果如下:
==3294== Memcheck, a memory error detector
==3294== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward
et al.
==3294== Using Valgrind-3.8.1 and LibVEX; rerun with -h for
copyright info
==3294== Command: ./t5
==3294==
==3294== Conditional jump or move depends on uninitialised value(s)
==3294== at 0x3CD4C47E2C: vfprintf (in /lib64/libc-2.12.so)
==3294== by 0x3CD4C4F189: printf (in /lib64/libc-2.12.so)
==3294== by 0x400589: main (t5.c:9)
==3294==
==3294== Invalid free() / delete / delete[] / realloc()
==3294== at 0x4A06430: free (vg_replace_malloc.c:446)
==3294== by 0x4005B5: main (t5.c:13)
==3294== Address 0x4c27040 is 0 bytes inside a block of size
100 free'd
==3294== at 0x4A06430: free (vg_replace_malloc.c:446)
==3294== by 0x4005A9: main (t5.c:12)
==3294==
==3294== Invalid free() / delete / delete[] / realloc()
==3294== at 0x4A06430: free (vg_replace_malloc.c:446)
==3294== by 0x4005C1: main (t5.c:14)
==3294== Address 0x4c27040 is 0 bytes inside a block of size
100 free'd
==3294== at 0x4A06430: free (vg_replace_malloc.c:446)
==3294== by 0x4005A9: main (t5.c:12)
==3294==
Memory Allocated at: /n==3294==
==3294== HEAP SUMMARY:
==3294== in use at exit: 0 bytes in 0 blocks
==3294== total heap usage: 1 allocs, 3 frees, 100 bytes allocated

(5)内存动态管理

#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
    int i;
    char* p = (char*)malloc(10);
    char* pt=p;
    for(i = 0;i < 10;i++)
    {
        p[i] = 'z';
    }
    free(p);
    pt[1] = 'x';
    free(pt);
    return 0;
}

 申请内存在使用完成后就要释放。如果没有释放,或少释放了就是内存泄露;多释放也会产生问题。上述程序中,指针p和pt指向的是同一块内存,却被先后释放两次。系统会在堆上维护一个动态内存链表,如果被释放,就意味着该块内存可以继续被分配给其他部分,如果内存被释放后再访问,就可能覆盖其他部分的信息,这是一种严重的错误,上述程序第14行中就在释放后仍然写这块内存。

参考文献:

【1】Valgrind使用指南和错误分析:Valgrind使用指南和错误分析 - tigerloveapple - 博客园

【2】 内存泄漏检测工具valgrind神器:内存泄漏检测工具valgrind神器 - 知乎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值