168. valgrind
概述
Valgrind是一套Linux下,开放源代码(GPL V2)的仿真调试工具的集合。
Valgrind由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(framework),它模拟了一个CPU环境,并提供服务给其他工具;而其他工具则类似于插件 (plug-in),利用内核提供的服务完成各种特定的内存调试任务。
下载
见这里
基本选项
基本工具介绍
选项 | 功能 |
---|---|
Memcheck | 这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等 |
Callgrind | 它主要用来检查程序中函数调用过程中出现的问题 |
Cachegrind | 它主要用来检查程序中缓存使用出现的问题 |
Helgrind | 它主要用来检查多线程程序中出现的竞争问题 |
Massif | 它主要用来检查程序中堆栈使用中出现的问题 |
Extension | 可以利用core提供的功能,自己编写特定的内存调试工具 |
常用选项
适用于所有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:portLOG信息输出
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]适用于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手册或官网:
注意
- valgrind不会自动的检查程序的每一行代码,只会检查运行到的代码分支,所以单元测试或功能测试用例很重要;
- 可以把valgrind看成是一个sandbox,通过valgrind运行的程序实际上是运行在valgrind的sandbox中的,所以,不要测试性能,会让你失望的,建议只做功能测试
- 编译代码时,建议增加
-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 | {问题描述} |
memcheck包含的7类错误
illegal read/illegal write errors 提示信息:[invalid read of size 4]
use of uninitialised values 提示信息:[Conditional jump or move depends on uninitialised value]
use of uninitialised or unaddressable values in system calls 提示信息:[syscall param write(buf) points to uninitilaised bytes]
illegal frees 提示信息:[invalid free()]
when a heap block is freed with an inappropriate deallocation function 提示信息:[Mismatched free()/delete/delete[]]
overlapping source and destination blocks 提示信息:[source and destination overlap in memcpy(,)]
memory leak detection
① still reachable 内存指针还在还有机会使用或释放,指针指向的动态内存还没有被释放就退出了
② definitely lost 确定的内存泄露,已经不能访问这块内存
③ indirectly lost 指向该内存的指针都位于内存泄露处
④ possibly lost 可能的内存泄露,仍然存在某个指针能够快速访问某块内存,但该指针指向的已经不是内存首位置
示例
示例1

- 当前程序(./test1_g)的进程号
- valgrind memcheck工具的license说明
- 加载程序的运行方式
- 父进程号,当前终端的进程
- 检测到的错误信息
- 堆栈摘要、小结,该例子中总共两次alloc、两次free,没有内存泄漏
- 检测到的错误数量,这里提示1个
示例2
- 异常读写报告
- 主线程异常读写
- 线程A异常读写报告
- 线程B异常读写报告
- 堆内存泄露报告
- 堆内存使用情况概述(HEAP SUMMARY)
- 确信的内存泄露报告(definitely lost)
- 可疑内存操作报告 (show-reachable=no关闭,打开:–show-reachable=yes)
- 泄露情况概述(LEAK SUMMARY)
memcheck 工具原理
Memcheck实现了一个仿真的CPU,被监控的程序被这个仿真CPU解释执行,该仿真CPU可以在所有的内存读写指令发生时,检测地址的合法性和读操作的合法性。

Memcheck 能够检测出内存问题,关键在于其建立了两个全局表。
Valid-Value 表:
对于进程的整个地址空间中的每一个字节(byte),都有与之对应的8 个bits;对于CPU 的每个寄存器,也有一个与之对应的bit 向量。这些bits 负责记录该字节或者寄存器值是否具有有效的、已初始化的值。
Valid-Address 表
对于进程整个地址空间中的每一个字节(byte),还有与之对应的1 个bit,负责记录该地址是否能够被读写。
检测原理:
当要读写内存中某个字节时,首先检查这个字节对应的A bit。如果该A bit显示该位置是无效位置,memcheck 则报告读写错误。
内核(core)类似于一个虚拟的CPU 环境,这样当内存中的某个字节被加载到真实的CPU 中时,该字节对应的V bit 也被加载到虚拟的
CPU 环境中。一旦寄存器中的值,被用来产生内存地址,或者该值能够影响程序输出,则memcheck 会检查对应的V bits,如果该值
尚未初始化,则会报告使用未初始化内存错误。
简单来说
如何知道那些地址是合法的(内存已分配)? 维护一张合法地址表(Valid-address (A) bits),当前所有可以合法读写(已分配)的地址在其中有对应的表项。该表通过以下措施维护:
① 全局数据(data, bss section)–在程序启动的时候标记为合法地址
② 局部变量–监控sp(stack pointer)的变化,动态维护
③动态分配的内存–截获 分配/释放 内存的调用:malloc, calloc, realloc, valloc, memalign, free, new, new[], delete and delete[]
④ 系统调用–截获mmap映射的地址
⑤ 其他–可以显示知会memcheck某地字段是合法的
如何知道某内存是否已经被赋值?
①维护一张合法值表(Valid-value (V) bits),指示对应的bit是否已经被赋值。因为虚拟CPU可以捕获所有对内存的写指令,所以这张表很容易维护。