提问人:BCS 提问时间:9/9/2008 最后编辑:BCS 更新时间:2/17/2020 访问量:2128
如何记录 mallocs
How to log mallocs
问:
这有点假设和严重简化,但是......
假设一个程序将调用由第三方编写的函数。这些当事方可以被认为是非敌对的,但不能被认为是“有能力的”。每个函数都会接受一些参数,有副作用并返回一个值。它们在不运行时没有状态。
目的是通过记录所有 malloc(等)然后在函数退出后释放所有内容来确保它们不会导致内存泄漏。
这可能吗?这实用吗?
p.s. 对我来说,重要的部分是确保没有分配持续存在,因此在不这样做的情况下消除内存泄漏的方法对我没有用。
答:
你不能强迫他们在堆栈上分配所有内存吗?这样,在函数退出后,它就可以被释放。
比尝试记录 malloc 更好的解决方案可能是在调用函数时对函数进行沙盒处理 - 允许它们访问固定的内存段,然后在函数完成运行时释放该段。
不受限制、不称职的内存使用可能与恶意代码一样具有破坏性。
您可以在单独的进程中运行第三方函数,并在使用完库后关闭该进程。
首先,您必须提供 和 和 朋友的入口点。因为这段代码已经编译好了(对吧?),所以你不能依赖它来重定向。malloc()
free()
#define
然后,您可以以显而易见的方式实现这些例程,并通过将这些例程链接到这些模块来记录它们来自某个模块。
最快的方法根本不涉及日志记录。如果他们使用的内存量是有限制的,为什么不预先分配他们需要的所有“堆”,并从中写出一个分配器呢?然后当它完成时,释放整个“堆”,你就完成了!你可以将这个想法扩展到多个堆,如果它比这更复杂。
如果你真的需要“记录”而不是创建自己的分配器,这里有一些想法。第一,使用带有指针和内部链接的哈希表。另一种方法是在每个块前面分配额外的空间,并将你自己的结构放在那里,比如说,在你的“日志表”中包含一个索引,然后保留一个日志表条目的空闲列表(作为一个堆栈,所以得到一个空闲的或放回一个空闲的就是 O(1))。这需要更多的内存,但应该很快。
实用吗?我认为是,只要速度命中是可以接受的。
既然你担心内存泄漏并谈论 malloc/free,我假设你是 C。我还根据您的问题假设您无权访问第三方库的源代码。
我唯一能想到的就是在调用之前和之后检查应用程序的内存消耗,如果它们不同,则记录错误消息,并说服第三方供应商修复您发现的任何泄漏。
您没有指定操作系统或环境,此答案假定 Linux、glibc 和 C。
您可以将 __malloc_hook、__free_hook 和 __realloc_hook 设置为指向将分别从 malloc()、realloc() 和 free() 调用的函数。有一个显示原型的__malloc_hook手册页。您可以在这些钩子中添加轨道分配,然后返回让 glibc 处理内存分配/解除分配。
听起来您希望在第三方函数返回时释放任何实时分配。有一些方法可以让 gcc 使用 -finstrument-functions 在每个函数入口和出口处自动插入调用,但我认为这对于您尝试做的事情来说是不优雅的。在调用这些第三方函数之一后,是否可以让自己的代码调用内存跟踪库中的函数?然后,您可以检查是否有第三方函数尚未释放的任何分配。
过去,我用 C 语言编写了一个软件库,它有一个内存管理子系统,它能够记录分配和释放,并手动匹配每个分配和释放。这在尝试查找内存泄漏时有一些用处,但使用起来既困难又耗时。日志的数量是压倒性的,需要花费大量时间来理解日志。
话虽如此,如果您的第三方库有大量分配,那么通过日志记录来跟踪它很可能是不切实际的。如果你在 Windows 环境中运行,我建议使用 Purify[1] 或 BoundsChecker[2] 等工具,它们应该能够检测第三方库中的泄漏。对该工具的投资应该在节省的时间中收回成本。
[1]:http://www-01.ibm.com/software/awdtools/purify/ 净化
[2]:http://www.compuware.com/products/devpartner/visualc.htm BoundsChecker
如果您有闲钱,请考虑使用 Purify 来跟踪问题。它创造了奇迹,并且不需要源代码或重新编译。还有其他更便宜的调试 malloc 库可用。电篱笆是我记得的一个名字。也就是说,Denton Gentry 提到的调试钩子看起来也很有趣。
如果你太穷了,无法净化,试试Valgrind。它比 6 年前好得多,而且比 Purify 更容易深入研究。
评论
Microsoft Windows提供了(如果你需要POSIX,请使用SUA),很可能是当今任何交付操作系统中最先进的堆+(已知使用堆的其他api)基础设施。
__malloc() 调试钩子和关联的 CRT 调试接口非常适合您拥有测试源代码的情况,但是它们经常会错过标准库或其他链接代码的分配。这是意料之中的,因为它们是 Visual Studio 堆调试基础结构。
gflags 是一组非常全面和详细的调试功能,多年来一直包含在 Windows 中。为仅源代码和二进制用例提供高级功能(因为它是操作系统堆调试基础结构)。
如果需要,它可以记录所有堆用户的完整堆栈跟踪(在后处理操作中重新标记符号信息),用于所有堆修改入口点。此外,它可能会使用病理情况修改堆,这些情况可能会调整数据分配,以便以最佳方式分配 VM 系统提供的页面保护(即在页面末尾分配您请求的堆块,因此在溢出时甚至检测到单个字节溢出。
UMDH 是一种工具,可以帮助评估各个检查点的状态,但是在目标执行过程中,数据会不断累积,或者在传统上下文中它不是简单的检查点调试停止。另外,警告,至少上次我检查过,存储堆栈信息的循环缓冲区的总大小,对于每个请求都有些小(64k 个条目(条目 + 堆栈)),因此您可能需要为大量堆用户快速转储。还有其他方法可以访问这些数据,但 umdh 相当简单。
注意:有 2 种模式;
- 模式 1, umdh {-p:Process-id|-pn:ProcessName} [-f:文件名] [-g]
模式 2,umdh [-d] {File1} [File2] [-f:文件名]
我不知道选择在 -p:foo 参数说明符和参数的裸露排序之间交替的开发人员是什么疯狂,但这可能会有点令人困惑。
调试 sdk 可以与许多其他工具一起使用,memsnap 是一个显然专注于内存 leask 等的工具,但我没有使用过它,您的里程可能会有所不同。
在UI模式下执行不带参数的gflags,+args和/args也是不同的使用“模式”。
On Linux I've successfully used mtrace(3)
to log allocations and freeings. Its usage is as simple as
- Modify your program to call when you need to begin tracing (e.g. at the top of ),
mtrace()
main()
- Set environment variable to the file path where the trace should be saved and run the program.
MALLOC_TRACE
After that the output file will contain something like this (excerpt from the middle to show a failed allocation):
@ /usr/lib/tls/libnvidia-tls.so.390.116:[0xf44b795c] + 0x99e5e20 0x49
@ /opt/gcc-7/lib/libstdc++.so.6:(_ZdlPv+0x18)[0xf6a80f78] - 0x99beba0
@ /usr/lib/tls/libnvidia-tls.so.390.116:[0xf44b795c] + 0x9a23ec0 0x10
@ /opt/gcc-7/lib/libstdc++.so.6:(_ZdlPv+0x18)[0xf6a80f78] - 0x9a23ec0
@ /opt/Xorg/lib/video-libs/libGL.so.1:[0xf668ee49] + 0x99c67c0 0x8
@ /opt/Xorg/lib/video-libs/libGL.so.1:[0xf668f14f] - 0x99c67c0
@ /opt/Xorg/lib/video-libs/libGL.so.1:[0xf668ee49] + (nil) 0x30000000
@ /lib/libc.so.6:[0xf677f8eb] + 0x99c21f0 0x158
@ /lib/libc.so.6:(_IO_file_doallocate+0x91)[0xf677ee61] + 0xbfb00480 0x400
@ /lib/libc.so.6:(_IO_setb+0x59)[0xf678d7f9] - 0xbfb00480
评论