轻松看懂的加解密系列(5)番外篇I:快速检测和定位内存泄漏(附测试代码)
继续以上篇文章中的程序代码为实验对象,步骤如下:
1. 先用 Application Verifier + Windbg 组合快速发现问题;
笔者实验时安装的【Application Verifier】版本为 10.0(x64),【Windbg】版本为 (WinDbg Preview 1.2306.14001.0)。启动【Application Verifier】之后,首先为目标测试程序选择要监控的项目,本例为了演示方便,只选择了监控【Memory】,如【图-1】。
如你所知,【Application Verifier】是一个用于 Windows 应用程序测试和调试的工具,其原理并不复杂,通过在注册表中针对特定应用程序的配置信息和设置进行修改,从而来检查和捕获错误。
如【图-2】所示,【GlobalFlag】
是一个用于控制调试和错误报告的标志位。具体地说,【0x100】对应于【FLG_HEAP_ENABLE_TAIL_CHECK】
标志,表示启用堆栈末尾检查。这意味着应用程序的堆栈分配将被监视,以检测是否发生了堆栈溢出或破坏。
【vrfcore.dll】是【Application Verifier】的核心 DLL 文件之一。它包含了用于执行内存和资源验证的核心功能。当【Application Verifier】启用时,它将使用【vrfcore.dll】
来监视应用程序的内存使用,并检查潜在的问题。
诸如以上这些目标测试程序相关联的注册表配置并不是笔者手工添加的,都是【Application Verifier】根据 UI 界面的配置自动生成的。
接下来我们就可以通过 Windbg 来启动目标测试程序,然后执行可能有内存泄漏风险的步骤,如【图-3】所示。此时由于注册表中已经预先定义了一些检测配置,所以程序在运行时会被记录下相关的状态并输出在 Windbg 控制台中。当我们执行完测试退出程序之后,可以发现控制台明确地打印出了 “Detected memory leaks!”,并且还将泄漏的内存块地址和大小打印了出来。比如【图-4】中的以下输出内容:
{1470} normal block at 0x04681248, 24 bytes long.
Data: < 6 ; * ! ( , > 1D 00 36 00 3B 00 2A 00 21 00 28 00 2C 00 1E 00
- 【1470】这个标识符通常称为"堆块号"(Heap Block Number),它是在进程的堆中分配的内存块的一个标识符。
- 【0x04681248】则是分配的内存块的起始地址,它是内存块在物理内存中的位置。大小为
24
字节。 - 【Data】将内存里的内容以ASCII码和16进制两种格式输出,
如果实验人员对被测程序很熟悉的话,其实这时已经可以对发生内存泄漏的地方大致心里有数了。如果暂时还不能确定?没关系,接下来我们可以回到 Visual Studio,使用其他工具来定位问题所在位置。
2. 再用 Visual Studio Diagnostic Tools 定位问题;
在 Visual Studio 里 Debug 运行测试程序之后,被测试的 MFC 程序界面初始化结束,此时可以打开【Diagnostic Tools】窗口,如【图-5】。记得在其中按下【Heap Profiling】按钮后,以监控堆内存,接着就可以像正常使用软件一样操作起来了。然后在你认为可能发生了内存泄漏之后,按下【Take Snapshot】按钮,这就像按下了照相机快门一样,给此时的程序的堆内存拍摄一张快照。【图-6/7/8】分别是程序初始化结束、加密操作结束和解密操作结束,这三个时间窗口拍摄下来的内存快照。
我们来观察一下,从堆内存的角度看【Heap Profiling】,第二张快照比第一张快照发生了什么具体变化。如【图-9】右侧【Diagnostic Tools】界面,首先直观上的感受是占用的内存数量在增加,不过这也很正常,我们需要跟踪的是新增的,但是又最终没有被释放的内存。【图-9】左侧展示的表格为具体有哪些地方增加了内存,包括数量和大小(字节)的变化。点开每一行具体的内容,如果其堆栈类似【图-10】这样完全是系统函数的堆栈调用,那基本上就可以免检了,相信操作系统的代码是会正确释放申请的内存。但如果点开后如【图-11】这样,调用栈中包含了本地代码函数,而且进一步点击定位代码后,如【图-12】这样,明显有申请内存,但没见着释放内存的代码,就要引起高度注意了,需要查询确认一下内存最后被释放的逻辑。而当前测试程序内存泄漏的原因,正是字符集转换时没有释放申请的内存导致的。
3. 修复问题后,再次使用 Windbg 来验证结果;
总结:
本例给出的是笔者总结的 Windows Native C++ 程序开发阶段快速定位内存泄漏的一种策略。实际研发过程当中,还有许多其他的工具和策略可供选择,比如利用 Windbg !heap 系列命令调试内存泄漏问题、比如使用 “gflags 和 UMDH tool” 组合调试内存泄漏问题。不过在原理上其实都差不多,都是通过比较两次堆内存的差别,进而帮助使用者定位到问题代码。
避免内存泄漏的传统思路是严格成对使用new/delete或malloc/free来进行内存分配和释放。但是当代码变得复杂时,很容易出现疏漏。为了解决这个问题,我们可以使用智能指针(Smart Pointer)作为终极解决方案。本文是传统思路的修复版,附录里还提供了一个智能指针修复版本。
普通网友: 每当我阅读你的编程博客文章时,我总能感受到你的专业水平和耐心解答的精神。【我也写了一些相关领域的文章,希望能够得到博主的指导,共同进步!】
CSDN-Ada助手: 恭喜您撰写第14篇博客!标题中的“轻松看懂”确实吸引了我的注意力,让我对加解密系列产生了浓厚的兴趣。而这篇番外篇则更加引人期待,关于如何快速检测和定位内存泄漏的内容听起来非常实用。感谢您在博客中分享这些宝贵的知识! 我鼓励您继续创作下去,并希望能在未来的博客中看到更多关于加解密的话题。或许您可以考虑介绍一些实际应用中的加解密案例,或者深入探讨一些加解密算法的优缺点。这样的创作将进一步拓宽读者的知识面,让我们更好地了解加解密的实际应用和技术细节。期待您的下一篇博客!
CSDN-Ada助手: 恭喜作者发布了第15篇博客,内容依然丰富有趣。对于加解密系列的番外篇II,介绍如何监视Windows操作系统上应用程序的API调用,让读者们能够更深入地了解相关知识。希望作者能够继续坚持创作,不断分享更多有价值的内容。或许在下一篇博客中,可以探讨一些实际案例,让读者更好地理解如何应用这些知识。期待作者的下一步创作!
CSDN-Ada助手: 恭喜您写了第16篇博客,内容看起来非常有趣!不过,我想提醒一下,标题中的内容可能会引起一些争议,因为获取同事的电脑登录密码可能涉及到隐私和安全问题。希望您在接下来的创作中能够更加谨慎地选择话题,避免触及敏感领域。期待您的下一篇作品!
CSDN-Ada助手: 恭喜你写了第18篇博客,标题看起来很吸引人!对于了解 UML 类图概念,你的文章确实给了很多帮助。不过,我觉得下一步你可以尝试深入分析一些 UML 类图的实际应用场景,这样读者会更加容易理解和接受。希望你可以继续保持创作,加油!