admin管理员组

文章数量:1531689

  • Valgrind手册
  • Quick start

文章目录

  • 概述
    • 体系结构
    • Valgrind 原理
    • 安装
    • 编译时需要注意
    • 快速入门
    • LEAK SUMMARY:内存泄漏总结(分类)
  • 实践
    • 第一个例子:没有内存泄漏
    • 第二个例子:只申请内存而不释放
      • 编译程序1
      • 编译程序2
    • 第3个例子: 使用未初始化的内存
    • 第3个例子: 内存读写越界
    • 第4个例子: 重复释放
    • 第4个例子: malloc与delete释放问题
    • 第5个例子:内存覆盖

概述

体系结构

Valgrind 是一套linux下,开放源代码的仿真调试工具的集合。Valgrind有内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架,它模拟了一个CPU环境,并提供服务给其他工具;而其他工具则类似于插件,利用内核提供的服务完成各种特定的内存调试任务。


Valgrind 包括如下一些工具:

  • Memcheck。这是 valgrind 应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等
  • Callgrind。它主要用来检查程序中函数调用过程中出现的问题。
  • Cachegrind。它主要用来检查程序中缓存使用出现的问题。
  • Helgrind。它主要用来检查多线程程序中出现的竞争问题。
  • Massif。它主要用来检查程序中堆栈使用中出现的问题。
  • Extension。可以利用 core 提供的功能,自己编写特定的内存调试工具。

Valgrind 原理

valgrind是一个提供了一些debug和优化工具的工具箱,可以使得你的程序减少内存泄漏或者错误访问.

valgrind 默认使用 memcheck 去检查内存问题.

memcheck 检测内存问题的原理如下图所示:

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

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

检测原理:

  • 当要读写内存中某个字节时,首先检查 valid-address map 中这个字节对应的 A bit。如果该A bit显示该位置是无效位置,memcheck 则报告读写错误。
  • 内核(core)类似于一个虚拟的 CPU 环境,这样当内存中的某个字节被加载到真实的 CPU 中时,该字节对应的 V bit (在 valid-value map 中) 也被加载到虚拟的 CPU 环境中。一旦寄存器中的值,被用来产生内存地址,或者该值能够影响程序输出,则 memcheck 会检查对应的 V bits,如果该值尚未初始化,则会报告使用未初始化内存错误。

安装

centos下:

sudo yum install valgrind

源代码方式安装:

wget https://fossies/linux/misc/valgrind-3.15.0.tar.bz2
tar -jxvf valgrind-3.15.0.tar.bz2
cd valgrind-3.15.0
./configure 
make 
sudo make install

编译时需要注意

1、为了使 valgrind 发现的错误更精确,如能够定位到源代码行,建议在编译时加上-g
参数,编译优化选项请选择 O0,虽然这会降低程序的执行效率。

对于CMakelist

add_definitions("-Wall -g")

2、对于C++程序,请启动-fno-inline忽略代码中的 inline 关键字,该选项使编译器将内联函数以普通函数对待;等同无优化选项时的处理)。这样可以更轻松地查看函数调用链。

或者,Valgrind选项 --read-inline-info=yes指示Valgrind读取描述内联信息的调试信息

快速入门

使用valgrind 很简单, 首先编译好要测试的程序 (为了使valgrind发现的错误更精确,如能够定位到源代码行,建议在编译时加上-g参数,编译优化选项请选择O0,虽然这会降低程序的执行效率。), 假设运行这个程序的命令是

./a.out arg1 arg2

那么要使用 valgrind 的话只需要运行

valgrind --leak-check=yes ./a.out arg1 arg2

LEAK SUMMARY:内存泄漏总结(分类)

definitely lost: 4 bytes in 1 blocks:绝对丢失,这种情况应该由程序员来解决,下面几种情况,可以当作参考

  • indirectly lost: 0 bytes in 0 blocks:间接丢失
  • possibly lost: 0 bytes in 0 blocks:可能丢失
  • still reachable: 0 bytes in 0 blocks:仍然可以访问
  • suppressed: 0 bytes in 0 blocks:抑制错误中的丢失

实践

第一个例子:没有内存泄漏

#include <iostream.h>
int main()
{
 cout << "Hello kiccleaf!/n" << endl;
 return 0;
}

编译

gcc -o ./Share ./main.cpp

运行:

valgrind --tool=memcheck --leak-check=full ./Share

结果:

==56806== Memcheck, a memory error detector
==56806== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==56806== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==56806== Command: ./Share
==56806== 
Hello kiccleaf!/n
==56806== 
==56806== HEAP SUMMARY:
==56806==     in use at exit: 0 bytes in 0 blocks
==56806==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==56806== 
==56806== All heap blocks were freed -- no leaks are possible
==56806== 
==56806== For lists of detected and suppressed errors, rerun with: -s
==56806== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

第二个例子:只申请内存而不释放

#include <stdlib.h>
#include <stdio.h>
void func()
{
 //只申请内存而不释放
    void *p=malloc(sizeof(int));
}
int main()
{
    func();
    getchar();
    return 0;
}

编译程序1

gcc -o ./a.out ./main.cpp

使用valgrind命令来执行程序同时输出日志到文件

valgrind --log-file=valReport --leak-check=full --show-reachable=yes --leak-resolution=low ./a.out
  • –log-file=valReport 是指定生成分析日志文件到当前执行目录中,文件名为valReport
  • –leak-check=full 显示每个泄露的详细信息
  • –show-reachable=yes 是否检测控制范围之外的泄漏,比如全局指针、static指针等,显示所有的内存泄露类型
  • –leak-resolution=low 内存泄漏报告合并等级

最后执行输出的内容如下

报告解读,其中54017是指进程号,如果程序使用了多进程的方式来执行,那么就会显示多个进程的内容

第一段是valgrind的基本信息

==54017== Memcheck, a memory error detector
==54017== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==54017== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==54017== Command: ./a.out
==54017== Parent PID: 52130

第二段是对堆内存分配的总结信息,其中提到程序一共申请了1次内存,其中0次释放了,4 bytes被分配

==54017== HEAP SUMMARY:
==54017==     in use at exit: 4 bytes in 1 blocks
==54017==   total heap usage: 1 allocs, 0 frees, 4 bytes allocated

第三段的内容描述了内存泄露的具体信息,其中有一块内存占用4字节,在调用malloc分配,调用栈中可以看到是func函数最后调用了malloc,所以这一个信息是比较准确的定位了我们泄露的内存是在哪里申请的

==54017== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==54017==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==54017==    by 0x40057E: func() (in /home/oceanstar/CLionProjects/Share/src/a.out)
==54017==    by 0x40058D: main (in /home/oceanstar/CLionProjects/Share/src/a.out)

最后这一段是总结,4字节为一块的内存泄露

==54017== LEAK SUMMARY:
==54017==    definitely lost: 4 bytes in 1 blocks
==54017==    indirectly lost: 0 bytes in 0 blocks
==54017==      possibly lost: 0 bytes in 0 blocks
==54017==    still reachable: 0 bytes in 0 blocks
==54017==         suppressed: 0 bytes in 0 blocks

编译程序2

 gcc -g -o ./a.out ./main.cpp

运用valgrind检测内存泄漏:

==55430== Memcheck, a memory error detector
==55430== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==55430== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==55430== Command: ./a.out
==55430== 
^C==55430== 
==55430== Process terminating with default action of signal 2 (SIGINT)
==55430==    at 0x4F25F70: __read_nocancel (in /usr/lib64/libc-2.17.so)
==55430==    by 0x4EB2B13: _IO_file_underflow@@GLIBC_2.2.5 (in /usr/lib64/libc-2.17.so)
==55430==    by 0x4EB3CE1: _IO_default_uflow (in /usr/lib64/libc-2.17.so)
==55430==    by 0x4EAE6B9: getchar (in /usr/lib64/libc-2.17.so)
==55430==    by 0x400592: main (main.cpp:11)
==55430== 
==55430== HEAP SUMMARY:
==55430==     in use at exit: 4 bytes in 1 blocks
==55430==   total heap usage: 1 allocs, 0 frees, 4 bytes allocated
==55430== 
==55430== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==55430==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==55430==    by 0x40057E: func() (main.cpp:6)
==55430==    by 0x40058D: main (main.cpp:10)
==55430== 
==55430== LEAK SUMMARY:
==55430==    definitely lost: 4 bytes in 1 blocks
==55430==    indirectly lost: 0 bytes in 0 blocks
==55430==      possibly lost: 0 bytes in 0 blocks
==55430==    still reachable: 0 bytes in 0 blocks
==55430==         suppressed: 0 bytes in 0 blocks
==55430== 
==55430== For lists of detected and suppressed errors, rerun with: -s
==55430== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

可以看到,在head summary中,有该程序使用的总heap内存量,分配内存次数和释放内存次数,如果分配内存次数和释放内存次数不一致则说明有内存泄漏。

下面会有具体的分析:包括在main函数中和f函数中通过malloc申请了内存但是没有释放造成了多大的内存损失都标记有,–甚至行号都标记了,debug神器!

变形1:

#include <stdio.h>
#include <iostream>

int main()
{
    int *x;
    x = static_cast<int *>(malloc(8 * sizeof(int)));
    x = static_cast<int *>(malloc(8 * sizeof(int)));

    return 0;
}

报告是:

==59320== HEAP SUMMARY:
==59320==     in use at exit: 64 bytes in 2 blocks
==59320==   total heap usage: 2 allocs, 0 frees, 64 bytes allocated
==59320==        *********有几个没有释放就会有几个definitely lost*************
==59320== 32 bytes in 1 blocks are definitely lost in loss record 1 of 2
==59320==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==59320==    by 0x400691: main (main.cpp:7)
==59320== 
==59320== 32 bytes in 1 blocks are definitely lost in loss record 2 of 2
==59320==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==59320==    by 0x4006A2: main (main.cpp:8)
==59320== 
==59320== LEAK SUMMARY:
==59320==    definitely lost: 64 bytes in 2 blocks
==59320==    indirectly lost: 0 bytes in 0 blocks
==59320==      possibly lost: 0 bytes in 0 blocks
==59320==    still reachable: 0 bytes in 0 blocks
==59320==         suppressed: 0 bytes in 0 blocks
==59320== 
==59320== For lists of detected and suppressed errors, rerun with: -s
==59320== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

第3个例子: 使用未初始化的内存

#include <stdio.h>                                                              
int main()
{
    int x;
    if(x == 0)
    {
        printf("X is zero");
    }
    return 0;
}

  • Conditional jump or move depends on uninitialised value(s)
$ valgrind --tool=memcheck --leak-check=full ./Share 
==57052== Memcheck, a memory error detector
==57052== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==57052== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==57052== Command: ./Share
==57052== 
==57052== Conditional jump or move depends on uninitialised value(s)
==57052==    at 0x400549: main (main.cpp:5)
==57052== 
X is zero==57052== 
==57052== HEAP SUMMARY:
==57052==     in use at exit: 0 bytes in 0 blocks
==57052==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==57052== 
==57052== All heap blocks were freed -- no leaks are possible
==57052== 
==57052== Use --track-origins=yes to see where uninitialised values come from
==57052== For lists of detected and suppressed errors, rerun with: -s
==57052== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

第3个例子: 内存读写越界

#include <stdio.h>
#include <iostream>

int main()
{
    int len = 5;
    int *pt = (int*)malloc(len*sizeof(int)); //problem1: not freed
    int *p = pt;
    for (int i = 0; i < len; i++){
        p++;
    }
    *p = 5; //problem2: heap block overrun
    printf("%d\n", *p); //problem3: heap block overrun
   // free(pt);
    return 0;
}

problem1: 指针pt申请了空间,但是没有释放;
problem2: pt申请了5个int的空间,p经过4次循环(i=3时)已达到最后申请的p[4], 在i=4时p所指向的空间没有申请过; (下面valgrind报告中 Invalid write of size 4)
problem1: 同line8 (下面valgrind报告中 Invalid read of size 4 )

==58261== Memcheck, a memory error detector
==58261== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==58261== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==58261== Command: ./Share
==58261== 
==58261== Invalid write of size 4
==58261==    at 0x400707: main (main.cpp:12)
==58261==  Address 0x5a23054 is 0 bytes after a block of size 20 alloc'd
==58261==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==58261==    by 0x4006DC: main (main.cpp:7)
==58261== 
==58261== Invalid read of size 4
==58261==    at 0x400711: main (main.cpp:13)
==58261==  Address 0x5a23054 is 0 bytes after a block of size 20 alloc'd
==58261==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==58261==    by 0x4006DC: main (main.cpp:7)
==58261== 
5
==58261==   ****************这一段都是因为malloc但是没有free,如果free,就不会出现这个*******************
==58261== HEAP SUMMARY:
==58261==     in use at exit: 20 bytes in 1 blocks
==58261==   total heap usage: 1 allocs, 0 frees, 20 bytes allocated  
==58261== 
==58261== 20 bytes in 1 blocks are definitely lost in loss record 1 of 1
==58261==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==58261==    by 0x4006DC: main (main.cpp:7)
==58261== 
==58261== LEAK SUMMARY:
==58261==    definitely lost: 20 bytes in 1 blocks
==58261==    indirectly lost: 0 bytes in 0 blocks
==58261==      possibly lost: 0 bytes in 0 blocks
==58261==    still reachable: 0 bytes in 0 blocks
==58261==         suppressed: 0 bytes in 0 blocks
==58261== ****************************************************************
==58261== For lists of detected and suppressed errors, rerun with: -s
==58261== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)

 //--------------------如果没有出现堆内存泄漏,会打印-----------------
==58522== HEAP SUMMARY:
==58522==     in use at exit: 0 bytes in 0 blocks
==58522==   total heap usage: x allocs, xfrees, xxxx bytes allocated
==58522== 
==58522== All heap blocks were freed -- no leaks are possible

变形1:写越界

#include <stdio.h>
#include <iostream>

int main()
{
    int *x;
    x = static_cast<int *>(malloc(8 * sizeof(int)));
    x[9] = 0; //数组下标越界   Invalid write of size 4
    free(x);

    return 0;
}

变形2:读越界

#include <stdio.h>
#include <iostream>

int main()
{
    int *x;
    x = static_cast<int *>(malloc(8 * sizeof(int)));
    std::cout << x[9] ; //数组下标越界    Invalid read of size 4
    free(x);

    return 0;
}

第4个例子: 重复释放

#include <stdio.h>
#include <iostream>

int main()
{
    int *x;
    x = static_cast<int *>(malloc(8 * sizeof(int)));
    x = static_cast<int *>(malloc(8 * sizeof(int)));
    free(x);
    free(x);
    return 0;
}

报告:

==59602== Invalid free() / delete / delete[] / realloc()
==59602==    at 0x4C2B06D: free (vg_replace_malloc.c:540)
==59602==    by 0x4006FE: main (main.cpp:10)
==59602==  Address 0x5a230a0 is 0 bytes inside a block of size 32 free'd
==59602==    at 0x4C2B06D: free (vg_replace_malloc.c:540)
==59602==    by 0x4006F2: main (main.cpp:9)
==59602==  Block was alloc'd at
==59602==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==59602==    by 0x4006E2: main (main.cpp:8)
==59602== 
==59602== 
==59602== HEAP SUMMARY:
==59602==     in use at exit: 32 bytes in 1 blocks
==59602==   total heap usage: 2 allocs, 2 frees, 64 bytes allocated
==59602== 
==59602== 32 bytes in 1 blocks are definitely lost in loss record 1 of 1
==59602==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==59602==    by 0x4006D1: main (main.cpp:7)
==59602== 
==59602== LEAK SUMMARY:
==59602==    definitely lost: 32 bytes in 1 blocks
==59602==    indirectly lost: 0 bytes in 0 blocks
==59602==      possibly lost: 0 bytes in 0 blocks
==59602==    still reachable: 0 bytes in 0 blocks
==59602==         suppressed: 0 bytes in 0 blocks
==59602== 
==59602== For lists of detected and suppressed errors, rerun with: -s
==59602== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

第4个例子: malloc与delete释放问题

int main()
{
    char* str = (char*)malloc(5*sizeof(char));
    delete []str;
}

用malloc申请空间的指针用free释放;用new申请的空间用delete释放 (valgrind中Mismatched free() / delete / delete []);

==61950== Mismatched free() / delete / delete []
==61950==    at 0x4C2BB8F: operator delete[](void*) (vg_replace_malloc.c:651)
==61950==    by 0x4006E8: main (main.cpp:8)
==61950==  Address 0x5a23040 is 0 bytes inside a block of size 5 alloc'd
==61950==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==61950==    by 0x4006D1: main (main.cpp:7)
==61950== 
==61950== 
==61950== HEAP SUMMARY:
==61950==     in use at exit: 0 bytes in 0 blocks
==61950==   total heap usage: 1 allocs, 1 frees, 5 bytes allocated
==61950== 
==61950== All heap blocks were freed -- no leaks are possible

第5个例子:内存覆盖

int main()
{
    char str[11];
    for (int i = 0; i < 11; i++){
        str[i] = i;
    }
    memcpy(str + 1, str, 5);

    char x[5] = "abcd";
    strncpy(x + 2, x, 3);
}

问题出在memcpy上, 将str指针位置开始copy 5个char到str+1所指空间,会造成内存覆盖。strncpy也是同理。

==61609== Source and destination overlap in memcpy(0x1ffefffe31, 0x1ffefffe30, 5)
==61609==    at 0x4C2E81D: memcpy@@GLIBC_2.14 (vg_replace_strmem.c:1035)
==61609==    by 0x400721: main (main.cpp:11)
==61609== 
==61609== Source and destination overlap in strncpy(0x1ffefffe25, 0x1ffefffe23, 3)
==61609==    at 0x4C2D453: strncpy (vg_replace_strmem.c:552)
==61609==    by 0x400748: main (main.cpp:14)
==61609== 
==61609== 
==61609== HEAP SUMMARY:
==61609==     in use at exit: 0 bytes in 0 blocks
==61609==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==61609== 
==61609== All heap blocks were freed -- no leaks are possible

#!/bin/sh
# $Id$
#
if [ -z "$1" ]; then
    echo "Usage: `basename $0` prog"
    exit 1
fi
valgrind --tool=memcheck \
--leak-check=full \
--time-stamp=yes \
--show-reachable=yes \
--track-origins=yes \
--trace-children=no \
--leak-resolution=med \
--track-fds=yes \
--log-file=myvalmem.log \
"$@"
  • –track-origins=yes表示开启“使用未初始化的内存”的检测功能,并打开详细结果。如果没有这句话,默认也会做这方面的检测,但不会打印详细结果。

本文标签: 工具valgrind