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实现内部的错误。建议通过抑制文件处理这些误报。

posted @ 2025-07-05 15:07  LoftyAmbition  阅读(177)  评论(0)    收藏  举报