MIPS 上的 Valgrind 报告没有堆使用

Posted

技术标签:

【中文标题】MIPS 上的 Valgrind 报告没有堆使用【英文标题】:Valgrind on MIPS Reports no Heap Usage 【发布时间】:2014-11-25 08:33:47 【问题描述】:

我正在使用 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 仍然能够发现“无效写入”错误。

正在编译有问题的应用程序以及其他软件包,其中包含用于 MIPS 处理器 (uclibc v0.9.29) 的 uclibc-gcc 工具链,以闪存到运行 busybox (v1.17.2) linux shell 的嵌入式设备上.我直接在设备上运行 valgrind。我在启动 Valgrind 时使用以下选项:

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

基本上,即使我使用了堆,Valgrind 也不会检测到任何堆使用情况。为什么会这样?我的任何假设(如下)是错误的吗?


我的尝试

简单的测试程序

我从 Valgrind quick-start tutorial 编译了简单的测试程序(使用与上述应用程序相同的目标和工具链),以查看 Valgrind 是否会检测到泄漏。最终输出与上面相同:没有使用堆。

链接问题?

Valgrind 文档在 their FAQ 上有以下说明:

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

上面听起来和我的问题一模一样,所以我检查了它是否动态链接到包含mallocfree 的C 库。我使用了 uclibc 工具链的自定义 ldd 可执行文件 (I can't use the native 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 的引用是未定义的(这是动态链接的可执行文件的特征)。

此外,我尝试按照常见问题解答的建议使用 --soname-synonyms=somalloc=NONE 选项启动 Valgrind。

LD_PRELOAD 支持?

正如评论者和回答者所指出的,Valgrind 取决于 LD_PRELOAD 的使用。有人建议我的工具链不支持此功能。为了确认确实如此,我按照this example 创建了一个简单的测试库并加载它(我将rand() 替换为只返回42 的函数)。测试成功了,看来我的目标支持 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

【问题讨论】:

您是否提供了 --trace-children=yes 选项?因为如果你使用 exec,你必须把那个选项 @yakoudbz 我最初没有使用该选项,但我现在使用了,结果没有改变。谢谢你的建议。我已经编辑了帖子以显示我正在使用的选项。 我记得一个类似的问题,即 uClibC 没有使用 LD_PRELOAD 支持构建,而 Valgrind 依赖于它。你能测试一下这是不是你的问题吗?如果是这样,在构建 uClibC 时启用 LD_PRELOAD 支持应该可以解决问题。 一个可能的原因可能是重定向机制没有将 malloc 调用重定向到 valgrind 拦截,因为 uclibc soname 不是预期的名称。如果是这种情况,请使用 --soname-synonyms=somalloc=xxxxxx 其中 xxxxxx 是 uclibc 库的 soname 您是否尝试创建一个您更确定会造成泄漏的虚拟程序,只是为了验证 Valgrind 也无法看到这一点?我注意到 Valgrind 对 MIP32 的支持是非常新的,也许有问题。但是,我确实期望 Valgrind 的质量,所以这似乎不太可能。 【参考方案1】:

首先,让我们做一个真实的测试,看看是否有静态链接。

$ 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

至于可能出现的问题,我可以提出四点建议:

    也许它没有链接到普通的libc,而是链接到其他一些 C 库(例如 uclibc)或其他 valgrind 不期望的东西。上面的测试将准确地向您展示它所链接的内容。为了使valgrind 工作,它使用LD_PRELOAD 包装malloc()free() 函数(一般函数包装here 的说明)。如果您的libc 替代品不支持LD_PRELOAD 或(不知何故)C 库的malloc()free() 根本没有被使用(使用这些名称),那么valgrind 将不起作用。也许您可以包含构建应用程序时使用的链接行。

    它正在泄漏,但它没有使用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 小时后看到 1,000 件,在 72 小时后看到 1,500 件,这应该让您知道什么是“泄漏”。然而,正如 haris 在 cmets 中指出的那样,虽然这会导致内存不显示为泄漏,但它并不能解释“总堆使用量”为零,因为这描述了总分配制造和释放。

    也许valgrind 无法在您的平台上运行。如果您构建一个像下面这样的非常简单的程序,并在您的平台上运行valgrind,会发生什么?如果这不起作用,您需要找出为什么valgrind 运行不正常。请注意,MIPS 上的 valgrind 是相当新的。 Here 是一个电子邮件线程,使用 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);

【讨论】:

It isn't leaking in the strict sense, but it is leaking to all intents and purposes. For instance, perhaps it has datastructure (like a cache), which grows over time. 任何随时间增长的东西(也许增长取决于应用程序的活动)都应该使用动态内存分配,然后这些分配和空闲应该出现在 valgrind 输出中。如果我错了,请纠正我。 @haris valgrind 不显示退出时释放的分配,仅显示退出前未释放的分配。 我尝试了一个简单的malloc()free(),在堆摘要中它来自total heap usage: 1 allocs, 1 frees, 4 bytes allocated @haris - 啊 - 在“总堆使用量”行 - 是的,你是对的。我会调整我的答案。 1. 可执行文件动态链接到 lic.so 和 uclibc.so。我相信,但我不是 100% 确定,我的 uclibc 构建有 LD_PRELOAD 支持......但我也尝试为 valgrind 设置 soname-synonyms 选项,但这没有用。 2. 代码中肯定有malloc 调用,因此即使泄漏来自mmap 或其他分配方法,堆也不应为空。 3.同上,堆不能为空。【参考方案2】:

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

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

    valgrind自己的测试程序复制了这个问题 确认测试程序二进制动态链接到uclibc 已确认 LD_PRELOAD 正在您的系统上运行 确认(如果仅使用测试程序)这不是来自其他库的符号干扰

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

因此,我建议您使用here 描述的机制报告错误。或许可以省略有关您复杂应用程序的信息,只指出简单的测试程序不起作用。如果您还没有,您可以尝试here 中描述的用户邮件列表。

【讨论】:

感谢您的回答。我发现我更可能误解了我的一个先入之见,而不是存在根本错误;尽管如此,我的邮件列表上确实有一条未决消息,如果这未能发现我的任何误解,我将尝试提交错误报告。【参考方案3】:

为了确认可执行文件不是静态链接的,我运行了文件 snmpd

您的问题很可能不是二进制文件是静态链接的(您现在知道它不是),而是 mallocfree 静态链接到其中(也许您正在使用替代的 malloc 实现,例如 @ 987654323@?)。

当您构建简单的测试用例(Valgrind 在其上正常工作)时,您可能没有使用与实际应用程序相同的链接命令行(和相同的库)。

无论如何,检查是微不足道的:

readelf -Ws snmpd | grep ' malloc'

如果这显示UND(即未定义),Valgrind 应该可以轻松拦截它。但它可能会显示 FUNC GLOBAL DEFAULT ... malloc,这意味着就 valgrind 而言,您的 snmpd与静态链接一样好

假设我的猜测是正确的,重新链接 snmpd-Wl,-y,malloc 标志。这将告诉您哪个库定义了您的malloc。从链接中删除它,找到并修复泄漏,然后决定拥有该库是否值得它给您带来的麻烦。

【讨论】:

输出显示“GLOBAL DEFAULT”和“UND”。 97: 00404690 0 FUNC GLOBAL DEFAULT UND malloc89: 00404690 0 FUNC GLOBAL DEFAULT UND malloc 后续:我运行了nm 命令,对 malloc 和 free 的引用被标记为“U”,所以它们是未定义的。 @WoodrowBarlow 你也可以在简单的测试用例上运行readelf 吗? readelf 显示非0 地址和UNDmalloc 让我感到惊讶。 没错,输出基本相同:14: 004007f0 0 FUNC GLOBAL DEFAULT UND malloc54: 004007f0 0 FUNC GLOBAL DEFAULT UND malloc。奇怪的输出可能是因为我在 x86 机器上使用 readelf,但可执行文件是 MIPS 可执行文件。

以上是关于MIPS 上的 Valgrind 报告没有堆使用的主要内容,如果未能解决你的问题,请参考以下文章

Valgrind报告从stdin的getline后释放的指针上的内存泄漏

Valgrind 报告内存肯定丢失而没有错误

valgrind 使用 std::string 报告无效读取

实验报告实验一 MIPS指令系统和MIPS体系结构

Valgrind 内存泄漏报告中的时间戳不正确

没有dpkg-architecture时如何区分linux上的mips cpu类型?