Memcheck:一个内存错误检测器

目录

4.1。概观 4.2。来自Memcheck的错误消息说明
4.2.1。非法读取/非法写入错误 4.2.2。使用未初始化的值 4.2.3。在系统调用中使用未初始化或不可寻址的值 4.2.4。非法释放 4.2.5。当一个堆块被释放不正当的解除分配功能 4.2.6。重叠的源和目标块 4.2.7。有趣的参数值 4.2.8。内存泄漏检测
4.3。Memcheck命令行选项 4.4。编写抑制文件 4.5。Memcheck检查机械的详细信息
4.5.1。有效值(V)位 4.5.2。有效地址(A)位 4.5.3。把它们放在一起
4.6。Memcheck监视器命令 4.7。客户端请求 4.8。内存池:描述和使用自定义分配器 4.9。使用Valgrind调试MPI并行程序
4.9.1。建造和安装包装纸 4.9.2。入门 4.9.3。控制包装库 4.9.4。功能 4.9.5。类型 4.9.6。写新的包装纸 4.9.7。使用包装器时期待什么?

要使用此工具,可以--tool=memcheck 在Valgrind命令行中指定。不过,由于Memcheck是默认工具,所以您不需要。

4.1。概观

Memcheck是一个内存错误检测器。它可以检测C和C ++程序中常见的以下问题。

  • 访问内存您不应该,例如超越和低估堆块,超出堆栈的顶部,以及在释放后访问内存。

  • 使用未定义的值,即尚未初始化的值,或已从其他未定义值导出的值。

  • 不正确的释放堆内存,比如双重释放堆块,或使用 mallocnewnew[] 对 freedelete/delete[]

  • 重叠src和 dst指针 memcpy以及相关功能。

  • 将一个腥味(大概为负)值传递给 size一个内存分配功能的参数。

  • 内存泄漏。

像这样的问题可能难以通过其他方式找到,经常长时间不被发现,然后导致偶尔的,难以诊断的崩溃。

4.2。来自Memcheck的错误消息说明

Memcheck发出一系列错误消息。本节将简要介绍哪些错误消息的意思。错误检查机械的精确行为在Memcheck检查机械的详细信息中有所描述。

4.2.1。非法读取/非法写入错误

例如:

大小4读取无效
   在0x40F6BBCC :(在/usr/lib/libpng.so.2.1.0.9中)
   由0x40F6B804 :(在/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)
 地址0xBFFFF0E0不是stack'd,malloc'd或free'd

当您的程序在Memcheck认为不应该读取或写入内存时,会发生这种情况。在这个例子中,程序做了4个字节的地址处读0xBFFFF0E0,内系统提供的库libpng.so.2.1.0.9,将其从其他地方以相同的库调用某处,从326行调用qpngio.cpp,等等上。

Memcheck尝试确定非法地址可能涉及什么,因为这通常是有用的。所以,如果它指向一个已经被释放的内存块,那么你会被通知这个,而且块被释放。同样,如果它应该是刚刚结束堆块,这是数组下标中一个一个错误的常见结果,你会被告知这个事实,以及块分配的地方。如果您使用--read-var-info选项Memcheck将运行更慢,但可能会更详细地描述任何非法地址。

在这个例子中,Memcheck不能识别地址。实际上地址在堆栈上,但是由于某种原因,这不是有效的堆栈地址 - 它不在堆栈指针下面,这是不允许的。在这种特殊情况下,这可能是由GCC产生的无效代码造成的,这是古代GCC的一个已知bug。

请注意,Memcheck仅告诉您,您的程序即将访问非法地址的内存。它不能阻止访问发生。所以,如果您的程序进行通常会导致分段错误的访问,您的程序仍然会遇到相同的命运 - 但​​是在此之前您将收到来自Memcheck的消息。在这个特定的例子中,在堆栈上读取垃圾是非致命的,并且程序保持活着。

4.2.2。使用未初始化的值

例如:

有条件的跳跃或移动取决于未初始化的值
   在0x402DFA94:_IO_vfprintf(_itoa.h:49)
   by 0x402E8476:_IO_printf(printf.c:36)
   通过0x8048472:main(tests / manuel1.c:8)

当程序使用尚未初始化的值(换句话说,未定义)时,将报告未初始化值使用错误。这里,未定义的值用于printfC库机械的 某处。运行以下小程序时报告此错误:

int main()
{
  int x;
  printf(“x =%d \ n”,x);
}

重要的是要了解,您的程序可以尽可能多地复制垃圾(未初始化)数据。Memcheck观察这个并且跟踪数据,但不会抱怨。只有当您的程序尝试以可能影响程序外部可见行为的方式使用未初始化的数据时,才会发出投诉。在此示例中,x未初始化。Memcheck观察到传递给的值_IO_printf,然后发送给_IO_vfprintf,但不发表任何评论。但是, _IO_vfprintf必须检查它的值, x所以它可以把它变成相应的ASCII字符串,而在这一点Memcheck抱怨。

未初始化数据的来源往往是:

  • 程序中的局部变量尚未初始化,如上例所示。

  • 在你(或构造函数)之前的堆块(分配给 mallocnew或类似的函数)的内容写在那里。

要查看程序中未初始化数据源的信息,请使用该--track-origins=yes选项。这使得Memcheck运行速度更慢,但可以更容易地追踪未初始化值错误的根本原因。

4.2.3。在系统调用中使用未初始化或不可寻址的值

Memcheck检查系统调用的所有参数:

  • 它会自动检查所有直接参数,无论它们是初始化的。

  • 此外,如果系统调用需要从程序提供的缓冲区中读取,Memcheck会检查整个缓冲区是否可寻址,并将其内容初始化。

  • 此外,如果系统调用需要写入用户提供的缓冲区,Memcheck会检查缓冲区是否可寻址。

系统调用后,Memcheck更新其跟踪信息,以精确反映系统调用引起的内存状态变化。

以下是两个无效参数的系统调用示例:

  #include 
  #include 
  int main(void)
  {
    char * arr = malloc(10);
    int * arr2 = malloc(sizeof(int));
    写(1 / * stdout * /,arr,10);
    出口(ARR2 [0]);
  }

你得到这些投诉...

  Syscall param write(buf)指向未初始化的字节
     在0x25A48723:__write_nocancel(在/lib/tls/libc-2.3.3.so)
     by 0x259AFAD3:__libc_start_main(in /lib/tls/libc-2.3.3.so)
     由0x8048348 :(在/auto/homes/njn25/grind/head4/a.out中)
   地址0x25AB8028是大小为10的块内的0个字节
     在0x259852B0:malloc(vg_replace_malloc.c:130)
     由0x80483F1:主(ac:5)

  Syscall param exit(error_code)包含未初始化的字节
     在0x25A21B44:__GI__exit(在/lib/tls/libc-2.3.3.so)
     通过0x8048426:main(ac:8)

因为程序具有(a)从堆块将未初始化的垃圾写入标准输出,并且(b)将未初始化的值传递给exit。请注意,第一个错误是指由buf(不是 buf本身)指向的内存 ,但是第二个错误直接指向了exit参数 arr2[0]

4.2.4。非法释放

例如:

无效()
   在0x4004FFDF:free(vg_clientmalloc.c:577)
   通过0x80484C7:main(tests / doublefree.c:10)
 地址0x3807F7B4是一个大小为177的空闲块内的0个字节
   在0x4004FFDF:free(vg_clientmalloc.c:577)
   通过0x80484C7:main(tests / doublefree.c:10)

Memcheck使用malloc/ 跟踪程序分配的块new,所以它可以准确地知道free/ / 的参数 delete是否合法。这里,这个测试程序已经释放了同一个程序段两次。与非法读/写错误一样,Memcheck尝试了解地址的释放。如果在这里,地址是先前被释放的地址,那么你会被告知 - 同一块的重复释放容易发现。如果您尝试释放不指向堆块开始的指针,您还将收到此消息。

4.2.5。当一个堆块被释放不正当的解除分配功能

在以下示例中,分配的块 new[]错误地被释放 free

free()/ delete / delete []
   在0x40043249:free(vg_clientfuncs.c:171)
   by 0x4102BB4E:QGArray ::〜QGArray(void)(tools / qgarray.cpp:149)
   由0x4C261C41:PptDoc ::〜PptDoc(void)(include / qmemarray.h:60)
   通过0x4C261F0E:PptXml ::〜PptXml(void)(pptxml.cc:44)
 地址0x4BB292A8是一个大小为64个alloc'd的块内的0个字节
   在0x4004318C:operator new [](unsigned int)(vg_clientfuncs.c:152)
   by 0x4C21BC15:KLaola :: readSBStream(int)const(klaola.cc:314)
   by 0x4C21C155:KLaola :: stream(KLaola :: OLENode const *)(klaola.cc:416)
   通过0x4C21788F:OLEFilter :: convert(QCString const&)(olefilter.cc:272)

C++它与它是如何分配兼容的方式来释放内存是很重要的。交易是:

  • 如果与分配 malloc, calloc, realloc, valloc或者 memalign,您必须取消分配free

  • 如果分配new,你必须解除配置delete

  • 如果分配new[],你必须解除配置delete[]

最糟糕的是,在Linux上显然无论如何混合使用,但同样的程序可能会在不同的平台(例如Solaris)上崩溃。所以最好是妥善解决它。根据KDE的人“令人惊奇的是有多少C ++程序员不知道这个”。

要求背后的原因如下。在某些C ++实现中,delete[]必须使用分配的对象,new[]因为在指针实际返回之前,编译器会将数组的大小和指针成员存储到数组的内容的析构函数中。 delete不解决这个问题,会混淆,可能会破坏堆。

4.2.6。重叠的源和目标块

下面的C库函数从一个存储器块复制一些数据到另一个(或类似的东西): , memcpy, strcpy, strncpy, 。strcat strncat由他们src和 dst指针指向的块不允许重叠。POSIX标准具有“如果在重叠的对象之间进行复制,行为未定义”。因此,Memcheck检查这一点。

例如:

== 27492 == memcpy中的源和目的地重叠(0xbffff294,0xbffff280,21)
== 27492 == 0x40026CDC:memcpy(mc_replace_strmem.c:71)
== 27492 == by 0x804865A:main(overlap.c:40)

您不希望两个块重叠,因为其中一个块可能被复制部分覆盖。

你可能会认为,Memcheck在dst小于等于 的情况下被过度迂回地报告src。例如,明显的实现方式memcpy是从第一个字节到最后一个字节的复制。但是,某些架构的优化指南建议从最后一个字节复制到第一个。而且,复制前的一些实现memcpy为零 dst,因为归零目的地的高速缓存行可以提高性能。

故事的道德是:如果你想写真正的便携式代码,不要对语言实现做任何假设。

4.2.7。有趣的参数值

所有内存分配函数都会使用一个参数来指定应该分配的内存块的大小。显然,请求的大小应该是非负值,通常不会过大。例如,在64位机器上,分配请求的大小超过2 ** 63个字节是非常不起眼的。更可能的是,这样的值是错误大小计算的结果,并且实际上是负值(恰好恰好出现过大,因为位模式被解释为无符号整数)。这样的值被称为“腥值”。所述size的分配如下功能参数被检查为腥: malloc, calloc, realloc, memalign, new, new []。 __builtin_new, __builtin_vec_newcalloc

例如:

== 32233 ==函数malloc的参数'size'有一个腥(可能为负)的值:-3
== 32233 ==在0x4C2CFA7:malloc(vg_replace_malloc.c:298)
== 32233 == 0x400555:foo(fishy.c:15)
== 32233 == 0x400583:main(fishy.c:23)

在早期的Valgrind版本中,这些值被称为“愚蠢的参数”,并且不包括后跟踪。

4.2.8。内存泄漏检测

Memcheck记录所有响应于malloc/ etc的调用发出的堆块 new。所以当程序退出时,它知道哪些块没有被释放。

如果--leak-check适当设置,对于每个剩余的块,Memcheck确定块是否可以从根集中的指针到达。根组包括(a)所有线程的通用寄存器,以及(b)可访问的客户端存储器中的初始化,对齐,指针大小的数据字,包括堆栈。

有两种方式可以达到一个块。第一个是使用“开始指针”,即指向块开头的指针。第二个是“内部指针”,即指向块中间的指针。有几种方式可以发现内部指针可以发生:

  • 指针本来可能是一个开始指针,并被程序故意地(或不是故意地)移动。特别是,如果您的程序使用带标记的指针,即如果它使用指针的底部,两位或三位,由于对齐而通常总是为零,以便存储额外的信息,则可能会发生这种情况。

  • 这可能是内存中的随机垃圾值,完全不相关,只是巧合。

  • 它可能是指向C ++的内部char数组的指针 std::string。例如,一些编译器在std :: string的开头添加3个字,以便在包含字符数组的内存之前存储长度,容量和引用计数。他们在这3个字之后返回一个指针,指向char数组。

  • 一些代码可能会分配一个内存块,并使用前8个字节来存储(块大小-8)作为64位数。 sqlite3MemMalloc做这个。

  • 它可能是一个指向分配给C ++对象(具有析构函数)的数组的指针new[]。在这种情况下,一些编译器在分配的块的开始处存储包含数组长度的“魔术cookie”,并返回一个指向刚刚过去的魔术cookie(即内部指针)的指针。有关详细信息,请参阅此页面。

  • 它可能是指向使用多重继承的C ++对象的内部部分的指针。

您可以选择激活启发式检漏过程中使用,以检测对应的内部指针stdstring, length64, newarray 和multipleinheritance案件。如果启发式检测到内部指针对应于这种情况,则该块将被认为是内部指针可达的。换句话说,内部指针将被视为起始指针。

考虑到这一点,考虑下面描述的九种可能的情况。

     指针链AAA泄漏案BBB泄漏案
     ------------- ------------- -------------
(1)RRR ------------> BBB DR
(2)RRR ---> AAA ---> BBB DR IR
(3)RRR BBB DL
(4)RRR AAA ---> BBB DL IL
(5)RRR ------?-----> BBB(y)DR,(n)DL
(6)RRR ---> AAA  - ? - > BBB DR(y)IR,(n)DL
(7)RRR  - → - > AAA ---> BBB(y)DR,(n)DL(y)IR,(n)IL
(8)RRR-α - > AAA  - → - > BBB(y)DR,(n)DL(y,y)IR,(n,y)IL,(_,n)
(9)RRR AAA-β-> BBB DL(y)IL,(n)DL

指针链传说:
-  RRR:根集节点或DR块
-  AAA,BBB:堆块
-  --->:一个开始指针
-   - ? - >:内部指针

泄漏案例传奇:
-  DR:直接可达
-  IR:间接可达
-  DL:直接丢失
-  IL:间接丢失
- (y)XY:如果内部指针是真正的指针,则为XY
- (n)XY:如果内部指针不是真正的指针,则为XY
- (_)XY:在任一种情况下都是XY

每个可能的情况都可以减少到上述九个之一。Memcheck在其输出中合并了一些这种情况,导致以下四种泄漏。

  • “仍然可达”。这涵盖上面的1和2(对于BBB块)的情况。找到一个起始指针或链的起始指针。由于块仍然指向,程序员至少原则上可以在程序退出之前释放它。“仍然可达”的块是非常普遍的,可以说是不成问题的。因此,默认情况下,Memcheck不会单独报告此类块。

  • “绝对迷失”。这涵盖上面的案例3(对于BBB块)。这意味着没有找到指向块的指针。该块被分类为“丢失”,因为程序员不可能在程序退出时释放它,因为没有指向它的指针。这可能是程序中某个较早点丢失指针的症状。这种情况应由程序员修正。

  • “间接失踪”。这涵盖上面的案例4和9(对于BBB块)。这意味着块丢失,而不是因为没有指针,而是因为所有指向它的块都是丢失的。例如,如果您有一个二叉树并且根节点丢失,则其所有子节点将被间接丢失。因为如果导致间接泄漏的绝对丢失块是固定的,那么问题会消失,因此默认情况下Memcheck不会单独报告这些块。

  • “可能丢失”。这涵盖上面的5-8(对于BBB块)。这意味着已经找到了一个或多个指向块的指针的链,但是指针中的至少一个是内部指针。这可能只是内存中的一个随机值,恰好指向一个块,所以你不应该考虑这个确定,除非你知道你有内部指针。

(注:将九种可能的情况映射到四种泄漏方式不一定是报告泄漏的最佳方法;特别是内部指针被不一致地处理,将来可能会改进分类。)

此外,如果块的存在,则无论其属于上述四种,都将被报告为“被抑制”。

以下是泄漏汇总示例。

泄漏摘要:
   绝对丢失:3个块中的48个字节。
   间接丢失:32个字节在2个块。
     可能丢失:6个块中的96个字节。
   仍然可达:64个字节在4个块。
        被禁止:0个字节,0个块。

如果使用启发式方法来将某些块视为可达到的,则漏洞摘要将详细介绍每个启发式启发式启发式可达的“仍可达到的”子集。在下面的例子中,95字节仍然可以达到,87字节(56 + 7 + 8 + 16)被认为是启发式可达的。

泄漏摘要:
   绝对丢失:4个字节在1个块
   间接丢失:0个字节,0个块
     可能丢失:0个块中的0个字节
   仍然可达:6个块中有95个字节
                      其中通过启发式可达到:
                        stdstring:56个字节,2个块
                        length64:16个字节,1个块
                        newarray:1个块中有7个字节
                        多个继承:1个块中8个字节
        被禁止:0个字节,0个块

如果--leak-check=full指定,Memcheck将给出每个绝对丢失或可能丢失的块的详细信息,包括分配的位置。(实际上,它将具有相同泄漏类型的所有块的结果和足够相似的堆栈跟踪合并到一个“丢失记录”中, --leak-resolution允许您控制“足够相似”的含义。)它不能告诉你什么时候或如何或为什么指向一个泄漏块的指针丢失了; 你必须为自己工作。一般来说,您应该尝试确保您的程序在出口时没有任何明显的丢失或可能丢失的块。

例如:

1块中的8个字节在14的损失记录中绝对丢失
   在0x ........:malloc(vg_replace_malloc.c:...)
   by 0x ........:mk(leak-tree.c:11)
   by 0x ........:main(leak-tree.c:39)

1个块中的88个(8个直接,80个间接)字节在14的损失记录13中绝对丢失
   在0x ........:malloc(vg_replace_malloc.c:...)
   by 0x ........:mk(leak-tree.c:11)
   by 0x ........:main(leak-tree.c:25)

第一条消息描述了一个完全丢失的单个8字节块的简单情况。第二个例子提到另一个8字节块已经绝对丢失了; 不同之处在于其他块中的另外80个字节由于这个丢失块而间接丢失。损失记录不以任何显着的顺序呈现,因此损失记录数字并不具体。损失记录号可以在Valgrind gdbserver中使用,以列出泄露块的地址和/或提供有关块仍然可达的更多细节。

该选项--show-leak-kinds= 控制一组泄漏种类以显示何时--leak-check=full被指定。

泄漏种在下列方式之一指定的:

  • 一个或多个逗号分隔的列表 definite indirect possible reachable

  • all 指定完整集(所有泄漏种类)。

  • none 为空集。

泄漏种类的默认值为 --show-leak-kinds=definite,possible

还可以显示可达和间接丢失的块,除了绝对可能丢失的块,您可以使用--show-leak-kinds=all。只显示可达和间接丢失的块,使用 --show-leak-kinds=indirect,reachable。然后可以显示可达和间接丢失的块,如以下两个示例所示。

4块中的64字节仍然可以在4的损失记录中达到
   在0x ........:malloc(vg_replace_malloc.c:177)
   by 0x ........:mk(leak-cases.c:52)
   by 0x ........:main(leak-cases.c:74)

2个字节中的32个字节在4的损失记录1中间接丢失
   在0x ........:malloc(vg_replace_malloc.c:177)
   by 0x ........:mk(leak-cases.c:52)
   by 0x ........:main(leak-cases.c:80)

因为有不同种类的泄漏有不同的严重性,一个有趣的问题是:哪些泄漏应该算作真正的“错误”,哪些不应该?

这个问题的答案会影响打印ERROR SUMMARY在行中的数字,也影响--error-exitcode选项的效果。首先,如果指定了漏洞,则仅将其视为真实的“错误” --leak-check=full。然后,该选项--errors-for-leak-kinds=控制要作为错误考虑的一组泄漏种类。默认值为--errors-for-leak-kinds=definite,possible

4.3。Memcheck命令行选项

--leak-check= [default: summary]

启用时,在客户端程序完成时搜索内存泄漏。如果设置summary,它说发生了多少泄漏。如果设置为full或者 yes,每个单独的泄漏将被详细显示和/或计数为错误,如选项 --show-leak-kinds和 --errors-for-leak-kinds

--leak-resolution= [default: high]

在进行泄漏检查时,确定Memcheck如何将不同的回溯视为相同的目的,以将多个泄漏合并到单个泄漏报告中。设置时low,只有前两个条目需要匹配。何时med,四个条目必须匹配。何时high,所有条目都需要匹配。

对于硬核泄漏调试,您可能希望 --leak-resolution=high--num-callers=40这些大数量一起使用 。

请注意,该--leak-resolution设置不会影响Memcheck查找泄漏的能力。它只会改变结果的呈现方式。

--show-leak-kinds= [default: definite,possible]

指定泄漏检查中显示的泄漏种类,full 方法如下:

  • 一个或多个逗号分隔的列表 definite indirect possible reachable

  • all指定完整集(所有泄漏种类)。相当于 --show-leak-kinds=definite,indirect,possible,reachable

  • none 为空集。

--errors-for-leak-kinds= [default: definite,possible]

指定泄漏种类作为full泄漏搜索中的错误 。这 是相同的 --show-leak-kinds

--leak-check-heuristics= [default: all]

指定在泄漏搜索期间使用的泄漏检查启发式集合。启发式控制指向一个块的内部指针使其被认为是可达到的。启发式集合以下列方式之一指定:

  • 一个或多个逗号分隔的列表 stdstring length64 newarray multipleinheritance

  • all激活完整的启发式集。相当于 --leak-check-heuristics=stdstring,length64,newarray,multipleinheritance

  • none 为空集。

--show-reachable= ,  --show-possibly-lost=

这些选项提供了一种替代方法来指定泄漏种类来显示:

  • --show-reachable=no --show-possibly-lost=yes相当于 --show-leak-kinds=definite,possible

  • --show-reachable=no --show-possibly-lost=no相当于 --show-leak-kinds=definite

  • --show-reachable=yes相当于 --show-leak-kinds=all

--undef-value-errors= [default: yes]

控制Memcheck报告是否使用未定义的值错误。no如果您不想看到未定义的值错误,请将其设置为 。它也有加速Memcheck的副作用。

--track-origins= [default: no]

控制Memcheck是否跟踪未初始化值的来源。默认情况下,它不会,这意味着虽然可以告诉您未初始化的值正在以危险的方式使用,但它不能告诉您未初始化值的来源。这通常使得难以追溯根本问题。

设置时yes,Memcheck记录所有未初始化值的起始位置。然后,当报告未初始化的值错误时,Memcheck将尝试显示值的原点。原点可以是以下四个位置之一:堆块,堆栈分配,客户端请求或其他其他来源(例如,调用brk)。

对于源自堆块的未初始化值,Memcheck显示块分配的位置。对于来自堆栈分配的未初始化值,Memcheck可以告诉您分配了哪个值,但不超过该值 - 通常它会显示函数的开始大括号的源位置。所以你应该仔细检查所有函数的局部变量是否正确初始化。

性能开销:原始跟踪价格昂贵。它使Memcheck的速度减半,并将内存使用量提高至少100MB,甚至更多。然而,它可以大大减少确定未初始化价值错误的根本原因所需的努力,因此,尽管运行速度更慢,但通常也是程序员的生产力获胜。

准确度:Memcheck相当准确地跟踪起始点。为了避免非常大的空间和时间开销,进行一些近似。尽管可能不大可能,Memcheck会报告错误的来源,或者无法识别任何来源。

注意组合 --track-origins=yes 并且--undef-value-errors=no是无意义的。Memcheck启动时检查并拒绝此组合。

--partial-loads-ok= [default: yes]

控制Memcheck如何从一些字节可寻址的地址处理32位,64位,128位和256位自然对齐的负载,而其他字节不可寻址。何时yes,这样的负载不会产生地址错误。相反,来自非法地址的加载字节被标记为未初始化,并且以正常方式处理与合法地址相对应的字节。

no来自部分无效地址的加载处理与完全无效地址的加载相同时:发出非法地址错误,并将生成的字节标记为初始化。

请注意,以这种方式行为的代码违反ISO C / C ++标准,应被视为破坏。如果可能,这些代码应该是固定的。

--expensive-definedness-checks= [default: no]

控制在检查值的定义时,Memcheck是否应该使用更精确但更昂贵(耗时)的算法。默认设置不是这样做,它通常就足够了。然而,对于高度优化的代码,valgrind有时可能会错误地抱怨。调用valgrind --expensive-definedness-checks=yes 有助于提高性能成本。已经观察到25%的运行时间降级,但额外的成本在很大程度上取决于手头的应用。

--keep-stacktraces=alloc|free|alloc-and-free|alloc-then-free|none [default: alloc-and-free]

控制用于保持malloc'd和/或free'd块的堆栈跟踪。

使用alloc-then-free,在分配时间记录堆栈跟踪,并与块相关联。当块被释放时,记录第二个堆栈跟踪,这将替换分配堆栈跟踪。因此,与此块相关的任何“使用免费”错误只能显示块被释放的位置的堆栈跟踪。

对于alloc-and-free块,存储块的分配和分配堆栈跟踪。因此,“免费使用”错误将同时显示,这可能会使错误更容易诊断。与alloc-then-free此相比,此设置稍微增加了Valgrind的内存使用,因为该块包含两个引用而不是一个引用。

有了alloc,只是分配栈跟踪记录(并报告)。随着free,只有释放堆栈跟踪记录(并报告)。这些值稍微减少了Valgrind的内存和CPU使用率。它们可以是有用的,具体取决于您正在搜索的错误类型以及需要分析的详细程度。例如,如果您只对内存泄漏错误感兴趣,则记录分配堆栈跟踪就足够了。

随着none,没有堆栈跟踪记录的malloc和free操作。如果您的程序分配了许多块和/或从许多不同的堆栈跟踪分配/释放,这可以显着降低cpu和/或所需的内存。当然,与堆块相关的错误将不会报告很少的细节。

请注意,一旦记录了堆栈跟踪,Valgrind将堆栈跟踪保存在内存中,即使它没有被任何块引用。一些程序(例如,递归算法)可以产生大量堆栈跟踪。如果Valgrind在这种情况下使用太多内存,可以减少选项所需的内存--keep-stacktraces 和/或使用较小的选项值--num-callers

--freelist-vol= [default: 20000000]

当客户端程序使用free(in C)或 delete (C++)释放内存时 ,该内存不会立即可用于重新分配。相反,它被标记为不可访问,并放置在自由块的队列中。目的是尽可能延长释放记忆回流的时间。这增加了Memcheck在被释放后的某个相当长的一段时间内能够检测到块的无效访问的机会。

此选项指定队列中块的最大总大小(以字节为单位)。默认值为二千万字节。增加这会增加Memcheck使用的内存总量,但是可能会检测到无效的释放块的使用,否则将无法检测到。

--freelist-big-blocks= [default: 1000000]

当从可用于重新分配的释放块的队列中制作块时,Memcheck优先重新循环大小或等于的大小--freelist-big-blocks。这确保了释放大块(特别是释放块大于 --freelist-vol)不会立即导致自由列表中所有(或许多)小块的重新流通。换句话说,即使大块被释放,这个选项增加了发现“小”块的悬挂指针的可能性。

设置值为0表示所有块都按FIFO顺序重新循环。

--workaround-gcc296-bugs= [default: no]

启用时,假设读取和写入堆栈指针之外的一些小距离是由于GCC 2.96中的错误,并且不会报告它们。“小距离”默认为256字节。请注意,GCC 2.96是一些古老的Linux发行版(RedHat 7.X)上的默认编译器,因此您可能需要使用此选项。不要使用它,如果你不必要,因为它可以导致真正的错误被忽视。一个更好的选择是使用更新的GCC修复这个错误。

在32位PowerPC Linux上使用GCC 3.X或4.X时,还可能需要使用此选项。这是因为GCC生成的代码偶尔访问堆栈指针以下,特别是对于浮点数到整数转换。这违反了32位PowerPC ELF规范,这并不意味着堆栈指针下方的位置可以访问。

此选项自版本3.12已弃用,可能会从将来的版本中删除。您应该使用它 --ignore-range-below-sp来指定应忽略的堆栈指针下方的偏移量的精确范围。一个合适的等价物是--ignore-range-below-sp=1024-1

--ignore-range-below-sp=-

这是替代不推荐使用的 --workaround-gcc296-bugs选项。指定时,会导致Memcheck不会在堆栈指针下方的指定偏移量下报告访问错误。两个偏移量必须是正十进制数,并且有些是反直觉的 - 第一个必须更大,以便将非环绕地址范围意味着忽略。例如,要忽略堆栈指针下方8192字节的4字节访问,请使用--ignore-range-below-sp=8192-8189。只能指定一个范围。

--show-mismatched-frees= [default: yes]

启用时,Memcheck使用与分配功能匹配的功能来检查堆块是否被释放。也就是说,它预计free将用于删除根据所分配的块mallocdelete供分配的块new,并delete[]为块的分配new[]。如果检测到不匹配,则报告错误。这通常很重要,因为在某些环境中,释放不匹配的功能可能导致崩溃。

但是有一种情况是不能避免这种错配。那就是当用户提供 newnew[]该呼叫mallocdelete/或delete[]该呼叫的实现free时,这些功能是不对称内联的。例如,假设delete[]是内联的,但new[]不是。结果是Memcheck将所有delete[]呼叫视为直接呼叫free,即使程序源不包含不匹配的呼叫。

这会导致很多混乱和不相关的错误报告。 --show-mismatched-frees=no禁用这些检查。尽管如此,通常不建议您禁用它们,因为您可能会错过实际错误。

--ignore-ranges=0xPP-0xQQ[,0xRR-0xSS]

Memcheck的可寻址性检查将忽略此选项中列出的任何范围(可以指定多个范围,以逗号分隔)。

--malloc-fill=

填补了分配的块malloc, new等等,但不calloc与指定的字节。这在尝试摆脱晦涩的内存损坏问题时很有用。分配的区域仍然被Memcheck视为未定义 - 此选项仅影响其内容。请注意,--malloc-fill当用作客户端请求VALGRIND_MEMPOOL_ALLOC或VALGRIND_MALLOCLIKE_BLOCK的参数时,不会影响内存块。

--free-fill=

填充由释放的块free, delete等等,与指定的字节值。这在尝试摆脱晦涩的内存损坏问题时很有用。Memcheck仍然将被释放的区域视为访问无效 - 此选项仅影响其内容。请注意,--free-fill当用作客户端请求VALGRIND_MEMPOOL_FREE或VALGRIND_FREELIKE_BLOCK的参数时,不会影响内存块。

4.4。编写抑制文件

抑制错误中描述了基本抑制格式 。

抑制型(第二行)应具有以下格式:

MEMCHECK:suppression_type

Memcheck抑制类型如下:

  • Value1, Value2, Value4, Value8, Value16,使用的1,2,4,8或16字节的值时意一个未初始化的值错误。

  • Cond(或其旧名称Value0),意思是使用未初始化的CPU条件代码。

  • Addr1, Addr2, Addr4, Addr8, Addr16,分别是1,2,4,8或16字节的存储器访问期间意思的地址无效。

  • Jump,意味着跳转到不可寻址的位置错误。

  • Param,意味着无效的系统调用参数错误。

  • Free,意思是无效或不匹配的。

  • Overlap,意思是 srcdst重叠 memcpy或相似的功能。

  • Leak,意味着内存泄漏。

Param 此时,错误具有强制性的额外信息行,这是违规系统调用参数的名称。

Leak 错误有一个可选的额外信息行,格式如下:

匹配泄漏种:<设定>

其中指定该抑制条目匹配的泄漏种类。 以与选项相同的方式指定--show-leak-kinds,即以下之一:

  • 一个或多个逗号分隔的列表 definite indirect possible reachable
  • all 指定完整集(所有泄漏种类)。
  • none 为空集。

如果此可选附加行不存在,则抑制条目将匹配所有泄漏类型。

请注意,使用创建的泄漏抑制 --gen-suppressions将包含此可选额外的行,因此可能会比您预期的更少的泄漏。您可能需要在使用生成的压缩之前删除该行。

其他Memcheck错误种类没有额外的行。

如果您给出-v选项,Valgrind将在执行结束时打印所使用的抑制列表。对于泄漏抑制,该输出给出与抑制相匹配的不同丢失记录的数量,以及抑制抑制的字节数和块数。如果运行包含多个泄漏检查,则在每次新的泄漏检查之前,字节和块的数量将重置为零。请注意,不同的丢失记录的数量不会重置为零。

在下面的示例中,在最后一次泄漏搜索中,7个块和96个字节已被抑制,名称为 some_leak_suppression

--21041-- used_suppression:10 some_other_leak_suppression s.supp:14被抑制:1个块中的12,400个字节
--21041-- used_suppression:39 some_leak_suppression s.supp:2被抑制:7个块中的96个字节

对于ValueNAddrN 错误,调用上下文的第一行是发生错误的函数的名称,或者失败的是.so包含错误位置的文件或可执行文件的完整路径。对于Free错误,第一行是函数做的释放(例如,名字 free__builtin_vec_delete等等)。为Overlap错误,第一行是与重叠参数的函数的名称(例如 memcpystrcpy等)。

任何抑制的最后一部分指定需要匹配的调用上下文的其余部分。

4.5。Memcheck检查机械的详细信息

如果您想要详细了解Memcheck正在检查的内容,请阅读本部分。

4.5.1。有效值(V)位

考虑到Memcheck实现与真实CPU相同的合成CPU是最简单的,除了一个关键的细节。在实际CPU中处理,存储和处理的数据的每一位(字面上)在合成CPU中具有相关联的“有效值”位,其说明伴随位是否具有合法值。在下面的讨论中,该位被称为V(有效值)位。

因此,系统中的每个字节都有一个8位,它随处可见。例如,当CPU从存储器加载一个字大小的项目(4字节)时,它也会从位图中加载相应的32 V位,该位图存储过程'整个地址空间的V位。如果CPU应该将该值的全部或部分内容写入不同地址的存储器,则相关的V位将被存储在V位位图中。

简而言之,系统中的每一位都有(概念上)一个关联的V位,即使在CPU内部也随处可见。是的,所有CPU的寄存器(整数,浮点,向量和条件寄存器)都有自己的V位向量。为了使这个工作,Memcheck使用大量的压缩来紧密地表示V位。

复制值不会导致Memcheck检查或报告错误。但是,当以可能影响程序的外部可见行为的方式使用值时,将立即检查相关的V位。如果其中任何一个表示该值未定义(甚至部分),则会报告错误。

这是一个(绝对荒谬的)例子:

int i,j;
int [10],b [10];
for(i = 0; i <10; i ++){
  j = a [i];
  b [i] = j;
}

Memcheck不会对此发出任何抱怨,因为它仅将未初始化的值复制a[]到 b[]并且不会以影响程序行为的方式使用它们。但是,如果循环更改为:

for(i = 0; i <10; i ++){
  j + = a [i];
}
if(j == 77) 
  printf(“hello there \ n”);

那么Memcheck会抱怨说, if条件取决于未初始化的值。请注意,它不会抱怨j += a[i];,因为在那个时候,未定义不是“可观察的”。只有当printfMemcheck投诉时,必须做出是否做出您的程序的可观察的操作的决定。

大多数低级操作(如增加)使Memcheck使用V位作为操作数来计算结果的V位。即使结果部分或全部未定义,也不会抱怨。

对定义的检查仅发生在三个位置:当使用值来生成存储器地址时,需要进行控制流决定时,以及检测到系统调用时,Memcheck根据需要检查参数的定义。

如果检查应检测未定义,则会发出错误消息。结果的值随后被认为是明确的。否则会给出长链的错误消息。换句话说,一旦Memcheck报告了一个未定义的值错误,它会尝试避免报告从相同的未定义值导出的进一步错误。

这听起来过于复杂。为什么不检查内存中的所有读数,并抱怨如果未定义的值被加载到CPU寄存器中?那么这样做并不好,因为完全合法的C程序通常会在内存中复制未初始化的值,我们不希望无限的抱怨。这是典型的例子。考虑一个这样的结构:

struct S {int x; char c };
结构S s1,s2;
s1.x = 42;
s1.c ='z';
s2 = s1;

要问的问题是:struct S以字节为单位多大?一个int是4个字节和 char一个字节,所以也许struct S占用5个字节?错误。我们所知道的所有非玩具编译器都将struct S整合大量的单词,在这种情况下为8个字节。不这样做会强制编译器生成真正令人震惊的代码来访问 struct S某些架构上的数组。

所以s1占用8个字节,但只有5个字节被初始化。对于作业s2 = s1,GCC生成代码,将所有8个字节批量复制到s2 不考虑其含义。如果Memcheck从内存中简单地检查了值,那么每当这样的结构赋值发生时,它就会变得啰嗦。所以上述更复杂的行为是必要的。这让GCC复制 s1s2它喜欢的任何方式,如果未初始化值以后要使用的警告才会发出。

4.5.2。有效地址(A)位

请注意,上一小节描述了如何建立和维护值的有效性,而无需说明程序是否具有访问任何特定内存位置的权限。我们现在考虑后一个问题。

如上所述,存储器或CPU中的每一位具有相关联的有效值(V)位。此外,内存中的所有字节,但不在CPU中,都具有相关的有效地址(A)位。这表示程序是否可以合法读取或写入该位置。它不表示在该位置的数据的有效性 - 这是V位的工作 - 只有位置是否可被访问。

每次程序读或写内存时,Memcheck会检查与地址相关的A位。如果其中任何一个表示无效的地址,则会发出错误。请注意,读写本身不改变A位,只能查阅它们。

那么A位如何设置/清除?喜欢这个:

  • 当程序启动时,所有的全局数据区被标记为可访问。

  • 当程序执行 mallocnew时,用于准确分配区域的A位,而不是更多的字节被标记为可访问。释放该区域后,A位被更改以指示不可访问。

  • 堆栈指针寄存器(SP)向上或向下移动时,A位置1。规则是从SP栈到底的区域 被标记为可访问,下面SP是不可访问的。(如果这听起来不合逻辑,请记住,几乎所有的Unix系统(包括GNU / Linux),堆栈都在增长,而不是上升。)SP像这样的跟踪 具有一个有用的副作用,即本地功能的一部分堆栈变量等在函数输入时自动标记可访问,退出时无法访问。

  • 进行系统调用时,A位被适当地改变。例如,mmap 神奇地使文件出现在进程的地址空间中,因此如果mmap 成功,必须更新A位。

  • 或者,您的程序可以使用上述客户端请求机制明确地告诉Memcheck这些更改。

4.5.3。把它们放在一起

Memcheck的检查机械可概括如下:

  • 存储器中的每个字节具有8个关联的V(有效值)位,表示该字节是否具有定义的值,以及一个A(有效地址)位,表示该程序当前是否具有读/写那个地址。如上所述,大量使用压缩意味着开销通常在25%左右。

  • 当读取或写入存储器时,可以查询相关的A位。如果它们指示无效的地址,Memcheck会发出无效的读取或无效的写入错误。

  • 当内存读入CPU的寄存器时,相关的V位将从存储器中读取并存储在模拟CPU中。没有咨询。

  • 当寄存器写入存储器时,该寄存器的V位也被写回存储器。

  • 当CPU寄存器中的值用于生成存储器地址或确定条件转移的结果时,将检查这些值的V位,如果其中任何一个未定义,则发出错误。

  • 当CPU寄存器中的值用于任何其他目的时,Memcheck计算结果的V位,但不检查它们。

  • 一旦检查了CPU中的值的V位,就将它们设置为指示有效性。这样可以避免长链错误。

  • 当从内存中加载值时,Memcheck会检查该位置的A位,并在需要时发出非法地址警告。在这种情况下,加载的V位将被强制指示有效,尽管位置无效。

    这显然奇怪的选择减少了向用户呈现的令人困惑的信息量。它避免了从不可寻址并包含无效值的地方读取存储器的不愉快现象,因此,您不仅可以获得无效地址(读/写)错误,还可以获得一个潜在的大量未初始化值错误,每次使用该值时都会出现一个错误。

    来自地址部分有效且部分无效的多字节加载有一个模糊的边界情况。有关详细信息,请参阅选项--partial-loads-ok的详细信息。

MEMCHECK拦截来电malloc, callocrealloc, vallocmemalign, freenew, new[], delete和 delete[]。你得到的行为是:

  • mallocnewnew[]:返回存储器被标记为可寻址的但不具有有效的值。这意味着你必须写信才能阅读它。

  • calloc:返回的内存标记为可寻址和有效,因为calloc将该区域清除为零。

  • realloc:如果新的大小大于旧的,新的部分可寻址但无效,如同 malloc。如果新尺寸较小,则下降部分被标记为不可寻址。你可能只传给 realloc以前由malloccalloc/ 发给你的指针 realloc

  • freedeletedelete[]:你只能传递给这些函数一个指针,以前由相应的分配函数发给你。否则,Memcheck抱怨。如果指针确实有效,则Memcheck将其指向的整个区域标记为不可寻址,并将该块置于自由块队列中。目的是推迟这个块的重新分配。在这种情况下,所有尝试访问它将会引发无效的地址错误,就像您所希望的那样。

4.6。Memcheck监视器命令

Memcheck工具提供由Valgrind内置的gdbserver 处理的监视器命令(请参阅Valgrind gdbserver的Monitor命令处理)。

  • xb [] 显示从开始的(默认为1)字节的定义(V)位和值。对于每8个字节,输出两行。

    第一行显示8个字节的有效位。使用两个十六进制数字给出范围内每个字节的定义。这些十六进制数字对相应字节的每个位的有效性进行编码,如果定义了该位,则使用0,如果该位未定义则为1。如果一个字节不可寻址,则其有效位被__(双下划线)替代。

    第二行显示低于相应有效位的字节值。用于显示字节数据的格式与GDB命令'x / xb '类似。不可寻址字节的值显示为?(两个问号)。

    在下面的示例中,string10是一个10个字符的数组,其中偶数字节未定义。在下面的例子中,对应的字节string10[5]是不可寻址的。

    (gdb)p&string10
    $ 4 =(char(*)[10])0x804a2f0
    (gdb)mo xb 0x804a2f0 10
                      ff 00 ff 00 ff __ ff 00
    0x804A2F0:0x3f 0x6e 0x3f 0x65 0x3f 0x ?? 0x3f 0x65
                      ff 00
    0x804A2F8:0x3f 0x00
    地址0x804A2F0 len 10有1个字节不可寻址
    (GDB)
    

    命令xb不能与寄存器一起使用。要获取寄存器的有效位,您必须使用该选项启动Valgrind --vgdb-shadow-registers=yes。然后可以通过打印相应的“shadow 1”寄存器来获得寄存器的有效位。在下面的x86示例中,寄存器eax具有未定义的所有位,而寄存器ebx被完全定义。

    (gdb)p / x $ eaxs1
    $ 9 = 0xffffffff
    (gdb)p / x $ ebxs1
    $ 10 = 0x0
    (GDB) 
    
  • get_vbits [] 使用与xb命令相同的约定,显示从开始的(默认1)字节的定义(V)位 。get_vbits仅显示V位(按4字节分组)。它不显示值。如果要将V位与相应的字节值相关联,则 xb命令将更容易使用,特别是在将未定义的整数部分与其V位值相关联时,在小端计算机上。

    下面的示例示出的结果get_vibts 对string10在所使用的 xb 命令的解释。

    (gdb)monitor get_vbits 0x804a2f0 10
    ff00ff00 ff__ff00 ff00
    地址0x804A2F0 len 10有1个字节不可寻址
    (GDB) 
    
  • make_memory [noaccess|undefined|defined|Definedifaddressable] []中的(默认为1)字节的范围标记为具有给定状态。参数 noaccess将范围标记为不可访问,所以Memcheck会报告任何访问错误。 undefineddefined将该区域标记为可访问的,但是Memcheck分别将其中的字节视为具有未定义或定义的值。 Definedifaddressable标记为已定义的范围内的字节,这些字节已经可以被寻址,但不能改变不可寻址范围内的字节的状态。请注意,第一个字母Definedifaddressable 是大写D以避免混淆defined

    在下面的例子中,第一个字节 string10被标记为定义:

    (gdb)monitor make_memory定义0x8049e28 1
    (gdb)monitor get_vbits 0x8049e28 10
    0000ff00 ff00ff00 ff00
    (GDB) 
    
  • check_memory [addressable|defined] []检查(默认为1)字节的范围是否具有指定的可访问性。然后输出的描述。在以下示例中,可以使用详细的描述,因为该选项--read-var-info=yes在Valgrind启动时给出:

    (gdb)monitor check_memory定义0x8049e28 1
    地址0x8049E28 len 1定义
    == 14698 ==位置0x8049e28是string10 [0]中的0个字节,
    == 14698 ==在prog.c中声明:10,在线程1的帧#0中
    (GDB) 
    
  • leak_check [full*|summary] [kinds |reachable|possibleleak*|definiteleak] [heuristics heur1,heur2,...] [increased*|changed|any] [unlimited*|limited 执行泄漏检查。参数*中的参数表示默认值。

    如果[full*|summary]是 summary,则仅提供泄漏搜索的摘要; 否则将生成完整的泄漏报告。完整的泄漏报告提供了每个泄漏的详细信息:泄漏的块被分配的堆栈跟踪,泄露的块的数量及其总大小。当请求完整的报告时,接下来的两个参数进一步指定了什么样的泄漏报告。如果匹配第二个参数和第三个参数,则会显示一个泄漏的详细信息。完整的泄漏报告可能会输出许多泄漏的详细信息。输出信息的nr可以使用limited随后的最大nr个泄漏记录输出的参数进行控制。如果达到此最大值,则泄漏搜索将输出最大字节数的记录。

    kinds参数控制了full泄漏搜索显示哪些块。可以使用与命令行选项类似的方式指定要显示的一组泄漏种类--show-leak-kinds。或者,该值definiteleak 相当于kinds definite,该值possibleleak相当于 kinds definite,possible:它还将显示可能的泄漏块,只有一个内部指针被找到。该值reachable将显示所有块类别(即等效于kinds all)。

    heuristics参数控制在泄漏搜索期间使用的启发式。可以使用类似于命令行选项来指定要使用的启发式集合--leak-check-heuristicsheuristics参数 的默认值为heuristics none

    [increased*|changed|any]参数控制对full泄漏搜索显示哪些更改。该值increased指定仅显示自上次泄漏检查以来泄漏字节或块数增加的块分配堆栈。该值changed指定应显示自上次泄漏检查以来发生任何更改的分配堆栈。该值any指定应显示所有泄漏条目,而不管任何增加或减少。当指定increasedchanged指定时,泄漏报告条目将显示相对于上一个泄漏报告的增量。

    以下示例显示leak_check了在memcheck/tests/leak-cases.c回归测试中使用 monitor命令。第一个命令输出一个具有增加的泄漏字节的条目。第二个命令与第一个命令相同,但使用GDB和Valgrind gdbserver接受的缩写形式。它只输出摘要信息,因为以前的泄漏搜索没有增加。

    (gdb)monitor leak_check full possibleleak增加
    在1(+1)个块中的== 19520 == 16(+16)个字节可能在12的损失记录中丢失
    == 19520 == at 0x40070B4:malloc(vg_replace_malloc.c:263)
    == 19520 == by 0x80484D5:mk(leak-cases.c:52)
    == 19520 == by 0x804855F:f(leak-cases.c:81)
    == 19520 == by 0x80488E0:main(leak-cases.c:107)
    == == 19520 
    == 19520 == LEAK SUMMARY:
    == 19520 ==绝对丢失:2(+0)个块中的32(+0)个字节
    == 19520 ==间接丢失:1(+0)个块中的16(+0)个字节
    == 19520 ==可能丢失:2(+1)块中的32(+16)个字节
    == 19520 ==仍然可达:6(+1)个块中的96(+16)个字节
    == 19520 == suppress:0(+0)个字节在0(+0)块
    == 19520 ==可以访问的块(找到指针的块)未显示。
    == 19520 ==要查看它们,请将“可达到的任何”参数添加到leak_check
    == == 19520 
    (gdb)mo l
    == 19520 == LEAK SUMMARY:
    == 19520 ==绝对丢失:2(+0)个块中的32(+0)个字节
    == 19520 ==间接丢失:1(+0)个块中的16(+0)个字节
    == 19520 ==可能丢失:2(+0)个块中的32(+0)个字节
    == 19520 ==仍然可达:6(+0)个块中的96(+0)个字节
    == 19520 == suppress:0(+0)个字节在0(+0)块
    == 19520 ==可以访问的块(找到指针的块)未显示。
    == 19520 ==要查看它们,请将“可达到的任何”参数添加到leak_check
    == == 19520 
    (GDB) 
    

    请注意,当使用Valgrind的gdbserver时,无需重新运行--leak-check=full --show-reachable=yes查看可访问的块。您可以通过使用GDB命令monitor leak_check full reachable any(或使用缩写:)获得相同的信息,而无需重新运行mo l f r a

  • block_list |.. [unlimited*|limited ] [heuristics heur1,heur2,...] 显示属于(或损失记录范围 ..)的块列表 。可以使用limited参数后跟最大nr个块来输出要打印 的块的nr。如果给出一个或多个启发式方法,则仅打印通过给定heur1,heur2,... 启发式之一找到的损失记录和块。

    泄漏搜索将丢失记录中的已分配块合并:丢失记录重新分组具有相同状态的所有块(例如,绝对丢失)和相同的分配回溯。每个损失记录在泄漏搜索结果中由损失记录号识别。该block_list命令显示损失记录信息,后面跟随损失记录中合并的块的地址和大小。如果使用启发式方法找到一个块,则块大小后面是启发式的。

    如果直接丢失的块导致一些其他块间接丢失,则block_list命令还将显示这些间接丢失的块。根据直接丢失块和间接丢失块之间的间接级别,间接丢失的块将被缩进。每个间接丢失的块后面都是其损失记录的引用。

    只要在这种泄漏搜索之后没有块被释放,就可以使用block_list命令对泄漏搜索结果:一旦程序释放一个块,在再次使用block_list之前需要新的泄漏搜索。

    在下面的示例中,程序通过丢失指向块A(树顶部)的指针来泄漏树结构。因此,块A直接丢失,导致块B到G的间接丢失。第一个block_list命令显示A(一个绝对丢失的块,地址为0x4028028,大小为16)的丢失记录。块A的间接丢失块的地址和大小显示在块A的下方。第二个命令显示第一个命令输出的间接损失记录之一的细节。

               一个
             / \
            公元前
           / \ / \ 
          DEFG
    
    (gdb)bt
    #0 main()在leak-tree.c:69
    (gdb)monitor leak_check full any
    == 19552 == 112(16个直接,96个间接)1个块中的字节在7的损失记录中绝对丢失
    == 19552 ==在0x40070B4:malloc(vg_replace_malloc.c:263)
    == 19552 == 0x80484D5:mk(leak-tree.c:28)
    == 19552 == by 0x80484FC:f(leak-tree.c:41)
    == 19552 == by 0x8048856:main(leak-tree.c:63)
    == == 19552 
    == 19552 == LEAK SUMMARY:
    == 19552 ==绝对丢失:1个块中的16个字节
    == 19552 ==间接丢失:6个块中的96个字节
    == 19552 ==可能丢失:0个字节,0个块
    == 19552 ==仍然可达:0个字节,0个块
    == 19552 == suppress:0个字节,0个块
    == == 19552 
    (gdb)monitor block_list 7
    == 19552 == 112(16个直接,96个间接)1个块中的字节在7的损失记录中绝对丢失
    == 19552 ==在0x40070B4:malloc(vg_replace_malloc.c:263)
    == 19552 == 0x80484D5:mk(leak-tree.c:28)
    == 19552 == by 0x80484FC:f(leak-tree.c:41)
    == 19552 == by 0x8048856:main(leak-tree.c:63)
    == 19552 == 0x4028028 [16]
    == 19552 == 0x4028068 [16]间接损失记录1
    == 19552 == 0x40280E8 [16]间接损失记录3
    == 19552 == 0x4028128 [16]间接损失记录4
    == 19552 == 0x40280A8 [16]间接损失记录2
    == 19552 == 0x4028168 [16]间接损失记录5
    == 19552 == 0x40281A8 [16]间接损失记录6
    (gdb)mo b 2
    == 19552 == 1个块中的16个字节在7的损失记录中间接丢失
    == 19552 ==在0x40070B4:malloc(vg_replace_malloc.c:263)
    == 19552 == 0x80484D5:mk(leak-tree.c:28)
    == 19552 == by 0x8048519:f(leak-tree.c:43)
    == 19552 == by 0x8048856:main(leak-tree.c:63)
    == 19552 == 0x40280A8 [16]
    == 19552 == 0x4028168 [16]间接损失记录5
    == 19552 == 0x40281A8 [16]间接损失记录6
    (GDB) 
    
    
  • who_points_at [] 显示找到指向addr的指针的所有位置。如果len等于1,则该命令仅显示正好位于addr的位置(即“add指针”到addr)。如果len> 1,指示len第一个字节的“内部指针”也将显示。

    搜索的位置与泄漏搜索中使用的位置相同。所以,who_points_at可以用来显示为什么泄漏搜索仍然可以到达一个块,或者可以搜索悬浮指针到一个被释放的块。将描述指向addr的每个位置(或指向内部addr,如果正在搜索内部指针)。

    在下面的示例中,block_list在树被泄露之前显示了指向“树块A”(参见命令中的示例)的指针。说明详见--read-var-info=yes Valgrind启动时的选项。第二个调用显示块G的指针(起始和内部指针)。块G(0x40281A8)可通过块C(0x40280a8)和寄存器ECX为tid 1(tid为Valgrind线程ID)到达。通过注册EBX“内部可达”。

    (gdb)monitor who_points_at 0x4028028
    == 20852 ==搜索指向0x4028028的指针
    == 20852 == * 0x8049e20分在0x4028028
    == 20852 ==位置0x8049e20是全局var“t”内的0个字节
    == 20852 ==在leak-tree.c中声明:35
    (gdb)monitor who_points_at 0x40281A8 16
    == 20852 ==搜索从0x40281a8指向16个字节的指针
    == 20852 == * 0x40280ac指向0x40281a8
    == 20852 ==地址0x40280ac是一个大小为16个alloc'd的块内的4个字节
    == 20852 == 0x40070B4:malloc(vg_replace_malloc.c:263)
    == 20852 == 0x80484D5:mk(leak-tree.c:28)
    == 20852 == 0x8048519:f(leak-tree.c:43)
    == 20852 == by 0x8048856:main(leak-tree.c:63)
    == 20852 == tid 1注册ECX分数为0x40281a8
    == 20852 == tid 1注册EBX内部点在2个字节内部0x40281a8
    (GDB)
    

    who_points_at找到一个内部指针时,它会报告这个内部指针被认为是可达到的启发式的。请注意,这是独立于该选项的值完成的--leak-check-heuristics。在下面的示例中,丢失记录6指示可能丢失的块。who_points_at报告指出内部指针指向该块,并且该块可以被认为是可以使用启发式的multipleinheritance

    (gdb)monitor block_list 6
    == 3748 == 1个块中的8个字节可能在7的损失记录6中丢失
    == 3748 == at 0x4007D77:operator new(unsigned int)(vg_replace_malloc.c:313)
    == 3748 == 0x8048954:main(leak_cpp_interior.cpp:43)
    == 3748 == 0x402A0E0 [8]
    (gdb)monitor who_points_at 0x402A0E0 8
    == 3748 ==搜索指向0x402a0e0的8个字节的指针
    == 3748 == * 0xbe8ee078内部点4个字节内部0x402a0e0
    == 3748 ==地址0xbe8ee078在线程1的堆栈
    == 3748 == block at 0x402a0e0认为可以通过ptr 0x402a0e4使用多重继承启发式
    (GDB) 
    

4.7。客户端请求

下面定义了以下客户端请求 memcheck.h。看到memcheck.h他们的参数的确切细节。

  • VALGRIND_MAKE_MEM_NOACCESS, VALGRIND_MAKE_MEM_UNDEFINED和 VALGRIND_MAKE_MEM_DEFINED。这些标记地址范围完全不可访问,可访问但包含未定义的数据,并且可访问和包含定义的数据。在Valgrind运行时返回-1,否则返回0。

  • VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE。这就像VALGRIND_MAKE_MEM_DEFINED只是影响那些已经可寻址的字节。

  • VALGRIND_CHECK_MEM_IS_ADDRESSABLE并且 VALGRIND_CHECK_MEM_IS_DEFINED:立即检查给定的地址范围是否具有相关属性,如果没有,请打印错误消息。此外,为了方便客户,如果相关属性成立,则返回零; 否则,返回的值是属性不为真的第一个字节的地址。在Valgrind不运行时始终返回0。

  • VALGRIND_CHECK_VALUE_IS_DEFINED:一个快速简便的方法来查明Valgrind是否认为一个特定的值(左值,准确)是可寻址和定义的。打印错误信息,如果没有。它没有返回值。

  • VALGRIND_DO_LEAK_CHECK:现在做一个完整的内存泄漏检查(像--leak-check=full)。这对于逐步检查程序执行中任意位置之间的泄漏是非常有用的。它没有返回值。

  • VALGRIND_DO_ADDED_LEAK_CHECK:与 VALGRIND_DO_LEAK_CHECK以前的泄漏搜索相同 但仅显示泄漏字节增加或块数泄漏的条目。它没有返回值。

  • VALGRIND_DO_CHANGED_LEAK_CHECK:与VALGRIND_DO_LEAK_CHECK以前的泄漏搜索相同 但仅显示泄露字节增加或减少的条目或泄漏的块数。它没有返回值。

  • VALGRIND_DO_QUICK_LEAK_CHECK:喜欢 VALGRIND_DO_LEAK_CHECK,除了它只产生泄漏汇总(如--leak-check=summary)。它没有返回值。

  • VALGRIND_COUNT_LEAKS:填写四个参数,其中先前泄漏检查发现的内存字节数(即直接泄漏和间接泄漏的总和),可疑,可达到并被抑制。这在调用VALGRIND_DO_LEAK_CHECK或 之后的测试线索代码中很有用VALGRIND_DO_QUICK_LEAK_CHECK

  • VALGRIND_COUNT_LEAK_BLOCKS: VALGRIND_COUNT_LEAKS除了它返回块数而不是每个类别中的字节数除外。

  • VALGRIND_GET_VBITS并且 VALGRIND_SET_VBITS:允许您获取并设置地址范围的V(有效性)位。你应该只设置你所拥有的V位 VALGRIND_GET_VBITS。只有那些真正了解他们在做什么的人才会。

  • VALGRIND_CREATE_BLOCK和 VALGRIND_DISCARD。 VALGRIND_CREATE_BLOCK 取一个地址,一个字节和一个字符串。然后指定的地址范围与该字符串相关联。当Memcheck报告对该范围内的地址的无效访问时,它将根据该块而不是根据其知道的任何其他块来描述它。请注意,这个宏的使用实际上并不会以任何方式改变内存的状态 - 它只是给出范围的名称。

    在某些时候,您可能希望Memcheck停止报告错误VALGRIND_CREATE_BLOCK。为了实现这一点,VALGRIND_CREATE_BLOCK返回一个“块句柄”,它是一个C int值。您可以将此块句柄传递给VALGRIND_DISCARD。在这样做之后,Valgrind将不再将指定范围内的寻址错误与块相关联。传递无效手柄 VALGRIND_DISCARD是无害的。

4.8。内存池:描述和使用自定义分配器

有些程序通常使用自定义内存分配器,出于性能原因。留给自己,Memcheck无法理解自定义分配方案的行为,并且它了解标准分配器,因此可能会错过您的程序中的错误和泄漏。本节描述的是一种给予Memcheck足够的自定义分配器的描述的方法,它可以使至少有一些感觉发生了什么。

有许多不同种类的自定义分配器,所以Memcheck尝试使用松散的抽象模型来推理它们。描述自定义分配系统时,我们使用以下术语:

  • 自定义分配涉及一组独立的“内存池”。

  • Memcheck对内存池的概念由一个“锚地址”和一组与锚地址相关联的非重叠“块”组成。

  • 通常,池的锚地址是保存“标题”结构的地址。

  • 典型地,该池的块被从通过该系统获得的连续的“超级块”拉伸 malloc或 mmap

请记住,上面的最后两点表示“通常”:Valgrind mempool客户端请求API对于mempool的确切结构有意识地模糊。没有具体提及标题或超级块。然而,以下图片可能有助于阐明API中术语的意图:

   “池子”
   (锚地址)
   |
   v
   + -------- + --- +
   | 标题| o |
   + -------- +  -  |  -  +
              |
              v超级块
              + ------ + --- + -------------- + --- + ------------------ +
              | | RZB | 分配| rzB | |
              + ------ + --- + -------------- + --- + ------------------ +
                         ^ ^
                         | |
                       “addr”“addr”+“size”

注意,报头和超级块可以是连续的或不连续的,并且可能存在与单个报头相关联的多个超级块; 这种变化对于Memcheck是不透明的。API只要求您的分配方案可以显示“pool”,“addr”和“size”的合理值。

通常,在发出与mempools相关的客户端请求之前,客户端程序将为其mempool分配这样的头和超级块,并使用VALGRIND_MAKE_MEM_NOACCESS客户端请求标记超级块NOACCESS 。

在处理mempools时,目标是保持一个特定的不变条件:Memcheck相信池的超级块(包括redzones)的未分配部分是NOACCESS。为了维护这个不变量,客户端程序必须确保超级块在那个状态下开始; Memcheck不能这样做,因为Memcheck从来没有明确地了解一个池的超级块,只有池中已分配的块。

一旦池的头和超级块被建立并被正确标记,有许多客户端请求程序可以用来通知Memcheck关于对mempool的状态的改变:

  • VALGRIND_CREATE_MEMPOOL(pool, rzB, is_zeroed):该请求将地址pool注册为内存池的锚地址。它还提供了一个大小rzB,指定从池分配的块周围放置的redzones 的大小 。最后,它提供了一个 is_zeroed参数,指定在分配时池的块是否为零(更精确地:定义)。

    完成此请求后,不会与池相关联。该请求简单地告诉Memcheck池存在,以便后续调用可以将其称为池。

  • VALGRIND_CREATE_MEMPOOL_EXT(pool, rzB, is_zeroed, flags):创建具有一些标志的内存池(可以一起OR-ed)指定扩展行为。当标志为零时,行为是相同的 VALGRIND_CREATE_MEMPOOL

    • 该标志VALGRIND_MEMPOOL_METAPOOL 指定与使用的池相关联的内存块VALGRIND_MEMPOOL_ALLOC将被应用程序用作超级块以使用MALLOC_LIKE块VALGRIND_MALLOCLIKE_BLOCK。换句话说,元池是一个“2级”池:第一级是由...描述的块VALGRIND_MEMPOOL_ALLOC。使用第二级块进行描述VALGRIND_MALLOCLIKE_BLOCK。请注意,池和第二级块之间的关联是隐式的:第二级块将位于第一级块内。有必要VALGRIND_MEMPOOL_METAPOOL对这样的2级池使用标志,否则valgrind将检测到重叠的内存块,并将中止执行(例如

    • VALGRIND_MEMPOOL_AUTO_FREE。这样的元池也可以使用标志来标记为“自动免费”池,该标志VALGRIND_MEMPOOL_AUTO_FREE必须与...一起使用VALGRIND_MEMPOOL_METAPOOL。对于“自动免费”池,VALGRIND_MEMPOOL_FREE 将自动释放第一个级别块中包含的第二级别块VALGRIND_MEMPOOL_FREE。换句话说,调用VALGRIND_MEMPOOL_FREE将对VALGRIND_FREELIKE_BLOCK包含在第一级块中的所有第二级块产生隐式调用。注意:使用VALGRIND_MEMPOOL_AUTO_FREE没有VALGRIND_MEMPOOL_METAPOOL标志的标志 是一个错误。

  • VALGRIND_DESTROY_MEMPOOL(pool):此请求告诉Memcheck池已被拆除。Memcheck然后删除与池相关联的所有块的记录,以及其存储池的记录。在破坏其记忆的记录的同时,Memcheck将池中任何活块的redzones重置为NOACCESS。

  • VALGRIND_MEMPOOL_ALLOC(pool, addr, size):此请求通知Memcheck size已经分配了一个字节块addr,并将该组合与指定的块相关联 pool。如果池是用非零rzB红色区域创建的 ,Memcheck会将rzB块之前和之后的字节标记 为NOACCESS。如果使用is_zeroed参数集创建池,Memcheck会将该块标记为DEFINED,否则Memcheck会将该块标记为UNDEFINED。

  • VALGRIND_MEMPOOL_FREE(pool, addr):此请求通知Memcheck,该块addr 不应被视为已分配。Memcheck将标记与NOACCESS相关联的块addr,并删除其块的存在记录。

  • VALGRIND_MEMPOOL_TRIM(pool, addr, size):此请求修剪与之相关联的块pool。该请求仅对与之相关的块进行操作 pool。修剪正式定义为:

    • 整个范围内的所有块 addr..(addr+size-1)都被保留。

    • 完全超出范围的所有块 addr..(addr+size-1)都被丢弃,就像 VALGRIND_MEMPOOL_FREE被称为它们一样。

    • 所有其他块必须与范围相交 addr..(addr+size-1); 十字路口以外的区域被标记为“非空”,就像它们已被独立释放一样 VALGRIND_MEMPOOL_FREE

    这是一个很少见的请求,但可以在实现常规LIFO分配器中常见的无大规模操作的类型中有用。

  • VALGRIND_MOVE_MEMPOOL(poolA, poolB):此请求通知Memcheck以前锚定在地址的池poolA已移动到锚地址 poolB。这是一个罕见的请求,通常只有当您realloc的回忆标题时才需要。

    此请求不会更改内存状态位。

  • VALGRIND_MEMPOOL_CHANGE(pool, addrA, addrB, size):该请求通知MEMCHECK先前在地址分配的组块addrA内的 pool已被移动和/或调整大小,应加以改变,以覆盖该区域addrB..(addrB+size-1)。这是一个罕见的请求,通常只有realloc在超级块或希望扩展块而不改变其内存状态位时才需要 。

    此请求不会更改内存状态位。

  • VALGRIND_MEMPOOL_EXISTS(pool):该请求通知呼叫者Memcheck是否正在锚定地址处追踪回忆pool。当与该地址相关联的mempool时,它评估为1,否则为0。这是一个罕见的请求,仅在客户端代码可能丢失了一组活动mempools的情况下才有用。

4.9。使用Valgrind调试MPI并行程序

Memcheck支持使用MPI消息传递标准的分布式内存应用程序的调试。该支持包括用于该PMPI_*接口的包装函数库 。当加入到应用程序的地址空间中,无论是直接链接或通过 LD_PRELOAD,该包装拦截来电PMPI_Send, PMPI_Recv等他们再使用客户端的请求,通知所造成的功能被包装内存状态变化MEMCHECK。这减少了Memcheck通常为MPI应用报告的误报数量。

包装器还利用机会仔细检查作为参数传递给MPI函数的缓冲区的大小和定义,从而检测错误,例如将未定义的数据传递到 PMPI_Send或将数据接收到太小的缓冲区中。

与大多数其他Valgrind不同,包装库受BSD风格许可,因此您可以将其链接到任何您喜欢的代码库。查看mpi/libmpiwrap.c 许可证详细信息的顶部。

4.9.1。建造和安装包装纸

如果可能,将自动构建包装库。Valgrind的配置脚本将寻找适合 mpicc于构建它。这必须与mpicc您用来构建您要调试的MPI应用程序相同。默认情况下,Valgrind尝试 mpicc,但您可以使用configure-time选项指定一个不同的 --with-mpicc。目前,封装器只能使用mpicc基于GNU GCC或Intel C ++编译器的s来构建 。

检查配置脚本是否打印如下所示的行:

检查可用的MPI2兼容mpicc和mpi.h ...是的,mpicc

如果说... no,您的 mpicc编译和链接测试MPI2程序失败。

如果配置测试成功,继续与通常的方式 makemake install。最后的安装树应该包含 libmpiwrap-.so

汇编测试MPI程序(例如,MPI hello-world),并尝试:

LD_PRELOAD = $ prefix / lib / valgrind / libmpiwrap-  .so \
           mpirun [args] $ prefix / bin / valgrind ./hello

你应该看到类似于以下内容

valgrind MPI包装纸31901:适用于pid 31901
valgrind MPI包装器31901:尝试MPIWRAP_DEBUG =帮助可能的选项

对于组中的每个过程重复。如果没有看到这些,有一种构建/安装问题。

要包装的MPI函数被假定为具有soname匹配的ELF共享对象 libmpi.so*。至少对于Open MPI和Quadrics MPI来说,这是正确的,如果需要,可以轻松更改。

4.9.2。入门

像往常一样编译MPI应用程序,注意使用与mpicc您的Valgrind构建配置相同的链接。

使用以下基本方案在Valgrind上运行您的应用程序:

MPIWRAP_DEBUG = [wrapper-args] \
   LD_PRELOAD = $ prefix / lib / valgrind / libmpiwrap-  .so \
   mpirun [mpirun-args] \
   $ prefix / bin / valgrind [valgrind-args] \
   [应用程序] [app-args]

作为一种替代 LD_PRELOADING libmpiwrap-.so,你可以简单地把它如果需要链接到您的应用程序。这不应该以任何方式打扰您的应用程序的本机行为。

4.9.3。控制包装库

环境变量 MPIWRAP_DEBUG在启动时被查阅。默认行为是打印起始横幅

valgrind MPI包装器16386:适用于pid 16386
valgrind MPI包装器16386:尝试MPIWRAP_DEBUG =帮助可能的选项

然后比较安静。

您可以在其中列出逗号分隔的选项 MPIWRAP_DEBUG。这些是

  • verbose:显示所有包装的条目/出口。还显示额外的调试信息,如优秀的状态 MPI_Request期从半拉造成MPI_Irecv秒。

  • quiet:相反verbose,当打包机想要报告检测到的编程错误,或者在包装器发生灾难性故障的情况下,只打印任何东西。

  • warn:默认情况下,缺少适当的包装器的功能没有评论,只是默默地忽略。这将导致每个使用的展开功能打印一个警告,每个功能最多可以有三个警告。

  • strict:如果使用缺少包装器的功能,则打印错误消息并中止程序。

如果要使用Valgrind的XML输出工具(--xml=yes),您应该传入 quiet, MPIWRAP_DEBUG以便从包装器中除去任何无关的打印。

4.9.4。功能

所有MPI2功能除外 MPI_Wtick, MPI_Wtime并 MPI_Pcontrol有包装。前两个没有包装,因为它们返回一个 double,Valgrind的函数包装机制无法处理(但是可以很容易地扩展)。 MPI_Pcontrol不可包裹,因为它具有可变特性: int MPI_Pcontrol(const int level, ...)

大多数函数都使用一个默认的包装器来打包,除非是根据MPIWRAP_DEBUG上面列出的设置调用,否则它不会执行任何命令,也不会中止它。以下功能具有“真实”,有用的包装器:

PMPI_Send PMPI_Bsend PMPI_Ssend PMPI_Rsend

PMPI_Recv PMPI_Get_count

PMPI_Isend PMPI_Ibsend PMPI_Issend PMPI_Irsend

PMPI_Irecv
PMPI_Wait PMPI_Waitall
PMPI_Test PMPI_Testall

PMPI_Iprobe PMPI_Probe

PMPI_Cancel

PMPI_Sendrecv

PMPI_Type_commit PMPI_Type_free

PMPI_Pack PMPI_Unpack

PMPI_Bcast PMPI_Gather PMPI_Scatter PMPI_Alltoall
PMPI_Reduce PMPI_Allreduce PMPI_Op_create

PMPI_Comm_create PMPI_Comm_dup PMPI_Comm_free PMPI_Comm_rank PMPI_Comm_size

PMPI_Error_string
PMPI_Init PMPI_Initialized PMPI_Finalize

PMPI_Address列出的 几个功能 HAS_NO_WRAPPER。他们根本没有包装,因为没有什么值得检查,给出一个无操作的包装器会降低性能没有任何理由。

请注意,包装库本身本身可以产生大量对MPI实现的调用,特别是在步行复杂类型时。所谓的最常用的功能是 PMPI_Extent, PMPI_Type_get_envelope, PMPI_Type_get_contents,和 PMPI_Type_free

4.9.5。类型

支持MPI-1.1结构化类型,并正确运行。目前支持的组合是 MPI_COMBINER_NAMED, MPI_COMBINER_CONTIGUOUS, MPI_COMBINER_VECTOR, MPI_COMBINER_HVECTOR MPI_COMBINER_INDEXED, MPI_COMBINER_HINDEXED和 MPI_COMBINER_STRUCT。这应该涵盖所有MPI-1.1类型。该机制(功能 walk_type)应易于扩展以覆盖MPI2组合器。

MPI定义了一些命名结构化类型(MPI_FLOAT_INT, MPI_DOUBLE_INT, MPI_LONG_INT, MPI_2INT, MPI_SHORT_INT, MPI_LONG_DOUBLE_INT),其是对一些基本类型和C的int。不幸的是,MPI规范使得不可能查看这些类型并查看字段在哪里。因此,这些包装器假设类型被布置为struct { float val; int loc; }(for MPI_FLOAT_INT)等,并且相应地进行操作。至少对于Open MPI 1.0.2和Quadrics MPI来说,这似乎是正确的。

如果strict是指定的选项MPIWRAP_DEBUG,如果遇到未处理类型,应用程序将中止。否则,应用程序将打印一条警告消息并继续。

做了一些努力来在单次通过中标记/检查对应于数组数组的存储器范围。这对于性能很重要,因为Valgrind要求标记/检查任何范围,无论多么小,承载相当大的恒定成本。这种优化被施加到原始类型的阵列(double, float, int, longlong longshort, char,和long double上平台其中sizeof(long double) == 8)。对于所有其他类型的数组,包装器单独处理每个元素,因此可能会有非常大的性能成本。

4.9.6。写新的包装纸

在大多数情况下,包装很简单。唯一重要的复杂性是出现在非阻塞接收。

问题是MPI_Irecv 声明recv缓冲区并立即返回,为事务提供一个handle(MPI_Request)。稍后用户将必须轮询完成 MPI_Wait等等,并且当事务成功完成时,包装器必须绘制recv缓冲区。但是recv缓冲区细节没有呈现给 MPI_Wait- 只有句柄是。因此,库保持一个影子表,它将未完成的MPI_Requests与相应的缓冲区地址/计数/类型相关联。操作完成后,将搜索表格中相关联的地址/计数/类型信息,并相应地标记内存。

访问表受到(POSIX pthreads)锁的保护,以使库线程安全。

该表被分配 malloc并且从不 freed,所以它将显示在泄漏检查中。

写新的包装器应该是相当容易的。源文件是 mpi/libmpiwrap.c。如果可能,找到一个与要包装的行为类似行为的函数的现有包装,并将其用作起点。包装器按照与MPI 1.1规范相同的顺序进行组织,以辅助导航。添加包装器时,请记住在文件底部的长列表中注释掉默认包装器的定义(不要删除它,只是将其注释掉)。

4.9.7。使用包装器时期待什么?

包装器应该减少Memcheck在MPI应用程序上的错误错误率。因为封装是在MPI接口完成的,所以接口下面的MPI实现中仍然可能会有大量的错误报告。你能做的最好的就是试图压制他们。

您还可以发现输入端(缓冲区长度/定义)检查在您的MPI使用中发现错误,例如传递太短的缓冲区 MPI_Recv

未包装的功能可能会增加错误率。一种可能的方法是与运行 MPI_DEBUG含有 warn。这将显示您缺少正确的包装器但仍然使用的功能。然后你可以为他们写封装。

PMPI_Reduce当使用自定义(用户定义的)缩减功能时,一个已知的潜在的错误错误源 就是功能系列。在缩减操作中,每个节点都意图将数据发送到使用指定的缩减功能的“中心点”,将数据项合并成单个项目。因此,通常,数据在节点之间传递并馈送到缩减功能,但是包装库不能将传输的数据标记为初始化之前被递送到缩减功能,因为所有这些都发生在“内部” PMPI_Reduce。因此,您的缩减功能可能会显示误报。

你可能感兴趣的:(valgrind,valgrind)