需要帮助解释WinDbg堆总数以调试内存泄漏

时间:2019-04-20 22:10:12

标签: .net memory garbage-collection windbg

这个问题非常类似于: windbg memory leak investigation - missing heap memory

除了在我的情况下,所有内容都是x86,而该帖子提供的答案是说Windbg x64坏了。

就我而言,当我执行“!heap -s”时,我得到:

************************************************************************************************************************
                                              NT HEAP STATS BELOW
************************************************************************************************************************
LFH Key                   : 0x653c3365
Termination on corruption : DISABLED
  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast 
                    (k)     (k)    (k)     (k) length      blocks cont. heap 
-----------------------------------------------------------------------------
00e70000 00000002  761224 757296 761012   6306  1149    51    0  572b1   LFH
00d60000 00001002    1292    128   1080     26     7     2    0      0   LFH
01050000 00001002    1292   1048   1080    271    18     2    0     31   LFH
..snip..

我对00e70000处的堆感兴趣。

接下来,当我执行命令时:!heap -stat -h 00e70000 -grp s 0n999

对于堆中的每个块,我得到509行输出,列出了它的组大小,与该大小匹配的块数以及该大小的所有块使用的内存总大小。部分输出为:

0:000> !heap -stat -h 00e70000 -grp s 0n999
 heap @ 00e70000
group-by: TOTSIZE max-display: 999
    size     #blocks     total     ( %) (percent of total busy bytes)
    1000 14a - 14a000  (20.24)
    600c 16 - 84108  (8.10)
    168 408 - 5ab40  (5.56)
    154 404 - 55550  (5.23)
    10d8 2a - 2c370  (2.71)
    24 113f - 26cdc  (2.38)
    22750 1 - 22750  (2.11)

然后我将所有内容粘贴到excel中,并将第3列转换为十进制并将其求和,总共只有6.5兆(或大约)。

!address -summary和!heap -s都表明我应该得到总计808兆的附近的东西。这使我认为要么我不理解-stat命令的单位,要么x64和x86(整个Windbg)都坏了,或者我有一个更根本的误解。

有人可以帮助我了解哪种情况吗?

谢谢!

编辑:附加信息 使用DebugDiag,我看到主要的(默认)堆具有46/54个段,这些段都共享一个共同的功能,它们的大小均为15.81 meg,并且几乎全部被完全分配。那代表了我所缺少的总差异。

看到这一点之后,我记得我们的本机代码正在使用FASTMM4,它可能解释了这两个段以及为什么我没有将这些对象包含在Windbg中。

因此,我打算从本机代码中删除FASTMM4,然后再次运行性能测试以查看是否会有所改变。请随时添加有关此的任何帮助。

第二编辑,附加信息: 从我们的代码库中删除FASTMM并重新运行测试后,我发现15.81 MB的段仍然存在并且仍在泄漏。这些可以在DebugDiag分析中看到:

Segment Information
Base Address    Reserved Size   Committed Size  Uncommitted Size    Number of uncommitted ranges    Largest uncommitted block   Calculated heap fragmentation
0x00e70000  1020 KBytes 1020 KBytes 0 Bytes 1   0 Bytes 0%  0
0x03be0000  1020 KBytes 1020 KBytes 0 Bytes 1   0 Bytes 0%  0
0x04a20000  2 MBytes    2 MBytes    0 Bytes 1   0 Bytes 0%  0
0x051e0000  4 MBytes    4 MBytes    0 Bytes 1   0 Bytes 0%  0
0x0c4b0000  8 MBytes    8 MBytes    0 Bytes 1   0 Bytes 0%  0
0x19dc0000  15.81 MBytes    15.78 MBytes    28 KBytes   1   28 KBytes   -11928.57%  Unavailable
0x1c3b0000  15.81 MBytes    15.81 MBytes    0 Bytes 1   0 Bytes 0%  0
0x2c900000  15.81 MBytes    15.81 MBytes    0 Bytes 1   0 Bytes 0%  0
..snip..

在底部显示的标记为15.81 MB的新段扩展了另外46个新段,这些段代表了非托管堆上的727.26 mb的泄漏内存。

搜索15.81 MBytes的值会使我引出一些与Microsoft VC Runtime有关的引文:

https://social.msdn.microsoft.com/Forums/vstudio/en-US/e7534d01-57ed-455c-bc0d-edb1b87d0f52/microsoft-vc-runtime-heap-fragmentation?forum=vclanguage https://docs.microsoft.com/en-us/visualstudio/debugger/crt-debug-heap-details?view=vs-2019 Debugdiag shows "Microsoft VC Runtime Heap" using over 1gb

使用Windbg,我可以显示有关分配的分配信息,如下所示:

    61f130c8: 08008 . 10008 [101] - busy (10000) Internal 
    61f230d0: 10008 . 10008 [101] - busy (10000) Internal 
    61f330d8: 10008 . 10008 [101] - busy (10000) Internal 
    61f430e0: 10008 . 08008 [101] - busy (8000) Internal 
    61f4b0e8: 08008 . 10008 [101] - busy (10000) Internal 
    61f5b0f0: 10008 . 10008 [101] - busy (10000) Internal

但是,由于它们被标记为“内部”,因此它们不参与“堆栈回溯跟踪”(gflags选项-ust)来确定为分配它们而执行的实际代码。

任何人都可以引导我获得有关此泄漏的任何其他信息吗?最终会导致我们的应用程序崩溃。我需要任何可以指导我确定如何影响或减少泄漏的方法。

1 个答案:

答案 0 :(得分:1)

我将其发布为答案,因为最终有几种方法的确使我们找到了代码中不受管理的泄漏的根源。我在这里要说的只是假设,因为我没有在Microsoft文档中找到任何要验证的东西。

在原始帖子中,我展示了DebugDiag分析的一部分,该部分描述了进程的默认堆中数量不断增长的15.81段。我已经相信这只是Windows允许堆(在可能有很多堆的系统中)增长而无需对重载情况下需要增长哪个堆进行任何假设的方式。他们似乎是用1mb的段创建的,然后是另一个,然后是2mb的段,然后是4mb,8mb和16mb。此后,它们只会根据需要增长16mb(即15.81)。

当本机堆泄漏时,段会像这样一遍又一遍地添加。

在这里的问题甚至还未开始之前,我们就使用了经过92小时扩展负载测试的各个时间点制作的转储文件进行了托管内存分析。我们同时使用了Visual Studio和Windbg的SOS命令,但没有发现“托管”增长。唯一的问题是,非托管代码正在泄漏,如原始帖子中所示。

那时,我们在进程中使用“ Gflags + ust”来获取堆栈回溯跟踪。这给了我们完全有效的信息,但是数据不足。它显示了大量泄漏的块,并声称它们是由SecureString.ctor分配的。在托管堆上看不到任何SecureString(有效或无效),我们选择不理会当时告诉我们的内容。

然后,我们采用了一种技术含量较低的过程来查找泄漏代码。我们将在单独的扩展负载测试中测试每个API调用,并从DebugDiag进行转储分析,直到我们看到它正在泄漏或接受它不是。

一旦我们发现泄漏的API,我们就将服务器修改为从本质上“剔除”正在执行的大量代码,并重复进行调试诊断分析,直到我们的“剔除代码”不再显示泄漏。

到那时,我们开始返回它的各个部分,重复进行测量,并在服务器上使用两种方法之一:1)通过注释掉该代码路径,或2)通过放置循环使代码路径恶化1 = 1到1000(或可以放大泄漏的适当数值)

一旦代码路径被证明泄漏,我们就会深入研究其范围,并将此过程作为一种二进制搜索进行重复。最终,这导致我们进入3行,其中第一行(并非偶然)分配了“ SecureString”。

这正在传递给一种方法来对其进行解码,其中包含大约以下代码:

//Convert to IntPtr using marshal
IntPtr tmp = Marshal.SecureStringToBSTR(SecureString_Param1);
//convert to string using marshal
string plain = Marshal.PtrToStringAuto(tmp);
//Return the now plain string
return plain;

泄漏的内存是非托管BSTR,实际上,它最初是由SecureString.ctor分配的。此代码已在单独的测试应用程序中进行了测试,以进行验证。

请随时在此帖子中添加任何评论。