这有点假设并且非常简化,但是......
假设一个程序将调用第三方编写的函数。这些当事方可以被认为是非敌对的,但不能被认为是“有能力的”。每个函数都会接受一些参数,有副作用并返回一个值。它们在不运行时没有状态。
目标是通过记录所有 malloc(等)并在函数退出后释放所有内容来确保它们不会导致内存泄漏。
这可能吗?这实用吗?
ps 对我来说重要的部分是确保没有分配持续存在,因此在不这样做的情况下消除内存泄漏的方法对我没有用。
这有点假设并且非常简化,但是......
假设一个程序将调用第三方编写的函数。这些当事方可以被认为是非敌对的,但不能被认为是“有能力的”。每个函数都会接受一些参数,有副作用并返回一个值。它们在不运行时没有状态。
目标是通过记录所有 malloc(等)并在函数退出后释放所有内容来确保它们不会导致内存泄漏。
这可能吗?这实用吗?
ps 对我来说重要的部分是确保没有分配持续存在,因此在不这样做的情况下消除内存泄漏的方法对我没有用。
您没有指定操作系统或环境,此答案假定 Linux、glibc 和 C。
您可以设置 __malloc_hook、__free_hook 和 __realloc_hook 指向将分别从 malloc()、realloc() 和 free() 调用的函数。有一个 __malloc_hook 手册页显示了原型。您可以在这些钩子中添加轨道分配,然后返回让 glibc 处理内存分配/释放。
听起来您想在第三方函数返回时释放任何实时分配。有一些方法可以让 gcc 使用 -finstrument-functions 在每个函数入口和退出时自动插入调用,但我认为这对于你想要做的事情是不优雅的。在调用这些第三方函数之一后,您可以让自己的代码调用内存跟踪库中的函数吗?然后,您可以检查是否有任何第三方功能尚未释放的分配。
首先,您必须为malloc()
和free()
和朋友提供入口点。因为这段代码已经编译(对吗?)你不能依赖#define
重定向。
然后,您可以以明显的方式实现这些,并通过将这些例程链接到这些模块来记录它们来自某个模块。
最快的方法根本不需要记录。 如果他们使用的内存量是有限的,为什么不预先分配他们将需要的所有“堆”并从中编写一个分配器呢?然后当它完成后,释放整个“堆”,你就完成了!如果它更复杂,你可以将这个想法扩展到多个堆。
如果您确实需要“记录”而不是制作自己的分配器,这里有一些想法。一,使用带有指针和内部链接的哈希表。另一种方法是在每个块前面分配额外的空间,并在那里放置你自己的结构,其中包含一个索引到你的“日志表”中,然后保留一个日志表条目的空闲列表(作为一个堆栈,以便获得一个空闲的或者放一个免费的是 O(1))。这需要更多内存,但应该很快。
实用吗?我认为是的,只要速度命中是可以接受的。
您可以在单独的进程中运行第三方函数,并在使用完库后关闭该进程。
比尝试记录 malloc 更好的解决方案可能是在调用函数时对函数进行沙箱化——让它们访问固定的内存段,然后在函数运行完成时释放该段。
无限制、无能的内存使用可能与恶意代码一样具有破坏性。
你不能强迫他们在堆栈上分配所有内存吗?这样就可以保证在函数退出后将其释放。
过去,我用 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
由于您担心内存泄漏并谈论 malloc/free,因此我假设您使用 C。我还根据您的问题假设您无权访问第三方库的源代码。
我唯一能想到的就是在调用前后检查你的应用程序的内存消耗,如果它们不同,则记录错误消息,并说服第三方供应商修复你发现的任何泄漏。
如果您有闲钱,请考虑使用 Purify 来跟踪问题。它可以创造奇迹,并且不需要源代码或重新编译。还有其他更便宜的调试 malloc 库可用。电子围栏是我记得的一个名字。也就是说,Denton Gentry 提到的调试钩子似乎也很有趣。
如果你对 Purify 太穷了,试试 Valgrind。它比 6 年前好多了,而且比 Purify 更容易深入。
Microsoft Windows 提供了(如果您需要 POSIX,请使用 SUA),很可能是当今任何发布操作系统的最先进的堆+(已知使用堆的其他 api)基础架构。
__malloc() 调试钩子和相关的 CRT 调试接口非常适合您拥有测试源代码的情况,但是它们通常会错过标准库或其他链接代码的分配。这是意料之中的,因为它们是 Visual Studio 堆调试基础结构。
gflags是一套非常全面和详细的调试功能,多年来一直包含在 Windows 中。具有仅用于源代码和二进制用例的高级功能(因为它是 OS 堆调试基础架构)。
它可以记录所有堆用户的所有堆修改入口点的完整堆栈跟踪(在后处理操作中重新分页符号信息),如果需要,可以串行记录。此外,它可能会用病态的情况修改堆,这可能会对齐数据的分配,以便最佳地分配 VM 系统提供的页面保护(即在页面末尾分配您请求的堆块,因此即使是单字节溢出在溢出时检测到。
umdh 是一个可以帮助评估各种检查点状态的工具,但是数据在目标执行过程中不断积累 o 它不是传统上下文中简单的检查点调试停止。另外,警告,最后我至少检查了存储堆栈信息的循环缓冲区的总大小,对于每个请求来说有点小(64k 条目(条目+堆栈)),因此您可能需要为重堆用户快速转储. 还有其他方法可以访问这些数据,但 umdh 相当简单。
注意有两种模式;
模式 2,umdh [-d] {File1} [File2] [-f:Filename]
我不知道选择在 -p:foo 参数说明符和参数的裸排序之间交替的开发人员是什么精神错乱,但它可能会有点混乱。
调试 sdk 与许多其他工具一起使用,memsnap是一个显然专注于内存泄漏等的工具,但我没有使用它,你的里程可能会有所不同。
为 UI 模式执行不带参数的 gflags,+arg 和 /args 也是不同的使用“模式”。
在 Linux 上,我已经成功地用来mtrace(3)
记录分配和释放。它的用法很简单
mtrace()
在您需要开始跟踪时调用(例如在顶部main()
),MALLOC_TRACE
为应保存跟踪并运行程序的文件路径。之后,输出文件将包含类似这样的内容(从中间摘录以显示分配失败):
@ /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