我正在使用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-gcc工具链(uclibc v0.9.29)将闪存到运行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
和{{ 1}}将位于其中一个库中,它们似乎是动态链接的。我还对可执行文件执行了free
和readelf
,以确认对nm
和malloc
的引用是未定义的(这是动态链接的可执行文件的特征)。
此外,我尝试使用常见问题解答建议的free
选项启动Valgrind。
正如评论者和回答者所指出的,Valgrind依赖于LD_PRELOAD的使用。有人建议我的工具链不支持此功能。为了确认它,我跟着this example创建了一个简单的测试库并加载它(我用一个只返回42的函数替换--soname-synonyms=somalloc=NONE
)。测试工作正常,所以看起来我的目标支持LD_PRELOAD就好了。
我还会在rand()
命令中包含一些可能有用的信息。而不是一个巨大的转储,我已经削减了一些东西,只包括可能相关的东西。
readelf
答案 0 :(得分:10)
首先,让我们做一个真正的测试,看看是否有静态链接。
$ 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小时后看到1000件,在72小时后看到1,500件,这应该可以告诉您什么是“漏水”。但是,正如 haris 在评论中指出的那样,虽然这会导致内存没有显示为泄漏,但它并没有解释总堆使用情况'为零,因为它描述了所做的总分配和释放。
或许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);
}
答案 1 :(得分:5)
(在OP获得第一笔奖金后,问题本身已经发生了重大变化,添加了另一个答案)
根据我对您编辑的理解,您现在拥有:
答案 2 :(得分:1)
为了确认可执行文件没有静态链接,我运行了文件snmpd
你的问题很可能不是二进制是静态链接的(你现在知道它不是),但是malloc
和free
静态链接到它(也许你正在使用替代的malloc实现,例如tcmalloc
?)。
当您构建简单的测试用例(Valgrind正常工作)时,您可能没有使用与实际应用程序相同的链接命令行(和相同的库)。
无论如何,检查是非常简单的:
readelf -Ws snmpd | grep ' malloc'
如果显示UND
(即未定义),Valgrind可以毫无困难地拦截它。但有可能会显示FUNC GLOBAL DEFAULT ... malloc
,这意味着就valgrind而言,snmpd
与静态链接一样好。
假设我的猜测是正确的,请使用snmpd
标记重新链接-Wl,-y,malloc
。这将告诉您哪个库定义了您的malloc
。将其从链接中删除,找到并修复泄漏,然后确定是否值得为它带来麻烦。