Valgrind在MIPS上报告没有堆使用

20

我正在使用valgrind(v3.10.0)来查找一个复杂应用程序(net-snmp的大规模修改构建的一部分)中的内存泄漏。我确定存在泄漏(应用程序的内存占用呈线性无限增长),但在终止时,valgrind总是报告以下内容。

==1139== HEAP SUMMARY:
==1139==     in use at exit: 0 bytes in 0 blocks
==1139==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==1139== 
==1139== All heap blocks were freed -- no leaks are possible

总堆使用量不能为零--应用程序中有许多许多次对mallocfree的调用。 Valgrind仍然可以找到“无效写入”错误。

所涉及的应用程序正在使用uclibc-gcc工具链(uclibc v0.9.29)与其他软件包一起编译,以便在运行busybox(v1.17.2)Linux shell的嵌入式设备上刷入MIPS处理器。我直接在设备上运行Valgrind。启动Valgrind时,我使用以下选项:

--tool=memcheck --leak-check=full --undef-value-errors=no --trace-children=yes

基本上,Valgrind没有检测到任何堆使用情况,尽管我已经使用了堆。这可能是为什么?我的以下假设是否有误?


我尝试过的方法

简单测试程序

我编译了Valgrind 快速入门教程中提供的简单测试程序(使用与上面应用程序相同的目标和工具链),以查看Valgrind是否会检测到泄漏。最终输出与上面相同:没有堆使用。

链接问题?

Valgrind文档在FAQ中有以下说明:

如果您的程序是静态链接的,则大多数Valgrind工具只有在能够替换某些函数(例如malloc)为其自己的版本时才能正常工作。默认情况下,不会替换静态链接的malloc函数。一个关键的指标是,如果Memcheck说“所有堆块都被释放 - 不可能有泄漏”。

以上听起来就像我的问题一样,所以我检查了它是否动态链接到包含mallocfree的C库。我使用了uclibc工具链的自定义ldd可执行文件(我不能使用本地linux的ldd),输出包括以下行:

libc.so.0 => not found (0x00000000)
/lib/ld-uClibc.so.0 => /lib/ld-uClibc.so.0 (0x00000000)

因为我正在x86主机设备上运行,而mips目标设备没有ldd可执行文件,所以它们找不到。

根据我的理解,mallocfree将在其中一个库中,并且它们似乎是动态链接的。我还对可执行文件进行了readelfnm操作,确认对mallocfree的引用未定义(这是动态链接可执行文件的特征)。

此外,我尝试使用FAQ建议的--soname-synonyms=somalloc=NONE选项启动Valgrind。

正如评论员和回答者所指出的,Valgrind依赖于LD_PRELOAD的使用。有人建议我的工具链不支持此功能。为了确认它是否支持,我按照这个示例创建了一个简单的测试库并加载它(我用一个只返回42的函数代替了rand())。测试成功,因此看来我的目标完全支持LD_PRELOAD。

我还将包括一些可能有用的readelf命令的信息。为了避免大量输出,我将它们削减到仅包含可能相关的部分。

Dynamic section
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libnetsnmpagent.so.30]
 0x00000001 (NEEDED)                     Shared library: [libnetsnmpmibs.so.30]
 0x00000001 (NEEDED)                     Shared library: [libnetsnmp.so.30]
 0x00000001 (NEEDED)                     Shared library: [libgcc_s.so.1]
 0x00000001 (NEEDED)                     Shared library: [libc.so.0]
 0x0000000f (RPATH)                      Library rpath: [//lib]

Symbol table '.dynsym'
   Num:    Value  Size Type    Bind   Vis      Ndx Name
    27: 00404a40     0 FUNC    GLOBAL DEFAULT  UND free
    97: 00404690     0 FUNC    GLOBAL DEFAULT  UND malloc

2
你是否使用了 --trace-children=yes 选项?因为如果你使用了 exec,你必须加上那个选项。 - yakoudbz
@yakoudbz 那是可能的,但我认为堆使用量不应该为零。 - Woodrow Barlow
1
我记得有一个类似的问题,即 uClibC 没有使用 LD_PRELOAD 支持进行构建,而 Valgrind 依赖于此。您能否测试一下是否是这个问题?如果是这样,在构建 uClibC 时启用 LD_PRELOAD 支持应该就可以解决问题了。 - Michael Foukarakis
1
可能的原因是重定向机制没有将malloc调用重定向到valgrind拦截,这是由于uclibc soname不是预期的名称。如果是这种情况,请使用--soname-synonyms=somalloc=xxxxxx,其中xxxxxx是uclibc库的soname。 - phd
2
你是否尝试创建一个虚拟程序,以确保Valgrind无法检测到它,从而验证其是否会导致内存泄漏?我注意到Valgrind对MIP32的支持非常新,可能存在问题。然而,我仍然期望Valgrind有很高的质量,所以这种情况似乎不太可能发生。 - unwind
显示剩余19条评论
3个回答

11

首先,让我们进行一项真正的测试,以确定某物是否是静态链接的。

$ ldd -v /bin/true
    linux-vdso.so.1 =>  (0x00007fffdc502000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0731e11000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f07321ec000)

    Version information:
    /bin/true:
        libc.so.6 (GLIBC_2.3) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.3.4) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.14) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.4) => /lib/x86_64-linux-gnu/libc.so.6
        libc.so.6 (GLIBC_2.2.5) => /lib/x86_64-linux-gnu/libc.so.6
    /lib/x86_64-linux-gnu/libc.so.6:
        ld-linux-x86-64.so.2 (GLIBC_2.3) => /lib64/ld-linux-x86-64.so.2
        ld-linux-x86-64.so.2 (GLIBC_PRIVATE) => /lib64/ld-linux-x86-64.so.2

输出的第二行显示它动态链接到libc,这是包含malloc的内容。

至于可能出错的原因,我可以提出四点建议:

  1. 也许它没有链接到普通的libc,而是链接到其他C库(例如uclibc)或者valgrind没有预期的东西。上面的测试将向您展示它究竟链接到了什么。为了让valgrind工作,它使用LD_PRELOAD来包装malloc()free()函数(有关一般函数包装的说明在此处)。如果您的libc替代品不支持LD_PRELOAD或者(以某种方式)根本未使用C库的malloc()free()函数(以这些名称),那么valgrind将无法工作。也许您可以在构建应用程序时包含链接命令。

  2. 它正在泄漏,但它并未使用malloc()分配内存。例如,它可能(不太可能)自己调用brk(),或者(更有可能)使用mmap分配内存。您可以使用此方法找出(这是cat本身的转储)。

.

$ cat /proc/PIDNUMBERHERE/maps
00400000-0040b000 r-xp 00000000 08:01 805303                             /bin/cat
0060a000-0060b000 r--p 0000a000 08:01 805303                             /bin/cat
0060b000-0060c000 rw-p 0000b000 08:01 805303                             /bin/cat
02039000-0205a000 rw-p 00000000 00:00 0                                  [heap]
7fbc8f418000-7fbc8f6e4000 r--p 00000000 08:01 1179774                    /usr/lib/locale/locale-archive
7fbc8f6e4000-7fbc8f899000 r-xp 00000000 08:01 1573024                    /lib/x86_64-linux-gnu/libc-2.15.so
7fbc8f899000-7fbc8fa98000 ---p 001b5000 08:01 1573024                    /lib/x86_64-linux-gnu/libc-2.15.so
7fbc8fa98000-7fbc8fa9c000 r--p 001b4000 08:01 1573024                    /lib/x86_64-linux-gnu/libc-2.15.so
7fbc8fa9c000-7fbc8fa9e000 rw-p 001b8000 08:01 1573024                    /lib/x86_64-linux-gnu/libc-2.15.so
7fbc8fa9e000-7fbc8faa3000 rw-p 00000000 00:00 0
7fbc8faa3000-7fbc8fac5000 r-xp 00000000 08:01 1594541                    /lib/x86_64-linux-gnu/ld-2.15.so
7fbc8fca6000-7fbc8fca9000 rw-p 00000000 00:00 0
7fbc8fcc3000-7fbc8fcc5000 rw-p 00000000 00:00 0
7fbc8fcc5000-7fbc8fcc6000 r--p 00022000 08:01 1594541                    /lib/x86_64-linux-gnu/ld-2.15.so
7fbc8fcc6000-7fbc8fcc8000 rw-p 00023000 08:01 1594541                    /lib/x86_64-linux-gnu/ld-2.15.so
7fffe1674000-7fffe1695000 rw-p 00000000 00:00 0                          [stack]
7fffe178d000-7fffe178f000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

注意观察 [heap] 的结束地址是否实际上在增长,或者你是否看到了额外的 mmap 条目。另一个判断 valgrind 是否工作良好的好指标是向进程发送 SIGSEGV 或类似信号,看看退出时是否看到正在使用堆。

它严格意义上不是泄漏,但实质上对所有意图都是泄漏。例如,可能有一种数据结构(如缓存),随着时间的推移而增长。在退出时,程序(正确地)释放缓存中的所有条目。因此,在退出时,堆上没有使用任何内容。在这种情况下,你需要知道什么在增长。这是一个更难的命题。我会使用杀死程序(上述)的技术,捕获输出并进行后处理。如果24小时后看到500个东西,48小时后看到1000个东西,72小时后看到1500个东西,那就应该能够说明发生了什么“泄漏”。然而,正如评论中的 haris 指出的那样,尽管这将导致内存不被显示为泄漏,但它并不能解释“总堆使用量”为什么为零,因为它描述的是进行的总分配和释放。

也许 valgrind 在你的平台上只是不起作用。如果您构建一个非常简单的程序,如下面的程序,并在您的平台上运行 valgrind,会发生什么?如果这不起作用,你需要找出为什么 valgrind 没有正常运行。请注意,MIPS 上的 valgrind 是相当新的。 这里 是一个电子邮件线程,一个具有 MIPS 和 uclibc 的开发人员发现 valgrind 没有报告任何分配。他的解决方案是将 ntpl 替换为 linuxthreads

#include <stdio.h>
#include <stdlib.h>
int
main (int argc, char **argv)
{
  void *p = malloc (100);       /* does not leak */
  void *q = malloc (100);       /* leaks */
  free (p);
  exit (0);
}

它严格意义上来说并不泄漏,但从所有意义上来看都是在泄漏。例如,它可能具有数据结构(如高速缓存),随着时间的推移而增长。任何随时间增长的东西(可能取决于应用程序的活动)都应该使用动态内存分配,那么这些分配和释放应该出现在valgrind输出中。如果我说错了,请纠正我。 - Haris
@haris valgrind 不会显示在退出时被释放的分配,只会显示在退出前未被释放的分配。 - abligh
  1. 可执行文件动态链接到lic.so和uclibc.so。我相信,但不确定,我的uclibc构建具有LD_PRELOAD支持...但我也尝试了为valgrind设置soname-synonyms选项,但那没起作用。
  2. 代码中肯定有'malloc'调用,因此即使泄漏来自'mmap'或其他分配方法,堆也不应该为空。
  3. 与之前相同,堆不应该为空。
- Woodrow Barlow
  1. 我已经构建了一个测试程序,我构建了Valgrind在其快速入门页面上提供的示例。它没有检测到任何堆使用或泄漏。我目前正在查看您链接的电子邮件线程,以确定它是否仍适用于我正在运行的valgrind版本,并确定是否可以使用linuxthreads而不是ntpl。非常感谢您在这里提供了异常详细的答案,它对我排除问题和建议一些新的解决方案非常有帮助。
- Woodrow Barlow
如果您同时链接到libc.souclibc.so,那可能就是问题所在了,因为valgrind只会覆盖一个malloc()实现,而链接两个会使其混淆。我怀疑这可能会让不止valgrind感到困惑!@WoodrowBarlow - abligh
显示剩余2条评论

5
(在OP授予第一个赏金后,我又添加了一个答案,因为问题本身发生了很大变化)

根据我对您的编辑的理解,您现在已经:

  1. 使用valgrind自己的测试程序复制了问题
  2. 确认测试程序二进制文件与uclibc动态链接
  3. 确认LD_PRELOAD在您的系统上运行正常
  4. 通过测试程序(即使只是使用测试程序)确认这不是来自另一个库的符号干扰

对我来说,这表明valgrind存在bug或与您的工具链不兼容。我发现有参考资料表明它应该与您的工具链一起使用,所以这意味着无论如何都存在bug。

因此,我建议您使用此处描述的机制报告错误。也许省略关于您复杂应用程序的部分,只指出简单测试程序不起作用。如果您还没有尝试,请按此处描述的用户邮件列表。


谢谢你的回答。我认为我有可能误解了我的某些先入之见,而不是存在根本性的错误;尽管如此,我已经在邮件列表上发了一条消息,如果那也无法揭示我任何的误解,我将尝试提交一个错误报告。 - Woodrow Barlow

1
为了确认可执行文件未静态链接,我运行了命令“file snmpd”。
你的问题很可能不是二进制文件被静态链接了(现在你知道它没有),而是其中的“malloc”和“free”被静态链接了(也许你正在使用其他的内存分配实现,比如“tcmalloc”?)。
当你构建简单的测试用例(Valgrind在其上正常工作)时,你可能没有使用与真实应用程序相同的链接命令行(和相同的库)。
无论如何,检查起来都非常简单:
readelf -Ws snmpd | grep ' malloc'

如果它显示为UND(即未定义),则Valgrind应该没有拦截它的问题。但是很有可能它显示为FUNC GLOBAL DEFAULT ... malloc,这意味着就Valgrind而言,snmpd与静态链接一样好。
假设我的猜测是正确的,请使用-Wl,-y,malloc标志重新链接snmpd。这将告诉您哪个库定义了您的malloc。从链接中删除它,找到和修复泄漏问题,然后决定是否值得继续使用那个库。

输出显示了"GLOBAL DEFAULT"和"UND"。97: 00404690 0 FUNC GLOBAL DEFAULT UND malloc89: 00404690 0 FUNC GLOBAL DEFAULT UND malloc - Woodrow Barlow
跟进:我执行了“nm”命令,发现malloc和free的引用被标记为“U”,说明它们是未定义的。 - Woodrow Barlow
@WoodrowBarlow,你能否在简单测试案例上运行readelfreadelf显示malloc既有非0地址又有UND是令我感到惊讶的。 - Employed Russian
当然,输出基本相同:14: 004007f0 0 FUNC GLOBAL DEFAULT UND malloc54: 004007f0 0 FUNC GLOBAL DEFAULT UND malloc。奇怪的输出可能是因为我在 x86 机器上使用 readelf,但可执行文件是 MIPS 可执行文件。 - Woodrow Barlow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接