最近项目突然收到了一个紧急的问题报告 - 用户在进行某些关键操作的时候整个软件突然就crash掉了。幸好产品继承了自动抓取dump的功能。。。
收到dump之后,通过windbg打开,查看相应的callstack。迅速定位到出错的线程。在调用MapViewOfFile()的时候没有map成功导致crash。
接下来,我们需要知道为什么为何map失败?通过GetLastErrorCode(),我们得知进程OutOfMemory导致map失败。MapViewOfFile()需要映射512KB(4Mb)连续内存,而通过!address -summary命令,我们发现系统里最大的连续内存块为1.5Mb。Crash因此发生了。
至此,我们可以肯定,该crash是由于memory leak造成MapViewOfFile()失败。那么,是什么leak了?重新看上面的输出信息,我们可以看到,MEM_MAPPED使用的内存高达934Mb,MEM_PRIVATE使用的内存为805Mb。据此,我们怀疑,产品里所使用的memory mapped file可能会存在大量的leak。
运行命令!address -f:MEM_MAPPED,会得到类似的输出:
第三列,也就是RgnSize,为当前MMF的映射窗口大小。查看了该输出,我们发现,窗口大小为512kB的文件在500个左右,而大小为256KB的文件多大1800个。据此,我们可以锁定这些256KB的MMF文件。
下面的问题,就是,谁映射了这么多的MMF文件?在此不同的项目可以有不同的方法。如果你很熟悉项目数据的话,你可以先通过dd查看一下该256KB的内容都是啥?有没有你所熟悉的内容?如果是字符串的话那就更容易定位问题的所在地方了。
这里,我们假设我们不熟悉文件的内容,从而无法猜测这些数据是谁产生的。我们只能采取其他办法。
上面输出的第一列,也就是BaseAddr,为该map view的地址。如果我们要使用该map view的话,我们必须要有对应的指针指向该view。因此,我们随机选了一个256KB的view的baseaddress,然后通过下面的命令查找谁还指向了这块内存。
从该输出我们能够看到,大概有10多个地方保存有该指针地址。接下来,我们就需要查看谁,也就是那些类的instance还保存这些地址。此处没有其他好的办法,只能在这些地址四处通过dds命令来查看是否有对象的symbol。
在查看了多个地址之后,我们发现,有一个vftable为CMMFSerializer的对象。这给了我们极大的肯定:该类名里有MMF!十有八九是该类的leak!
接下来,我们的目标为谁创建了CMMFSerializer对象而没有销毁?首先,我们通过x命令来获得CMMFSerializer的虚表地址,这样我们就能够在heap里搜索这些对象都存在哪里。
然后,运行!heap命令,就能够查看到拥有这些对象的地址了。
从我们的输出能够看到,大量的CommunicatorObj对象存在heap里。这就说明了CommunicatorObj这些对象没有被删除掉。从而,我们可以做这样的结论:CommunicatorObj的泄漏导致了如此之多的MMF的泄漏,以及很高的MMF_PRIVATE使用率如此之高。剩下来的工作就很简单了,看看代码,分析分析哪些地方使用了CommunicatorObj。