嵌入式系统中的内存泄漏检测
开发嵌入式系统的问题之一是内存泄漏的检测;我发现了三种对此有用的工具。这些工具用于检测应用程序错误,而不是内核内存泄漏。其中两个工具(mtrace 和 dmalloc)是 MontaVista Linux Professional Edition 2.1 产品的一部分。另一个(memwatch)可从 Web 获取(请参阅“资源”)。
C 和 C++ 程序员控制动态内存分配。不慎使用此控制可能会导致内存管理问题,从而导致性能下降、不可预测的执行或崩溃。
导致内存泄漏的一些问题包括写入或读取超出已分配的内存段,或尝试释放已释放的内存。当内存被分配后未在使用后释放,或者当指向内存分配的指针被删除,导致内存不再可用时,就会发生内存泄漏。内存泄漏会因页面调度增加而降低性能,并且随着时间的推移,会导致程序耗尽内存并崩溃。访问错误会导致数据损坏,从而导致程序行为不正确或崩溃。当程序耗尽内存时,也可能导致 Linux 内核崩溃。
设计和编程嵌入式应用程序需要非常小心。应用程序必须足够健壮,以处理可能发生的每个错误;应注意预测这些错误并相应地处理它们,尤其是在内存领域。通常,应用程序可能会运行一段时间,然后由于从未释放的内存分配而神秘地崩溃自身或系统。可以通过使用内存泄漏检测器来查找这些错误。
这些工具的工作原理是替换 malloc、free 和其他内存管理调用。每个工具都有代码来拦截对 malloc(和其他函数)的调用,并为每个内存请求设置跟踪信息。一些工具实现了内存保护围栏,以捕获错误的内存访问。
一些泄漏检测程序非常大,需要被搜索程序的虚拟内存映像。此要求使得在嵌入式系统上使用它们非常困难。但是,mtrace、memwatch 和 dmalloc 是简单的程序,可以找到大多数错误。
所有这三个工具都在一个包含常见内存处理错误的示例 C 程序上运行。此程序以及用于使用这三个工具构建它的 Makefile 可在 ftp.linuxjournal.com/pub/lj/listings/issue101/6059.tgz 下载。所有这些工具都已在几种不同的目标架构中使用过。示例代码无论本地编译还是交叉编译都可以工作。
在三种工具中最简单的是 mtrace。mtrace 是 GNU C 库的一个功能,它允许检测由不平衡的 malloc/free 调用引起的内存泄漏。它作为函数调用 mtrace() 实现,该函数调用启用跟踪并创建一个记录 malloc 和 freed 地址的日志文件。一个也称为 mtrace 的 Perl 脚本显示日志文件,仅列出不平衡的组合,并且——如果源文件可用——malloc 发生的源文件的行号。该工具可用于检查 Linux 下的 C 和 C++ 程序。使 mtrace 令人期望的特性之一是它具有可伸缩性。它可用于进行整体程序调试,也可以按模块进行缩放。
使用 mtrace 功能的关键是三项:包含 mcheck.h,设置 MALLOC_TRACE 环境变量并调用 mtrace() 函数调用。如果未设置 MALLOC_TRACE 变量,则 mtrace() 不执行任何操作。
mtrace 的输出包括如下消息:
- 0x0804a0f8 Free 13 was never alloc'd /memory_leak/memory_leaks/mtrace/my_test.c:193
指示已释放但从未 malloc 的内存,以及一个“Memory not freed”部分,其中包含未发生 free 的 malloc 调用的地址、大小和行号。
memwatch 是一个程序,它不仅检测 malloc 和 free 错误,还检测围栏柱条件。当将数据写入分配的内存块(由 malloc 分配)并且数据超出分配区域的末尾时,会发生围栏柱条件。memwatch 不会捕获的一些事情是写入已释放的地址以及从分配的内存外部读取数据。
memwatch 的核心是 memwatch.c 文件。它实现了地址检查的包装器和代码。要使用 memwatch,必须在源文件中包含 memwatch.h 文件。变量 MEMWATCH 和 MW_STDIO 必须在编译命令行上定义(-DMEMWATCH 和 -DMW_STDIO)。memwatch.c 文件也必须与应用程序一起使用。来自 memwatch.c 编译的对象模块必须包含在应用程序的链接中。执行应用程序后,如果 memwatch 发现任何异常,则 stdout 上将显示一条消息。创建文件 memwatch.log,其中包含有关遇到的错误的信息。每个错误消息都包含错误发生的行号和源代码文件名。
将 memwatch.log 与 mtrace 的日志进行比较,报告的错误相同。memwatch 工具还发现了一个围栏柱条件,其中内存地址被更改以覆盖分配区域的开始和结束,显示了 memwatch 在这种情况下扩展的功能。缺点是 memwatch 不可扩展。它必须在整个应用程序上运行。
第三个工具是一个库,它被设计为 malloc、realloc、calloc、free 和其他内存管理函数的直接替代品。它提供运行时可配置性。该工具的功能提供内存泄漏跟踪和围栏柱写入检测。它按文件名和行号报告其错误,并记录一些一般统计信息。这个库由 Gray Watson 创建和维护,已被移植到许多 Linux 以外的操作系统。
该软件包是可配置的,可以包含线程支持和 C++ 支持。它可以构建为共享库和静态库。所有这些选项都在构建工具时选择。结果是一组库,这些库在链接应用程序时使用。有一个包含文件 (dmalloc.h) 需要包含在要检查的应用程序的源代码中。除了库和包含文件之外,还需要设置一个环境变量,dmalloc 读取该环境变量以配置其检查方式以及放置日志信息的位置。以下行是用于 dmalloc 测试程序的设置
export \ DMALLOC_OPTIONS=debug=0x44a40503,inter=1,log=logfile
这意味着 1) log 是当前目录中名为 logfile 的文件,2) inter 是库自身检查的频率,3) debug 是一个十六进制数,其位选择要执行的检查类型。此示例测试几乎所有可能的错误。以下是测试列表以及在“debug”中设置的相应位
none (nil):无功能 (0)
log-stats (lst):记录一般统计信息 (0x1)
log-non-free (lnf):记录未释放的指针 (0x2)
log-known (lkn):仅记录已知的未释放指针 (0x4)
log-trans (ltr):记录内存事务 (0x8)
log-admin (lad):记录管理信息 (0x20)
log-blocks (lbl):在堆映射时记录块 (0x40)
log-bad-space (lbs):从坏指针转储空间 (0x100)
log-nonfree-space (lns):从未释放的指针转储空间 (0x200)
log-elapsed-time (let):记录已分配指针的经过时间 (0x40000)
log-current-time (lct):记录已分配指针的当前时间 (0x80000)
check-fence (cfe):检查围栏柱错误 (0x400)
check-heap (che):检查堆管理结构 (0x800)
check-lists (cli):检查空闲列表 (0x1000)
check-blank (cbl):检查被 alloc-blank、free-blank 覆盖的内存 (0x2000)
check-funcs (cfu):检查函数 (0x4000)
force-linear (fli):强制堆空间为线性 (0x10000)
catch-signals (csi):在 SIGHUP、SIGINT、SIGTERM 上关闭程序 (0x20000)
realloc-copy (rco):复制所有重新分配 (0x100000)
free-blank (fbl):用 BLANK_CHAR 覆盖释放的内存空间 (0x200000)
error-abort (eab):在错误时立即中止 (0x400000)
alloc-blank (abl):用 BLANK_CHAR 覆盖新分配的内存 (0x800000)
heap-check-map (hcm):在堆检查时记录堆映射 (0x1000000)
print-messages (pme):将消息写入 stderr (0x2000000)
catch-null (cnu):如果没有可用内存则中止 (0x4000000)
never-reuse (nre):永不重用释放的内存 (0x8000000)
allow-free-null (afn):允许释放 NULL 指针 (0x20000000)
error-dump (edu):在错误时转储核心,然后继续 (0x40000000)
如果库需要检查 C++ 程序,则应用程序需要一个名为 dmalloc.cc 的源文件。此模块为 new 到 malloc 和 delete 到 free 提供包装函数。GNU 调试器 GDB 可以与 dmalloc 一起使用。产品中包含一个文件,该文件可以用作 .gdbinit 文件的一部分,以便 GDB 自动设置为了解 dmalloc。
与库一起的是一个名为 dmalloc 的实用程序,它将以编程方式设置 DMALLOC_OPTIONS 变量。我制作了一个设置脚本,该脚本在运行要调试的程序之前被源化。这样,设置是可重复的,不会出错。
本文仅涵盖该工具的一般用途,但其文档非常详尽,并详细介绍了更多可用功能。与早期工具一起使用的测试程序是使用 DMALLOC 运行的。结果可能很长,并且可以选择性地包括内存关键区域(例如“fence-top”,指针在其中溢出 malloc 区域)中存在的字节。日志文件的末尾包括统计信息、地址、块大小和 malloc 而不 free 的事件的行号。
这三个工具为内存泄漏检测和报告提供了不同的支持。这些工具中的每一个都已在 Linux 工作站上使用,以及在几种不同的目标架构上交叉编译和执行。在一个应用程序中,开发人员使用了所有三个工具。mtrace 用于在第三方 C++ 库中查找内存泄漏,其中异常抛出/捕获块导致了严重的泄漏。dmalloc 工具用于查找 Linux pthreaded 应用程序执行中的内存泄漏。memwatch 工具用于捕获未正确碎片整理自身的缓冲区池机制。这些工具体积小巧,易于实现,并在调试完成后易于移除。
示例程序由一个源文件 my_test.c 组成。有三个单独的目录,其中包含一个 README、Makefile 和一个用于运行测试的脚本 [可在 ftp.linuxjournal.com/pub/lj/listings/issue101/6059.tgz 获取]。在 dmalloc 测试中,环境设置脚本也可用。每个测试都已在 Red Hat 和 SuSE Linux 发行版以及 MontaVista Linux 交叉开发环境中构建。
