168. valgrind

概述

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

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

下载

这里

基本选项

基本工具介绍

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

常用选项

  1. 适用于所有Valgrind工具

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    –tool=< name > 最常用的选项。运行 valgrind中名为toolname的工具。默认memcheck。
    -h --help 显示帮助信息。
    –version 显示valgrind内核的版本,每个工具都有各自的版本。
    -q --quiet 安静地运行,只打印错误信息。
    -v --verbose 更详细的信息, 增加错误数统计。
    –trace-children=no|yes 跟踪子线程? [no]
    –track-fds=no|yes 跟踪打开的文件描述?[no]
    –time-stamp=no|yes 增加时间戳到LOG信息? [no]
    –log-fd=< number > 输出LOG到描述符文件 [2=stderr]
    –log-file=< file > 将输出的信息写入到filename.PID的文件里,PID是运行程序的进行ID
    –log-file-exactly=< file > 输出LOG信息到 file
    –log-file-qualifier=< VAR > 取得环境变量的值来做为输出信息的文件名。 [none]
    –log-socket=ipaddr:port 输出LOG到socket ,ipaddr:port
  2. LOG信息输出

    1
    2
    3
    4
    5
    6
    –xml=yes 将信息以xml格式输出,只有memcheck可用
    –num-callers=< number > show < numbe r> callers in stack traces [12]
    –error-limit=no|yes 如果太多错误,则停止显示新错误? [yes]
    –error-exitcode=< number > 如果发现错误则返回错误代码 [0=disable]
    –db-attach=no|yes 当出现错误,valgrind会自动启动调试器gdb。[no]
    –db-command=< command > 启动调试器的命令行选项[gdb -nw %f %p]
  3. 适用于Memcheck工具的相关选项:

    1
    2
    3
    –leak-check=no|summary|full 要求对leak给出详细信息? [summary]
    –leak-resolution=low|med|high how much bt merging in leak check [low]
    –show-reachable=no|yes show reachable blocks in leak check? [no]

更详细的使用信息详见帮助文件、man手册或官网

注意

  1. valgrind不会自动的检查程序的每一行代码,只会检查运行到的代码分支,所以单元测试或功能测试用例很重要;
  2. 可以把valgrind看成是一个sandbox,通过valgrind运行的程序实际上是运行在valgrind的sandbox中的,所以,不要测试性能,会让你失望的,建议只做功能测试
  3. 编译代码时,建议增加 -g, -o0 选项,不要使用 -o1, -o2选项

常用选项示例

1
valgrind --tool=memcheck --log-file=log.txt --leak-check=full  ./test

说明:使用memcheck工具对test程序进行包含内存泄漏的检查,并将日志保存到log.txt

Memcheck 工具介绍

Memcheck是valgrind应用最广泛的工具,能够发现开发中绝大多数内存错误使用情况。此工具主要可检查以下错误

  • 使用未初始化的内存(Use of uninitialised memory)
  • 使用已经释放了的内存(Reading/writing memory after it has been free’d)
  • 使用超过malloc分配的内存空间(Reading/writing off the end of malloc’d blocks)
  • 对堆栈的非法访问(Reading/writing inappropriate areas on the stack)
  • 申请的空间是否有释放(Memory leaks – where pointers to malloc’d blocks are lost forever)
  • malloc/free/new/delete申请和释放内存的匹配(Mismatched use of malloc/new/new [] vs free/delete/delete [])
  • src和dst的重叠(Overlapping src and dst pointers in memcpy() and related functions)

注意:

  • 程序有时会申请很多常驻节点,这些未释放的节点不应视为问题;
  • 一般随着程序的运行,导致节点单向增加的malloc或new操作,视为内存泄漏

Memcheck 日志报告

基本格式

1
2
3
4
5
{问题描述}
at {地址、函数名、模块或代码行}
by {地址、函数名、代码行}
by …{逐层依次显示调用堆栈}
Address 0x??? {描述地址的相对关系}

memcheck包含的7类错误

  1. illegal read/illegal write errors 提示信息:[invalid read of size 4]

  2. use of uninitialised values 提示信息:[Conditional jump or move depends on uninitialised value]

  3. use of uninitialised or unaddressable values in system calls 提示信息:[syscall param write(buf) points to uninitilaised bytes]

  4. illegal frees 提示信息:[invalid free()]

  5. when a heap block is freed with an inappropriate deallocation function 提示信息:[Mismatched free()/delete/delete[]]

  6. overlapping source and destination blocks 提示信息:[source and destination overlap in memcpy(,)]

  7. memory leak detection

    ① still reachable 内存指针还在还有机会使用或释放,指针指向的动态内存还没有被释放就退出了

    ② definitely lost 确定的内存泄露,已经不能访问这块内存

    ③ indirectly lost 指向该内存的指针都位于内存泄露处

    ④ possibly lost 可能的内存泄露,仍然存在某个指针能够快速访问某块内存,但该指针指向的已经不是内存首位置

示例

示例1

  1. 当前程序(./test1_g)的进程号
  2. valgrind memcheck工具的license说明
  3. 加载程序的运行方式
  4. 父进程号,当前终端的进程
  5. 检测到的错误信息
  6. 堆栈摘要、小结,该例子中总共两次alloc、两次free,没有内存泄漏
  7. 检测到的错误数量,这里提示1个

示例2

  1. 异常读写报告
    1. 主线程异常读写
    2. 线程A异常读写报告
    3. 线程B异常读写报告
  2. 堆内存泄露报告
    1. 堆内存使用情况概述(HEAP SUMMARY)
    2. 确信的内存泄露报告(definitely lost)
    3. 可疑内存操作报告 (show-reachable=no关闭,打开:–show-reachable=yes)
    4. 泄露情况概述(LEAK SUMMARY)

memcheck 工具原理

Memcheck实现了一个仿真的CPU,被监控的程序被这个仿真CPU解释执行,该仿真CPU可以在所有的内存读写指令发生时,检测地址的合法性和读操作的合法性。

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

  1. Valid-Value 表:

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

  2. Valid-Address 表

    对于进程整个地址空间中的每一个字节(byte),还有与之对应的1 个bit,负责记录该地址是否能够被读写。

    检测原理:

    当要读写内存中某个字节时,首先检查这个字节对应的A bit。如果该A bit显示该位置是无效位置,memcheck 则报告读写错误。

    内核(core)类似于一个虚拟的CPU 环境,这样当内存中的某个字节被加载到真实的CPU 中时,该字节对应的V bit 也被加载到虚拟的

    CPU 环境中。一旦寄存器中的值,被用来产生内存地址,或者该值能够影响程序输出,则memcheck 会检查对应的V bits,如果该值

    尚未初始化,则会报告使用未初始化内存错误。

简单来说

  1. 如何知道那些地址是合法的(内存已分配)? 维护一张合法地址表(Valid-address (A) bits),当前所有可以合法读写(已分配)的地址在其中有对应的表项。该表通过以下措施维护:

    ① 全局数据(data, bss section)–在程序启动的时候标记为合法地址

    ② 局部变量–监控sp(stack pointer)的变化,动态维护

    ③动态分配的内存–截获 分配/释放 内存的调用:malloc, calloc, realloc, valloc, memalign, free, new, new[], delete and delete[]

    ④ 系统调用–截获mmap映射的地址

    ⑤ 其他–可以显示知会memcheck某地字段是合法的

  2. 如何知道某内存是否已经被赋值?

    ①维护一张合法值表(Valid-value (V) bits),指示对应的bit是否已经被赋值。因为虚拟CPU可以捕获所有对内存的写指令,所以这张表很容易维护。

reference