valgrind使用方法

发布时间 2023-04-29 15:40:08作者: Alpha205

valgrind使用

1. Preface

 valgrind是一套Linux下开源的程序仿真调试和分析工具的集合;集合中的每个工具负责执行某种类型的仿真,调试,或者分析任务;它的主要结构包括一个内核(软件模拟CPU环境)以及一系列的小工具。

valgrind包含的工具主要如下:

  • Memcheck

    主要针对C和C++程序的内存管理和分配错误;Memcheck会检测运行程序对内存所有的读写操作,包括(new/delete 和 malloc/free),Memcheck会对一下错误进行检测:
    (1) 访问非法的内存(未分配,已释放,堆边界以外的区域,栈不可访问的区域)
    (2) 以不安全的方式使用未初始化的值
    (3) 内存泄漏
    (4) 对内存释放异常(多次释放)
    (5) 向memcpy()函数传递的src和dst内存有重叠
    对于上述相关的错误,Memcheck会给出在源代码中的出错位置以及对应的调用栈信息

  • Cachegrind

    Cache分析器,它能够模拟CPU中的一级缓存L1,D1和二级缓存,且能够精确指出程序中cache的命中和丢失,此外还可以给出cache丢失次数;Cachegrind还可以给出每行代码,每个函数,每个模块,和整个程序的内存引用次数以及指令数;有利于优化程序;

  • Callgrind

    Callgrind相当于Cachegrind的一个扩展,它除了能够给出Cachegrind提供的所有信息之外,还可以给出程序的调用图;此外它还可作为一个独立的工具进行使用,可用于可视化的展示Callgrind收集到的数据;也可用于可视化的展示Cachegrind的输出信息;

  • Massif

    堆分析器,Massif通过程序的堆内存快照技术,实现堆内存的分析;Massif会生成一张表示程序运行过程中堆内存使用情况的图,包括在运行过程中哪个模块占用的堆内存最多等信息;生成的图以文本文件或者html文件呈现,

  • Helgrind

    Helgrind是线程调试器,用于检测多线程程序中出现的数据竞争问题,Helgrind会去查找被多个线程访问的内存区域,且检测这些内存区域在使用时是否进行了线程同步,如果没有,则这些内存区域会是潜在的隐患,可能会造成一些非常棘手的问题。

  • DRD

    功能与Helgrind类似,但是占用内存更少;

  • SGcheck

    用于检测栈和全局数组溢出

2. valgrind安装

源码下载地址:Valgrind官方网站

wget http://www.valgrind.org/downloads/valgrind-3.14.0.tar.bz2

2.1 解压安装包

tar -xvf valgrind-3.20.0.tar.bz2

2.2 运行autogen.sh
cd valgrind-3.20.0

./autogen.sh

如果提示缺少相应的autotools系列工具,则执行:

sudo apt get install autoconf

autotools系列工具包括如下子工具:

  • aclocal
  • autoscan
  • autoconf
  • autoheader
  • automake
2.3 运行configure脚本

./configure

2.4 编译安装
make 

sudo make install
3. valgrind使用

使用格式:

valgrind [options] prog-and-args

options选项:

  • -tool=<toolname> 选择valgrind工具集中的工具,默认为memcheck
  • -v/--version 显示版本信息
  • -h/--help 显示帮助信息
  • -q/--quiet 安静模式,只打印错误信息
  • -v/--verbose 打印详细信息
  • --trace-children=no|yes 是否跟踪子进程,默认值no
  • --trace-fds=no|yes 是否跟踪打开的文件描述符,默认值no
  • --time-stamp=no|yes 是否在打印的信息前面加上时间戳,默认值no

关于输出的选线:

  • --log-fd=<num> 输出Log信息到指定的文件描述符(0,1,2 /stdin,stdout,stderr)
  • --log-file=<file> 将Log信息输出到指定文件
  • --log-socket=ipAddr:Port 将Log输出到指定的socket
4. Memcheck的使用
  • --leak-check=no|summary|full 在退出时是否查找内存泄漏

    no表示不检查 summary表示输出概括性的信息 full表示输出详细信息

  • --leak-resolution=<low|med|high>

    表示是否将内存检查的结果进行合并展示,只会影响展示,不影响内存检查的结果(因为大概是这个意思)

  • --show-leak-kinds=<set>

    指定显示内存泄漏的种类 可选值:definite,indirect,possible,reachable,all

其他的参数可查阅说明书:https://valgrind.org/docs/manual/mc-manual.html#mc-manual.overview

Memcheck可检测的错误包括:

1.非法的读写错误

在程序读写Memcheck认为不合法的地址时,会输出类似的错误,例如访问已经释放的内存,以及非法的栈地址;

测试代码:

#include <stdlib.h>

int main(int argc, char* argv[])
{
    int* p = (int*)malloc(sizeof(int));

    *p = 1;

    *(p+1) = 2;      // 非法地址,未经过分配

    free(p);

    return 0;
}

Memcheck Log输出信息:

img

输出信息中需要注意如果ERROE SUMMARY: 后的错误数量不为0,则仔细查看输出的日志信息;

2.使用未初始化的值

测试代码:

#include <stdio.h>

int main(int argc, char* argv[])
{
    int x;
    
    printf("The value is %d\n", x);

    return 0;
}

Memcheck Log输出信息:

如果不想Memcheck检测此类错误,可以添加参数设置:

--undef-value-errors=no

3.未初始化或者在系统调用中无法寻址的变量

Memcheck会检测所有系统调用涉及到的参数,包括如下:

  • 如果系统调用需要从程序提供的缓冲区中读取数据,那么memcheck会检测缓冲区的地址是否有效以及缓冲区中的数据是否进行了初始化
  • 如果系统调用需要从程序提供的缓冲区中写入数据,则memcheck会检测缓冲区地址的有效性

测试代码:

#include <stdlib.h>
#include <unistd.h>

void test()
{
   char* arr  = (char*)malloc(10);

   int*  arr2 = (int*)malloc(sizeof(int));

   write( 1 /* stdout */, arr, 10 );

   exit(arr2[0]);   // arr2[0]无法进行寻址
}

int main(void)
{
   test();
}

Memcheck输出信息:

img

4. 非法的内存释放

Memcheck会对程序中使用new/malloc分配的内存进行持续追踪,因此它能够检测出传递给free/delete的内存地址是否是合法的;例如对同一块动态分配的内存执行两次释放;则第二次释放,free/delete的参数(地址)就是不合法的

测试程序:

#include <stdlib.h>
#include <unistd.h>

int main(void)
{
   char* arr  = (char*)malloc(10);

   free(arr);

   free(arr);      // 重复释放内存

   return 0;
}

img

5. 堆内存释放和分配函数不匹配

在c++中,内存的分配与释放函数必须匹配:

  • 使用malloc, calloc, realloc, valloc, memalign分配的内存,必须使用free进行释放
  • 使用new分配的内存必须使用delete进行释放
  • 使用new []分配的内存必须使用delete []进行释放
#include <stdlib.h>
#include <unistd.h>
#include <malloc.h>

int main(void)
{
    char* arr  = (char*)malloc(10);

    delete arr;

    return 0;
}

img

备注:在Linux平台上混用new和free并不会导致出错,但是在其他的平台上却不一定,可能会导致程序crash;

The reason behind the requirement is as follows. In some C++ implementations, delete[] must be used for objects allocated by new[] because the compiler stores the size of the array and the pointer-to-member to the destructor of the array's content just before the pointer actually returned. delete doesn't account for this and will get confused, possibly corrupting the heap. <from: https://valgrind.org/docs/manual/mc-manual.html#mc-manual.bad-syscall-args>

6. 源地址和目标地址的出现内存覆盖(overlapping)

c/c++语言可直接对内存进行操作,提供了许多的内存拷贝函数,memcpy, strcpy, strncpy, strcat, strncat,POSIX标准规定,如果对象之间的内存拷贝出现相互覆盖,则将导致最后的结果是不确定的;