内存泄漏检测器工作原理

Memory Leak Detectors Working Principle

内存泄漏检测器的实际工作原理是什么?一般而言,基本概念是什么?可以用C++作为语言来解释这个。

检漏仪有几种不同的工作方式。您可以将 mallocfree 的实现替换为可以在分配期间跟踪更多信息并且不关心性能的实现。这类似于 dmalloc 的工作方式。一般来说,任何被 malloc 但未被 free 的地址都会被泄露。

基本实现实际上非常简单。您只需维护每个分配及其行号的查找 table,并在释放时删除该条目。然后当程序完成时,您可以列出所有泄漏的内存。困难的部分是确定应该在何时何地释放分配。当有多个指针指向同一个地址时,这就更难了。

实际上,您可能需要的不仅仅是单个行号,而是丢失分配的堆栈跟踪。

另一种方法是 valgrind 的工作原理,它实现了一个完整的虚拟机来跟踪地址和内存引用以及相关的簿记。 valgrind 方法更昂贵,但也更有效,因为它还可以告诉您其他类型的内存错误,如越界读取或写入。

Valgrind 本质上是检测底层指令,并且可以跟踪给定内存地址何时没有更多引用。它可以通过跟踪地址分配来做到这一点,因此它不仅可以告诉您一块内存丢失了,而且可以准确地告诉您什么时候它丢失了。

C++ 使这两种类型的检漏器的操作变得有点困难,因为它添加了 newdelete 运算符。从技术上讲,new 可以是与 malloc 完全不同的内存来源。然而,在实践中,许多真正的 C++ 实现只使用 malloc 来实现 new 或者可以选择使用 malloc 而不是替代方法。

C++ 等高级语言也倾向于使用其他高级方法来分配内存,例如 std::vectorstd::list。一个基本的泄漏检测器将分别报告更高级别模式可能进行的许多分配。这比说整个容器丢失有用得多。

这是一个published technical paper on how our CheckPointer tool works.

从根本上说,它跟踪所有值(堆和堆栈)的生命周期,以及它们的大小根据语言定义的类型。这允许 CheckPointer 不仅可以发现泄漏,还可以发现数组外绑定访问,即使对于堆栈中的数组也是如此,而 valgrind 不会这样做。

特别是,它分析源代码以查找所有指针使用。 (这本身就是一项艰巨的任务)。

它跟踪每个指针的指针元数据,包括

  • 对堆分配对象或指针指向的全局或局部变量或函数的对象元数据的引用和
  • 指针当前可能访问的对象的(子)对象的地址范围。这可能小于地址范围 整个对象;例如如果您获取结构成员的地址,则经过检测的源代码将只允许在使用结果指针时访问该成员。

它还会跟踪每个对象的种类和位置,即是否 它是一个函数、一个全局变量、线程局部变量或局部变量、堆分配内存或字符串常量:

  • 可以安全访问的对象的地址范围,
  • 对于存储在堆分配对象或变量中的每个指针,对该指针的指针元数据的引用。

所有这些跟踪都是通过将原始程序源转换为执行原始程序所做的程序,并交错各种元数据检查或更新例程来完成的。生成的程序被编译并运行。如果元数据检查在 运行 时失败,则会提供回溯以及失败类型的报告(无效指针、有效范围外的指针,...)

这被标记为 C 和 C++,没有提到操作系统。此答案适用于 Windows.

C

Windows有虚拟内存的概念。进程可以获得的任何内存都是虚拟内存。这是通过 VirtualAlloc() [MSDN]. You can imagine the leak detector to put a breakpoint on that function and whenever it is called, it gets the callstack and saves it somewhere. Then it can do similar for VirtualFree()[MSDN] 完成的。

然后可以识别差异并将其与已保存的调用堆栈一起显示。

C++

C++ 有一个不同的概念:它将从 VirtualAlloc() 获得的 64kb 大块拆分成更小的块,称为堆。 C++ 堆管理器来自 Microsoft,并提供新方法 HeapAlloc() [MSDN] and HeapFree()[MSDN].

然后,您可以像以前一样做,但实际上,该功能已经内置。 Microsoft 的 GFlags [MSDN] 工具可以启用跟踪:

在这种情况下,它将为 C++ 堆管理器调用保存最多 50 MB 的调用堆栈信息。

由于该设置也可以通过 Windows 注册表启用,因此内存泄漏检测器可以轻松使用它。

一般概念

如您所见,一般概念是跟踪分配和释放,比较它们并显示差异的调用堆栈。