Memory Leak Detection in C++ - 在 C++ 中检测内存泄漏的工具 Back

谨记,千万不要延期修复内存泄漏的问题。而且,你可以使用一个或者多个的便捷检测工具,去作为你开发过程的一部分。

在早期,有那么一篇叫《在嵌入式系统中检测内存泄漏问题》,由LJ于2002年9月编写,并可在 www.linuxjournal.com/article/6059 查看的文章。该篇文章谈及到当我们使用 C 作为编程语言时,如何去检测内存泄漏的问题。而接下来,这篇文章将探讨如果是在 C++ 中,我们又如何检测呢?该文所谈及的检测工具,它们只能检测到应用程序的错误代码,但不能检测内核态下的内存泄漏情况。所有的这些工具,它们都被使用在 MontaVista Linux 专业版2.1和3.0产品上。其中的 dmalloc 更是被嵌入 MontaVista Linux 中。

当在嵌入式系统中开发应用程序时,设计师和工程师一定要对系统的内存资源,其使用情况要特别小心。不同于工作站(workstation)的是,嵌入式系统的内存资源非常有限。一般,只有理想的程序才不会占用任何的内存交换区(swap area)。当一个系统耗尽其资源时,它只能惊慌地扼杀掉部分的程序以腾出空间,给出所需要的资源。因此,在写程序时,防止内存泄漏是至关重要的。现在,有许多的工具能帮助程序员找到资源泄漏的地方。而所有这里讨论的这些程序,它们发行时都会携带有自己的测试程序。

在这些测试程序中,有一种方法是我亲眼目睹到应用开发者能成功使用的。其中包括使用一个工作站去开发原型代码以及尽可能地找出其中的漏洞。而在这种方法中,我强烈地建议去使用内存泄漏检测工具。因为只有这样,应用开发者在工作站检查漏洞时,才会更有信心地认为,转变到目标处理器将会更加简单。使用工作站的一个主要原因在于它们不仅便宜,而且参与的开发者都会人手一台。而目标,换句话说,是一件稀少而又有着急切需求的一样东西。

大部分的內存泄漏检测程序,都能提供出整套的源代码。一般,我们都会把其配置在基于 x86 的平台上。而如果需要其运行在非 x86 上,那么则需要进行移植。移植的工作可能就是重新编译,链接并执行,又可能是需要一些从一个平台到另一个平台的汇编代码。其中部分的工具,都会有关于使用交叉编译环境的窍门以及建议。

dmalloc

这是一款我曾经在2002年9月攥写的文章中提到的工具。该工具的作者表明其在 C++ 领域的知识是有限的,以至于对内存泄漏的检测也是有限的。为了在 C++ 及线程中使用 dmalloc,那么我们必须把该应用链接成静态。

ccmalloc

ccmalloc 工具是有着一个简单使用模块的内存分析工具。该模块支持动态链接库,但不支持 dlopen 的使用。ccmalloc 不仅能检测内存泄漏的情况,它还能对同一个数据进行释放,并签名、重写和写回到已被释放的数据区域。除此之外,它还能展示分配与释放的统计数据。该工具适用于优化并去除多余代码,而且这在 C++ 内是支持使用的。它还在整个调用链上,提供了文件与行号信息,而不仅仅是调用 malloc/free。该功能也支持在 C++ 上的使用。使用 ccmalloc 并不需要重新编译;而只需要简单地为 lccmalloc -ldlccmalloc.o -ldl 建立连接。ccmalloc 提供了调用链的基本展示、自定义展示以及可选择性展示。而且还能展示一个经过压缩的日志文件和一个叫 .ccmalloc 的配置文件。ccmalloc 的主要文档是放在一个命名为 ccmalloc.cfg 的文件里,而它那些包含有程序的测试文件则提供了更多的文档信息。这是因为 nm 和 gdb 都需要获取更多关于标志位和 gzip 的信息来压缩日志文件。

NJAMD

NJAMD 的作者声称:“NJAMD 不仅仅是另一个的 malloc 漏洞检测软件”。与大部分内存定位漏洞检测软件相比,NJAMD 中使用了新的函数去替代标准的定位函数。该函数能伴随着内存的使用,展现出不同的检测功能。尤其是能检测到动态缓存的上溢/下溢,以及内存释放后的重用情况。我们可以使用 LD_PRELOAD 去加载用于构建 NJAMD 的库,也可以直接把这些库链接到程序当中。在第一次内存分配时,NJAMD 会申请一块大的内存缓存空间(大约20MB),并分割成小块,供程序使用。这是因为,NJAMD 的程序需要内存空间。

NJAMD 可以单独通过一个前端使用或者在 gdb 中使用。它可以允许我们对程序完成后的堆进行分析,也允许只要简单地预加载对应的库,程序就不需要重新编译即可查漏洞的功能。此外,它还可以追踪库中那些包装有 malloc 和 free、GUI 组件分配以及 C++ 中 new 和 delete 的库函数,它们的内存泄漏情况。通常,一个内存泄漏问题虽潜伏在那等待着时机去击打我们,但不会立刻被发现。因此,追踪到它们会消耗大量的时间。NJAMD 有着许多环境变量,用于设置不同的检测登记。与其他软件相比,NJAMD 在性能上会是一个很大的问题。因此,该工具只能在开发期间使用。倘若把其置于生产环境,那么将会导致系统严重迟缓。

YAMD

YAMD (也是另一个内存漏洞检测工具)是另一个用于捕获内存寻址块边界的软件包。它是通过使用处理器的分页机制来做到这点的。通过这种功能,YAMD 能检测到程序越界读写的情况。当其它程序访问产生时,基于指令上的检测这种错误,将是非常高效的做法。这些捕获到的错误以及反捕获的信息将会被以文件名和行号的形式记录下来。反捕获轨迹将会是非常有用的信息,因为大部分的内存寻址都是通过一小部分的常规程序完成。

该库会模拟 malloc 和 free 的调用,去捕捉许多的 malloc 间接调用,例如那些通过调用 strdup 所产生的调用。当然,它也会去捕捉 new 和 delete 的调用。然而,如果 new 和 delete 操作符被重载,那么它们就不会被捕捉到。

YAMD 与其他同类软件一样,都需要大量的虚拟内存或内存交换区去完成其功能。在一个嵌入式系统中,这要求一般是不可行的。因此,我在早些时间建议,在工作站上使用这些工具来进行原型检测。在此,我也同样是如此建议。当该检测工作完成时,移植应用到目标机的信心将会大大增强。这是因为,大部分的内存泄漏问题已经被找出来,但记住,不是全部的都被发现。

YAMD 提供了一个运行脚本,叫做 run-yamd。它可使得 YAMD 的程序更加容易执行。在一定条件下,如果想进行恢复,该脚本提供了几种方式。当程序正在检测时,如果有一个核心问题需要展现,它会把其记录在一个日志文件里。而一个代码调试器,也可以用于调试 YAMD 的控制代码。可是,如果在 YAMD 预加载后才去调试,而不是静态链接方式链接后调试,那么这样会产生问题。

Valgrind

Valgrind 是用于 x86-GNU Linux 系统的一个相对较新的开源内存检测软件。它比起早期的一些工具,具有更多的功能。但是,它仅能运行于 X86 架构的主机上。当一个程序运行在 Valgrind 控制下,那么它所有内存的读写操作,包括调用 malloc、free、new 和 delete,都会被检测。Valgrind 可检测到未初始化的内存区域、内存泄漏、内存非初始化或不可读的传值操作、POSIX 线程产生的一些问题以及 malloc/free 和 new/delete 的不匹配使用等问题。

当然,Valgrind 可使用 gdb 去捕捉错误,并且允许程序开发者在该些错误上使用 gdb。如果能这样,程序开发者就可以寻找问题的根源,并能更快地解决问题。此外,在一些情况下,补丁可以使用并且能继续调试。Valgrind 是度身定做于大型或小型应用,包括 KDE 3、Mozilla、OpenOffice 和其他等。

Valgrind 的其中一个特性是,它能提供关于缓存收集的相关信息。它可以对 CPU 的 L1-D、L1-I 和统一的 L2 缓存进行一个细节性的仿真。除此之外,它还可以计算出一个缓存,其在各行追踪的程序中所命中的次数。Valgrind 有一个描述的很详细的文档 HOWTO。其中,包括有大量的例子。它的官方网站还包括了大量相关信息,可快速查询。在 Valgrind 中,我们可以组合许多不同的选项,并整合出自己最喜欢的组合。

Valgrind 在错误描述展示后面会包括有为该正在执行的程序所分配的进程 ID。而地址则是以行号和源文件名的形式展示。而一个完整的回溯过程,同样也会被展示出来。Valgrind 会读取一个启动文件,该启动文件可以包括一些的指令,这些指令用于隐藏错误检测过程中所产生的消息。这样,你就可以更专注于代码上,而不是已经存在且不能改变的库。

Valgrind 是通过在一个模拟处理器环境下运行应用程序来进行检测。它首先会让动态链接库/加载器先去加载模拟装置,然后,会把程序以及相关所需要的库加载到该装置中。当程序在运行时,所有的数据都会被收集。而当程序结束时,所有的日志数据要么会显示出来,要么会写入日志文件中。

mpatrol

mpatrol 是一个代码库。它可链接于你的程序,去溯源与追踪内存的分配情况。该库是在不同的操作系统平台写编写且运行。它有一个明显的优点,那就是它已经被移植进许多不同的目标处理器,包括 MIPS、PowerPC、x86 和由一些 MontaVista 的用户所完成的 StrongArm 目标处理器。

mpatrol 是一个可灵活配置的库;它可设置成从一个固定大小的静态数组中分配内存,而不使用堆。它也可以被构建成一个静态、共享或者线程安全的库。此外,它还可以作为一个大的对象文件,链接在应用里,而不需要包括在库中。这样的功能特性,给终端用户提供了极大的灵活度。

它所创建的代码包括了44个不同的内存分配及字符串函数。它还提供了钩子函数,使得程序能在 gdb 中调用。这样,使用 mpatrol 也就能去调试程序。

库的设置和堆的使用情况都会随着程序运行,定期地展示出来。运行期间统计的数据都会在程序结束后展示出来。集成 mpatrol 的程序都会有一个内置的默认值,该默认值可通过环境变量进行修改。在运行时,如果想要改变这些环境变量,它并不需要重新构建库。因此,在不同测试间的协调,都可以动态地进行。日志文件默认是存放在当前程序的文件夹;当然这可配置成输出到 stdout 和 stderr 或其他文件。

如果程序开发者想要对一个更小的内存占用模拟压力测试,那么,mpatrol 可以被指示去限制内存的占用。这样将对那种在实验环境中不能唾手可得的测试条件来说,是非常有帮助的。通过这样的特性,仿真一个用户的环境或设置一个苛刻的测试阻碍去进行压力测试将会变得更加容易。另外,测试程序可以特意去停止一组随机的内存占用,以测试负责错误恢复的程序。这样的功能可用于对 C++ 中的异常处理程序的检查。至于堆的快照,则可以用于参照来衡量内存使用的高低水位。

Insure++

由 Parasoft 开发的 Insure++ 并不是一款开源或免费的软件,但是,它和 mpatrol 类似的是,它是一个用于内存泄漏检测和代码覆盖的好工具。确实, Insure++ 在代码覆盖方面所做的工作比 mpatrol 要多。它提供了收集和展示数据的工具。该软件的试用版是可以下载于非 Linux 平台上使用一段时间。

当然,该产品在 Linux 下安装也是很容易的。只不过,这样会对安装的电脑产生节点锁(node-locked)。Insure++ 有一组以供理解的文档以及数个选项。代码覆盖工具是区分开的,但会集成在初始化的包中。

Insure++ 对所寻找到的问题,都会提供详细的信息。如果想要使用它,那么,你必须要使用 Insure++ 前端去编译它。该前端会把其传递到一个通用的编译器中,并指示代码去使用 Insure++ 的库。编译期间,所有非法的类型都会被检测出来,包括那些不正确的传参。明显的内存错误也会被公告出来。在运行期间,错误会记载在 stderr 中,但可通过一个图形工具去显示它。当构建一个应用程序时,命令行或 Makefile 文件都可以使用去促进项目以及大型程序的构建过程。

Insure++ 程序的执行时很简单的,因为它并不需要任何特殊的命令去执行;程序会像一个普通的程序一样去执行。所有的调试和错误捕获代码都包含在 Insure++ 的库中,这些库会被链接到程序里。

有一个叫 Inuse 的扩展工具,它能实时地展示程序使用内存的情况。它还可以给出一张精准的图片去展示内存到底是如何使用的,段是如何获取以及一些不易察觉的泄漏。这些泄漏看起来虽小,但可以随着时间增大而累计起来。有一次,我用来检测一个小客户端,结果发现该客户端中的一个特定 C++ 类 产生了小部分的内存泄漏,这泄漏是如此的小。然而对于一个嵌入式系统,它将会执行数月或数年的时间,而这小小的泄漏将会变得无比之大。有了这个工具,泄漏可以容易被追踪,发现并修复。而且他可用的工具却并不会捕捉到这么小的泄漏。

代码覆盖情况是可以通过另一个工具,TCA,去分析。随着含有 Insure ++ 的程序运行,数据可以被采集,并生成一张精准的图片,去显示哪段代码被执行过。TCA 有一个GUI 界面代码覆盖情况的显示。

资源

Cal Erickson(cal_erickson@mvista.com)是 MontaVista 软件公司的一名高级 Linux 顾问。在加入 MotaVista 之前,他曾经是在 Mentor Graphics Embedded Software Division 工作的一个高级工程师。Cal Erickson 在计算机行业已经工作超过30年,在计算机制作以及终端用户的开发环境上,有着资深的经验。

Empty Comments
Sign in GitHub

As the plugin is integrated with a code management system like GitLab or GitHub, you may have to auth with your account before leaving comments around this article.

Notice: This plugin has used Cookie to store your token with an expiration.