一、背景
1. 讲故事
前几天有位朋友找到我,说他的机器内存在不断的上涨,但在任务管理器中查不出是哪个进程吃的内存,特别奇怪,截图如下:
图片
在我的分析旅程中都是用户态模式的内存泄漏,像上图中的异常征兆已经明确告诉你了,不是用户态程序吃的内存,那就是内核态程序吃的,比如:
- 某些驱动程序
- 操作系统
从概率上来说一般都是某些第三方程序内存泄露导致的,这一篇我们就来聊一聊这种问题该如何解决。
二、内核模式堆泄露分析
1. 驱动程序是如何分配内存的
相信有很多朋友都知道,用户态的程序是直接或者间接的调用 VirtualAlloc 方法来向操作系统要内存,包括 C# 的 GC 堆也是一样,它的方法签名如下:
那内核中的驱动程序是如何向操作系统要内存的呢?一般都是调用 ExAllocatePool2 方法来要内存的,签名如下:
上面有两个参数要详细解释一下:
- Flags 参数
一般用的多的就是 POOL_FLAG_NON_PAGED 和 POOL_FLAG_PAGED 两种,前者表示分配的内存是需要永久驻留内存,不可以交换到硬盘的。后者分配的内存是可以交换到硬盘的。
- Tag 参数
这个参数的本意就是方便日后洞察内存泄露的,它强行让一块内存和这个 Tag(4byte的ascii 字符串) 做了强绑定,到时候通过这个 tag 就知道是谁分配的内存。
2. 制造内核模式堆泄露
为了能够让驱动程序泄露,可以使用微软提供的 NotMyFault 工具,这个工具利用 myfault.sys 驱动不断的向操作系统分配内存。官方网址为:https://learn.microsoft.com/zh-cn/sysinternals/downloads/notmyfault
打开 myfault 工具然后输入 40M/s 的泄露,并分配在非换页池中,同时配置下内核态转储dump, 代码和截图参考如下:
图片
在泄露的过程中,通过 Process Explorer 很明显的发现提交了 6.7G 的内存,其中有 4.9G 是在 NonPaged 中,即通过上图中的 POOL_FLAG_NON_PAGED 标记分配的,截图如下:
图片
接下来在 MyFault 上切换到 Crash 选项卡,强行让操作系统蓝屏来生成 dump 文件。
3. dump 分析
拿到dump后,先通过 !vm 观察下操作系统级的虚拟内存的分布情况。
从卦中的 NonPagedPool Usage 指标可以看到,当前的 非换页池 占用了 4.8G 内存,总计 121w 的内存页。
接下来就是要深挖下 非换页池 ,看看到底都是什么 Tag 分配的,可以使用 !poolused 2 命令。
从卦中数据看,有一个神秘的 Tag=Leak 的内存分配,它分配了 119 次,总大小 4.99G。哈哈,其实就是刚才通过 MyFault 做的 40M/s 的内存分配。
接下来的问题是:这个 Leak 是哪一个驱动程序所为呢?最简单的办法就是在各个驱动的内存空间中做内存搜索,看看谁里面有 Leak 的asc硬编码,对吧,有了这个思路,先用 lm 看看里面都有哪些 sys 。
接下来就是写脚本在每个 sys 的 start ~ end 区间做 s 搜索,这个脚本我就不放了,非常简单,最终就在 myfault.sys 中成功找到了 Leak 硬编码,参考如下:
三、总结
在过往的dump分析中都是用户态程序的泄露,内核态模式堆的的泄露还是第一次分析,不是朋友提供的这次机会,真的就没缘分啦!在这次dump分析过程中,也让大家看到了 windbg 是多么的强大!