Valgrind Memcheck:内存错误检测器
要使用此工具,可在Valgrind命令行中指定--tool=memcheck。不过无需显式指定,因为Memcheck是默认工具。
概述
Memcheck是一款内存错误检测工具,能够检测C/C++程序中常见的以下问题:
- 非法内存访问:如堆块越界(上溢/下溢)、栈顶越界、释放后使用(UAF)等
- 未初始化值使用:使用未初始化或源自未初始化值的变量
- 堆内存释放错误:如重复释放、malloc/new/new[]与free/delete/delete[]不匹配
(注:对于带大小和对齐要求的分配/释放函数,若释放值与分配值不匹配也会报告) - memcpy等函数中src和dst指针重叠
- 向内存分配函数的size参数传递可疑值(可能为负数)
- 使用realloc时size参数为0
- 使用非2的幂次的alignment值
- 内存泄漏
这些问题通常难以通过其他手段发现,可能长期潜伏,最终导致偶发且难以诊断的崩溃。
Memcheck还提供执行树内存分析功能,通过命令行选项--xtree-memory和监控命令xtmemory实现。
Memcheck错误消息解释
Memcheck会输出多种错误消息,本节简要说明各类错误含义。详细的检查机制原理参见Memcheck检查机制详解。
非法读/写错误
示例:
Invalid read of size 4
at 0x40F6BBCC: (within /usr/lib/libpng.so.2.1.0.9)
by 0x40F6B804: (within /usr/lib/libpng.so.2.1.0.9)
by 0x40B07FF4: read_png_image(QImageIO *) (kernel/qpngio.cpp:326)
by 0x40AC751B: QImageIO::read() (kernel/qimage.cpp:3621)
Address 0xBFFFF0E0 is not stack'd, malloc'd or free'd
当程序访问Memcheck认为不应访问的内存时触发。本例中,程序在libpng库内执行4字节读取,地址0xBFFFF0E0既非栈内存,也非堆内存。Memcheck会尝试关联非法地址与已释放块或堆块边界,若使用--read-var-info选项可获得更详细地址描述(但会降低运行速度)。
未初始化值使用
示例:
Conditional jump or move depends on uninitialised value(s)
at 0x402DFA94: _IO_vfprintf (_itoa.h:49)
by 0x402E8476: _IO_printf (printf.c:36)
by 0x8048472: main (tests/manuel1.c:8)
当程序使用未初始化值(如局部变量或堆块未写入内容)时触发。Memcheck仅在未初始化值可能影响程序外部行为时报告(如条件判断、系统调用参数等)。使用--track-origins=yes可追踪未初始化值来源,但会显著降低性能。
系统调用中的未初始化/不可寻址值
Memcheck会检查系统调用的所有参数:
- 直接参数是否已初始化
- 读写缓冲区是否可寻址且内容已初始化
示例程序:
#include <stdlib.h>
#include <unistd.h>
int main(void) {
char* arr = malloc(10);
int* arr2 = malloc(sizeof(int));
write(1, arr, 10); // 错误:写入未初始化的堆数据
exit(arr2[0]); // 错误:传递未初始化值给exit
}
非法释放
示例:
Invalid free()
at 0x4004FFDF: free (vg_clientmalloc.c:577)
by 0x80484C7: main (tests/doublefree.c:10)
Address 0x3807F7B4 is 0 bytes inside a block of size 177 free'd
at 0x4004FFDF: free (vg_clientmalloc.c:577)
by 0x80484C7: main (tests/doublefree.c:10)
Memcheck跟踪所有malloc/new分配的块,可检测重复释放或释放非堆起始地址。
不匹配的释放函数
示例:
Mismatched free() / delete / delete []
at 0x40043249: free (vg_clientfuncs.c:171)
by 0x4102BB4E: QGArray::~QGArray(void) (tools/qgarray.cpp:149)
...
C++中必须使用与分配方式匹配的释放函数:
- malloc/calloc/realloc → free
- new → delete
- new[] → delete[]
内存块重叠
memcpy等函数要求src和dst指针指向的内存块不能重叠,否则行为未定义。Memcheck会检测此类情况。
可疑参数值
内存分配函数的size参数应为非负值且合理。过大的size值(如2^63以上)可能是计算错误导致的负数,称为"可疑值"。Memcheck会检查以下函数的size参数:malloc、calloc、realloc、memalign、posix_memalign、aligned_alloc、new、new[]等。
realloc size为0
C17标准规定realloc的size为0时行为由实现定义,Memcheck会警告这种非可移植用法。
对齐错误
Memcheck会检查memalign、posix_memalign、aligned_alloc等函数的对齐参数是否为2的幂次,size是否为对齐的整数倍等。
内存泄漏检测
Memcheck跟踪所有堆块,程序退出时检查未释放的块。泄漏类型分为:
- Definitely lost:无指针可达的块
- Indirectly lost:因直接泄漏导致无法释放的块
- Possibly lost:通过内部指针可达的块
- Still reachable:通过有效指针可达的块(默认不报告)
使用--leak-check=full可查看详细泄漏信息。
Memcheck命令行选项
主要选项包括:
--leak-check=<no|summary|yes|full>:泄漏检查级别--leak-resolution=<low|med|high>:合并泄漏报告的严格度--show-leak-kinds=<set>:指定显示的泄漏类型--errors-for-leak-kinds=<set>:指定计为错误的泄漏类型--track-origins=<yes|no>:追踪未初始化值来源--freelist-vol=<number>:释放块队列大小(默认20MB)--freelist-big-blocks=<number>:优先回收的大块大小阈值--workaround-gcc296-bugs=<yes|no>:忽略GCC 2.96栈指针下方访问--ignore-range-below-sp=<number>-<number>:指定忽略的栈下偏移范围--show-mismatched-frees=<yes|no>:检查分配/释放函数匹配--show-realloc-size-zero=<yes|no>:检查realloc size=0用法--ignore-ranges=<range1>,<range2>...:忽略指定地址范围--malloc-fill=<hexnumber>:填充malloc分配的内存--free-fill=<hexnumber>:填充释放的内存--partial-loads-ok=<yes|no>:处理部分有效地址的加载--expensive-definedness-checks=<no|auto|yes>:精确检查定义性--keep-stacktraces=alloc|free|alloc-and-free|alloc-then-free|none:保留分配/释放栈跟踪--undef-value-errors=<yes|no>:报告未初始化值错误--xtree-memory=<no|yes>:生成执行树格式内存报告--xtree-memory-file=<filename>:指定执行树输出文件
抑制文件编写
抑制文件用于屏蔽已知误报,基本格式:
{
<error_type>
<调用栈信息>
...
}
支持的错误类型包括:
Value1/Value2/Value4/Value8/Value16:未初始化值使用Cond:未初始化CPU条件码使用Addr1/Addr2/Addr4/Addr8/Addr16:非法地址访问Jump:跳转到不可寻址位置Param:无效系统调用参数Free:无效或错误释放Overlap:memcpy等函数重叠Leak:内存泄漏
Leak类型可添加match-leak-kinds: definite,indirect,possible指定匹配的泄漏类型。
Memcheck检查机制详解
有效值位(V bits)
每个内存字节有8个V bits表示其有效性。CPU寄存器也有对应的V bits。仅当未初始化值影响程序行为时才会报告错误。
有效地址位(A bits)
每个内存字节有1个A bit表示其可访问性。非法访问会触发错误。
整体机制
Memcheck通过V bits和A bits的组合实现内存检查,拦截内存分配/释放函数,并在关键点检查值的有效性。
Memcheck监控命令
通过Valgrind内置的gdbserver提供以下监控命令:
xb <addr> [<len>]:显示内存定义位和值get_vbits <addr> [<len>]:显示定义位make_memory <status> <addr> [<len>]:修改内存状态check_memory <status> <addr> [<len>]:检查内存状态leak_check:执行泄漏检查block_list:显示泄漏块的详细信息who_points_at:查找指向某地址的指针xtmemory:生成执行树格式的内存报告
客户端请求
提供以下API供程序显式控制Memcheck行为:
VALGRIND_MAKE_MEM_NOACCESS/UNDEFINED/DEFINED:修改内存状态VALGRIND_CHECK_MEM_IS_ADDRESSABLE/DEFINED:检查内存属性VALGRIND_DO_LEAK_CHECK:立即执行泄漏检查VALGRIND_COUNT_LEAKS:获取泄漏统计VALGRIND_GET_VBITS/SET_VBITS:获取/设置定义位VALGRIND_CREATE_BLOCK/DISCARD:命名内存区域
内存池支持
通过客户端请求描述自定义分配器:
VALGRIND_CREATE_MEMPOOL:创建内存池VALGRIND_MEMPOOL_ALLOC:分配块VALGRIND_MEMPOOL_FREE:释放块VALGRIND_DESTROY_MEMPOOL:销毁内存池
MPI并行程序调试
Memcheck通过libmpiwrap库支持MPI程序调试:
- 拦截MPI调用并检查缓冲区有效性
- 支持MPI-1.1和MPI-2函数
- 通过
MPIWRAP_DEBUG环境变量控制行为
构建和安装
配置时自动构建,需使用与目标MPI程序相同的mpicc。安装后生成libmpiwrap-<platform>.so。
使用方法
通过LD_PRELOAD加载包装器库运行MPI程序:
LD_PRELOAD=$prefix/lib/valgrind/libmpiwrap-<platform>.so \
mpirun [args] $prefix/bin/valgrind ./program
控制选项
通过MPIWRAP_DEBUG环境变量控制:
verbose:显示详细调用信息quiet:仅报告错误warn:警告未包装函数strict:严格模式(未包装函数报错)
支持的函数
包装所有MPI-2函数(除MPI_Wtick/MPI_Wtime/MPI_Pcontrol外),包括:
- 点对点通信:
PMPI_Send/Recv/Isend/Irecv等 - 集合通信:
PMPI_Bcast/Reduce/Allreduce等 - 数据类型操作:
PMPI_Type_commit/Type_free等 - 其他:
PMPI_Init/Finalize/Comm_create等
类型处理
支持MPI-1.1结构化数据类型,包括:
MPI_COMBINER_NAMED/CONTIGUOUS/VECTOR等- 对
MPI_FLOAT_INT等内置类型按struct { float val; int loc; }处理
扩展包装器
通过修改mpi/libmpiwrap.c添加新包装器:
- 非阻塞接收需维护请求表
- 使用POSIX互斥锁保证线程安全
预期效果
减少MPI程序的误报,但可能仍报告MPI实现内部的错误。建议通过抑制文件处理这些误报。

浙公网安备 33010602011771号