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
总堆使用量不能为零——在整个应用程序中有很多很多对malloc
和free
的调用。 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 是否说“所有堆块都已释放 - 没有泄漏是可能的”。
上面听起来和我的问题一模一样,所以我检查了它是否动态链接到包含malloc
和free
的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 可执行文件。)根据我的理解,malloc
和 free
将在这些库之一中,它们似乎是动态链接的。我还在可执行文件上执行了readelf
和nm
,以确认对malloc
和free
的引用是未定义的(这是动态链接的可执行文件的特征)。
此外,我尝试按照常见问题解答的建议使用 --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
您的问题很可能不是二进制文件是静态链接的(您现在知道它不是),而是 malloc
和 free
静态链接到其中(也许您正在使用替代的 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 malloc
和 89: 00404690 0 FUNC GLOBAL DEFAULT UND malloc
后续:我运行了nm
命令,对 malloc 和 free 的引用被标记为“U”,所以它们是未定义的。
@WoodrowBarlow 你也可以在简单的测试用例上运行readelf
吗? readelf
显示非0
地址和UND
的malloc
让我感到惊讶。
没错,输出基本相同:14: 004007f0 0 FUNC GLOBAL DEFAULT UND malloc
和54: 004007f0 0 FUNC GLOBAL DEFAULT UND malloc
。奇怪的输出可能是因为我在 x86 机器上使用 readelf,但可执行文件是 MIPS 可执行文件。以上是关于MIPS 上的 Valgrind 报告没有堆使用的主要内容,如果未能解决你的问题,请参考以下文章
Valgrind报告从stdin的getline后释放的指针上的内存泄漏