仅通过转储来排除内存泄漏

有时我们会遇到这样一个场景:一个进程正在使用大量内存,而此时我们能够获得的唯一数据就是用户转储。通常,来自umdh或xperf等工具的数据会更好,因为它们提供一段时间内的内存使用数据,并且可以包括调用堆栈信息。但是,umdh需要重新启动进程(这会丢失高内存使用率的状态),xperf需要安装Windows Performance Toolkit,这可能并不总是一个立即的选项。
当我们有这样一个转储时,我们可能无法明确指出是哪段代码产生了高内存使用率,但我们可以将故障排除的范围缩小到特定的dll。我们需要做的第一件事是确定什么类型的内存使用了大部分地址空间。调试器命令!address -summary允许我们执行以下操作:

0:000> !address -summary

 

--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal

Free                                    489      7fe`6ff5a000 (   7.994 Tb)           99.92%

Heap                                   9094        1`75ed1000 (   5.843 Gb)  93.47%    0.07%

<unknown>                               275        0`12e41000 ( 302.254 Mb)  4.72%    0.00%

Image                                   937        0`05a6a000 (  90.414 Mb)  1.41%    0.00%

Stack                                   138        0`01700000 (  23.000 Mb)  0.36%    0.00%

Other                                    14        0`001bd000 (   1.738 Mb)  0.03%    0.00%

TEB                                      46        0`0005c000 ( 368.000 kb)   0.01%   0.00%

PEB                                       1        0`00001000 (   4.000 kb)  0.00%    0.00%

从这个例子中我们可以看到大部分内存被堆使用。一个进程通常有多个堆,每个堆都是通过调用HeapCreate创建的。我们可以检查这些堆的大小!heap -s:

0:000> !heap -s

LFH Key                   : 0x0000006c1104d280

Termination on corruption : ENABLED

          Heap     Flags  Reserv  Commit  Virt  Free  List   UCR Virt  Lock  Fast

                            (k)     (k)   (k)     (k) length      blocks cont. heap

-------------------------------------------------------------------------------------

0000000000100000 00000002  16384  12824  16384  1180   254     5   0      3   LFH

0000000000010000 00008000     64      4     64     1     1     1   0      0     

00000000003d0000 00001002   1088    708   1088   121    20     2   0      0   LFH

0000000003080000 00001002   1536    700   1536     4     4     2   0      0   LFH

00000000033a0000 00001002 5229696 1377584 5229696 414244  4039  3059   0     2c   LFH

    External fragmentation  30 % (4039 free blocks)

    Virtual address fragmentation  73 % (3059 uncommited ranges)

0000000003380000 00001002     64      8     64     3     1     1   0      0     

0000000003600000 00001002    512     56    512     3     1     1   0      0     

0000000003c20000 00001002    512      8    512     3     1     1   0      0     

0000000003220000 00001002    512      8    512     3     1     1   0      0     

0000000003e50000 00001002    512      8    512     3     1     1   0      0     

0000000003d00000 00001002    512    148    512     5     3     1   0      0   LFH

从上面的输出中我们可以看到大部分内存被堆00000000033a0000占用。在这一点上,我们需要尝试确定这个堆的用途。执行此操作的一种强力方法是使用“s”命令搜索内存。

0:000> s -q 0 l?7fffffffffffffff 00000000033a0000

....

000007fe`f21810a0  00000000`033a0000 00000000`00000001

's'命令的输出可能很详细。您将需要手动检查“s”找到命中的地址。大多数地址可能在堆内存中,我们正在寻找与模块匹配的地址。我截取了上面的输出以显示相关的命中,一个加载模块内部的地址。

对内存的搜索发现堆00000000033a0000被模块xxxx.dll,特别是它是全局“xxx”类的一部分。

0:000> ln 000007fe`f21810a0

(000007fe`f21810a0)  xxxx!xxx::m_Heap

在这一点上,我们不知道具体的代码是什么xxx.dll已经分配了很多堆,但是我们已经大大缩小了问题的范围。现在,我们可以确定中是否存在与堆使用有关的已知问题xxx.dll这将在以后的版本中讨论。我们还可以从经验中了解到,在特定情况下,此模块使用大量内存,例如发送到该服务的大量工作。

posted on 2020-12-21 08:05  活着的虫子  阅读(344)  评论(0编辑  收藏  举报

导航