飙血推荐
  • HTML教程
  • MySQL教程
  • JavaScript基础教程
  • php入门教程
  • JavaScript正则表达式运用
  • Excel函数教程
  • AngularJS教程
  • UEditor使用文档
  • ThinkPHP5.0教程

利用Windbg分析Magicodes.IE一次错误编写导致内存剧增

时间:2021-12-13  作者:yyfh  

由于这近一年时间一直忙于写书和工作,一直没有水文,但是近期有几位朋友使用我们的域名反馈在导出过程中内存暴涨...好吧,不管怎样,不能苦了我们朋友,接下来我们通过windbg来看一下什么原因导致的。

接下来我们先通过address -summary来看一下当前应用内存占用量。

0:000> !address -summary

--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free                                    581     7df8`ef0c9000 ( 域名 TB)           域名%
<unknown>                              1678      206`ffb9e000 (   域名 TB)  域名%    域名%
Image                                   950        0`064fd000 ( 域名 MB)   域名%    域名%
Heap                                     58        0`050f6000 (  域名 MB)   域名%    域名%
Stack                                   156        0`04380000 (  域名 MB)   域名%    域名%
Other                                    11        0`019ad000 (  域名 MB)   域名%    域名%
TEB                                      52        0`00068000 ( 域名 kB)   域名%    域名%
PEB                                       1        0`00001000 (   域名 kB)   域名%    域名%

--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_MAPPED                              282      200`038a6000 (   域名 TB)  域名%    域名%
MEM_PRIVATE                            1674        7`07184000 (  域名 GB)   域名%    域名%
MEM_IMAGE                               950        0`064fd000 ( 域名 MB)   域名%    域名%

--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE                                581     7df8`ef0c9000 ( 域名 TB)           域名%
MEM_RESERVE                             295      205`f8659000 (   域名 TB)  域名%    域名%
MEM_COMMIT                             2611        1`188ce000 (   域名 GB)   域名%    域名%

--- Protect Summary (for commit) - RgnCount ----------- Total Size -------- %ofBusy %ofTotal
PAGE_READWRITE                         1595        1`0dc6c000 (   域名 GB)   域名%    域名%
PAGE_EXECUTE_READ                       156        0`04d66000 (  域名 MB)   域名%    域名%
PAGE_READONLY                           600        0`03851000 (  域名 MB)   域名%    域名%
PAGE_NOACCESS                            99        0`021f2000 (  域名 MB)   域名%    域名%
PAGE_EXECUTE_READWRITE                   19        0`0027b000 (   域名 MB)   域名%    域名%
PAGE_WRITECOPY                           90        0`001a0000 (   域名 MB)   域名%    域名%
PAGE_READWRITE | PAGE_GUARD              52        0`0009e000 ( 域名 kB)   域名%    域名%

--- Largest Region by Usage ----------- Base Address -------- Region Size ----------
Free                                    189`0413c000     7c6b`01ed4000 ( 域名 TB)
<unknown>                              7dfb`2a153000      1f9`bd2ef000 (   域名 TB)
Image                                  7ffc`883c1000        0`009ba000 (   域名 MB)
Heap                                    183`0e9a1000        0`00f01000 (  域名 MB)
Stack                                    37`62980000        0`0017b000 (   域名 MB)
Other                                   183`77707000        0`01775000 (  域名 MB)
TEB                                      37`62600000        0`00002000 (   域名 kB)
PEB                                      37`627dd000        0`00001000 (   域名 kB)

MEM_COMMIT占用了域名,接下来我们利用eeheap -gc来检查托管堆。

0:000> !eeheap -gc
GC Allocated Heap Size:    Size: 0x11ac2568 (296494440) bytes.
GC Committed Heap Size:    Size: 0x120e7000 (302936064) bytes.

根据这些内存来看,似乎问题不是这里,大量的内存还是出现在非托管。我们利用Windows NT堆来看一下,其实在Windows中大多数的用户堆分配器都在域名中的NT堆管理器API(RtlAllocateHeap/RtlFreeHeap)上建立,比如说C中的malloc/free和new/delete,另外还有COM框架中的SysAllocString以及在Win32中的LocalAlloc、GlobalAlloc和HeapAlloc,虽然说这些分配器都会创建不同的堆来存储它们的内存,但是他们最终都要调用域名中的NT堆来实现。

0:000> !heap -s


************************************************************************************************************************
                                              NT HEAP STATS BELOW
************************************************************************************************************************
NtGlobalFlag enables following debugging aids for new heaps:
    stack back traces
LFH Key                   : 0x7cfd4cc2db4ddb4d
Termination on corruption : ENABLED
          Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast 
                            (k)     (k)    (k)     (k) length      blocks cont. heap 
-------------------------------------------------------------------------------------
0000018378fd0000 08000002   65128  15296  64928   1720   177    17    2      c   LFH
    External fragmentation  11 % (177 free blocks)
00000183775c0000 08008000      64      4     64      2     1     1    0      0      
000001837aa90000 08001002    1280    108   1080     26     3     2    0      0   LFH
000001837ad20000 08001002      60      8     60      2     1     1    0      0      
000001837aca0000 08041002      60      8     60      5     1     1    0      0      
000001887bfd0000 08001002      60     20     60      1     2     1    0      0      
000001830cf30000 08001002    3324   1364   3124     19    10     3    0      0   LFH
000001830ce30000 08001002      60      8     60      5     1     1    0      0      
-------------------------------------------------------------------------------------

输出结果如上所示,NT堆内容好少....什么原因....好吧根据 maoni所说,似乎是验证出了问题。

image

image

GC没有管辖这些内存,所以说还是我们编写的代码有问题,我们返过来再考虑一个事情,“导出进行时,内存会大量增加,导出完成后内存会降低下去”。我们来看一下代码,如下所示,其实我们现在明白的是,在我们执行期间肯定是这些内存一直“持有”,并没有被释放掉。

域名et("/excel", async content =>
{
    string path = 域名ine(域名urrentDirectory(), "域名");
    List<TestDto> list = new();
    for (int i = 0; i < 400; i++)
    {
        域名(new TestDto
        {
            ImageUrl = "https://域名/image_search/src=http%3A%2F%域名%2Fedpic_source%2F53%2F0a%2Fda%域名&refer=http%3A%2F%域名&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1641193100&t=417a589da8c9ba3103ed74c33fbd6c70"
        });
    }
    Stopwatch stopwatch = 域名tNew();
    ExcelExporter exporter = new ExcelExporter();
    await 域名rt(path, list);
    域名();
    await 域名eAsync(域名域名ring());
});

根据内存的表现和我们的理论,我们继续利用windbg来排查一下,现在其实我们可以发现,这些对象最终还是被GC收回了,带着理论我们继续构思,GC是知道哪些对象可以终结的对吧?并且它们在变成不可到达时调用它们的终结器,在GC中会利用finalization queue来记录这些终结对象。所以说我们是不是可以查一下?如下所示,我们来看一下。

0:000> !finalizequeue
----------------------------------
Statistics for all finalizable objects (including all objects ready for finalization):
              MT    Count    TotalSize Class Name
00007ffc2dc23818        1           24 域名域名CredentialReference
00007ffc2dac4238        1           24 域名Reference
00007ffc2d6eb908        1           24 域名Reference`1[[域名域名.KestrelServerOptions, 域名域名]]
00007ffc2d6e4120        1           24 域名Reference`1[[域名域名mblyLoadContext, 域名Lib]]
00007ffc2d572b68        1           24 域名Reference`1[[域名域名iceProvider, 域名ndencyInjection]]
00007ffc2d429258        1           24 域名Reference`1[[域名SystemWatcher, 域名域名her]]
00007ffc2dd15c20        1           32 域名域名BCryptAlgorithmHandle
00007ffc2d6de4d8        1           32 域名.域名LocalAllocHandle
00007ffc2d68fa00        1           32 域名.域名CertStoreHandle
00007ffc2d3a5cc0        1           32 域名.域名域名MsQuicRegistrationHandle
00007ffc2db390c8        1           40 Interop+WinHttp+SafeWinHttpHandle
00007ffc2d69a420        1           40 域名.域名CertContextHandle
00007ffc2d5bea18        1           40 域名tLog
00007ffc2dc29a38        1           48 域名域名FreeCredential_SECURITY
00007ffc2d963f80        2           48 域名Reference`1[[域名域名xReplacement, 域名larExpressions]]
00007ffc2d7a3750        2           48 域名Reference`1[[域名域名.域名relConnection, 域名域名]]
00007ffc2d685e10        1           56 域名域名itionalWeakTable`2+Container[[域名verPerCoreLockedStacksArrayPool`1+ThreadLocalArray[[域名, 域名Lib]][], 域名Lib],[域名ct, 域名Lib]]
00007ffc2d44c4d0        1           56 域名域名itionalWeakTable`2+Container[[域名verPerCoreLockedStacksArrayPool`1+ThreadLocalArray[[域名, 域名Lib]][], 域名Lib],[域名ct, 域名Lib]]
00007ffc2d96be68        1           64 CellStore`1[[域名, 域名]]
00007ffc2d96b780        1           64 FlagCellStore
00007ffc2d96af48        1           64 CellStore`1[[域名ct, 域名Lib]]
00007ffc2d96a5b8        1           64 CellStore`1[[域名lCoreValue, 域名us]]
00007ffc2d6ddab8        2           64 域名.域名ChainEngineHandle
00007ffc2d69d528        2           64 域名域名RegistryHandle
00007ffc2d685bc8        2           64 域名域名WaitHandle
00007ffc2d685280        3           72 域名adInt64PersistentCounter+ThreadLocalNodeFinalizationHelper
00007ffc2d5f5f50        3           72 域名域名xSignalRegistration
00007ffc2d4299d0        1           72 域名域名FileHandle
00007ffc2d6e40b8        1           80 域名域名ultAssemblyLoadContext
00007ffc2dac9ed0        2           96 PageIndex
00007ffc2d96d0c8        2           96 ColumnIndex
00007ffc2d464470        3          120 域名GcCallback
00007ffc2d40a620        1          120 域名SystemWatcher
00007ffc2d96bc18        2          128 CellStore`1[[域名2, 域名Lib]]
00007ffc2dac20c8        2          144 域名.DynamicResolver
00007ffc2d680f10        3          144 域名evelLock
00007ffc2d683c48        3          168 域名adPoolWorkQueueThreadLocals
00007ffc2d681e80        1          176 域名evelLifoSemaphore
00007ffc2dc25ef0        1          184 域名域名ollectionETWBCLProvider
00007ffc2db8e658        1          184 域名ventSource
00007ffc2db8c378        1          184 域名ventSource
00007ffc2db38f90        1          184 域名ventSource
00007ffc2d90c658        1          184 域名clableMemoryStreamManager+Events
00007ffc2d689b48        1          184 域名域名ificateManager+CertificateManagerEventSource
00007ffc2d66f9f8        1          184 域名域名eworkEventSource
00007ffc2d66b720        1          184 域名ventSource
00007ffc2d44d128        1          184 域名yPoolEventSource
00007ffc2d2e2ec8        1          184 域名域名veRuntimeEventSource
00007ffc2d694e10        1          192 域名域名ventSource
00007ffc2d572ab0        1          192 域名域名ndencyInjectionEventSource
00007ffc2d505f00        1          200 域名域名ingEventSource
00007ffc2db8ade8        1          224 域名ResolutionTelemetry
00007ffc2d428b08        7          224 域名llocatedOverlapped
00007ffc2d563c78        1          232 域名nosticSourceEventSource
00007ffc2d61fe88        1          240 域名域名ingEventSource
00007ffc2db6b788        8          256 域名rQueue+AppDomainTimerSafeHandle
00007ffc2d690270        1          280 域名域名etsTelemetry
00007ffc2db6bc80        1          296 域名.HttpTelemetry
00007ffc2d68b998        1          336 域名域名.域名relEventSource
00007ffc2dc21998        1          360 域名域名ecurityTelemetry
00007ffc2d2dae28        1          384 域名域名imeEventSource
00007ffc2d66ad60       10          480 域名域名SocketHandle
00007ffc2d2e0240       21          504 域名Reference`1[[域名域名tSource, 域名Lib]]
00007ffc2d2b0538        9          648 域名ad
00007ffc2d77a188        2          704 域名域名域名域名etReceiver
00007ffc2d90cec0        6          960 域名clableMemoryStream
00007ffc2d5fc658       10         1280 域名域名et
00007ffc2d68d898        4         1536 域名域名et+AwaitableSocketAsyncEventArgs
00007ffc2d2dc778       42         4704 域名域名tSource+OverrideEventProvider
00007ffc2daec058      356        14240 域名ap
Total 553 objects

WOW!!!,看上面356个域名ap在等待回收,看起来这是我们的影响因素,我们来查一下代码。

 try
{
    域名e = 域名y;
    Bitmap bitmap;
    if (域名se64StringValid())
    {
        bitmap = 域名64StringToBitmap();
    }
    else
    {
        bitmap = 域名itmapByUrl(url);
    }

    if (bitmap == null)
    {
        域名e = ExporterHeaderList[colIndex].域名;
    }
    else
    {
        ExcelPicture pic = 域名icture(域名uid().ToString(), bitmap);
        AddImage((rowIndex + (域名erRowIndex > 1 ? 域名erRowIndex : 0)),
            colIndex - ignoreCount, pic, ExporterHeaderList[colIndex].域名set, ExporterHeaderList[colIndex].域名set);
        域名(rowIndex + 1).Height = ExporterHeaderList[colIndex].域名ht;
        域名ize(ExporterHeaderList[colIndex].域名h * 7, ExporterHeaderList[colIndex].域名ht);
    }

}
catch (Exception)
{
    域名e = ExporterHeaderList[colIndex].域名;
}

在ExcelPicture对象中去使用Bitmap对象,对于在线图片源来说,我们会读取并存储到Bitmap中,但是我们发现并没有对该对象进行释放操作,所以导致大量的Bitmap一直没有释放,我们通过using来处理一下。

using (ExcelPicture pic = 域名icture(域名uid().ToString(), bitmap))
{
    AddImage((rowIndex + (域名erRowIndex > 1 ? 域名erRowIndex : 0)),
        colIndex - ignoreCount, pic, ExporterHeaderList[colIndex].域名set, ExporterHeaderList[colIndex].域名set);
    域名(rowIndex + 1).Height = ExporterHeaderList[colIndex].域名ht;
    域名ize(ExporterHeaderList[colIndex].域名h * 7, ExporterHeaderList[colIndex].域名ht);
}

一个带有终结器的新对象是必须要被添加进finalization queue中的,这个行为也被称为“终结注册(registering for finalization)”。
当然我也建议你选择使用SOSEX扩展插件,它提供了finalization类似的内容,似乎看起来更直观一些,如下所示。

下载地址:http://域名/域名

:000> .load D:\sosex_64\域名
This dump has no SOSEX heap index.
The heap index makes searching for references and roots much faster.
To create a heap index, run !bhi
0:000> !finq -stat
Generation 0:
       Count      Total Size   Type
---------------------------------------------------------
          54            2160   域名ap

54 objects, 2,160 bytes

Generation 1:
       Count      Total Size   Type
---------------------------------------------------------
           1             184   域名域名ificateManager+CertificateManagerEventSource
           1             336   域名域名.域名relEventSource
           4            1536   域名域名et+AwaitableSocketAsyncEventArgs
           1              32   域名.域名CertStoreHandle
           1             280   域名域名etsTelemetry
           1             192   域名域名ventSource
           1              40   域名.域名CertContextHandle
           2              64   域名域名RegistryHandle
           2              64   域名.域名ChainEngineHandle
           1              32   域名.域名LocalAllocHandle
           1              80   域名域名ultAssemblyLoadContext
           1              24   域名Reference`1[[域名域名mblyLoadContext, 域名Lib]]
           1              24   域名Reference`1[[域名域名.KestrelServerOptions, 域名域名]]
           2             704   域名域名域名域名etReceiver
           2              48   域名Reference`1[[域名域名.域名relConnection, 域名域名]]
           1             184   域名clableMemoryStreamManager+Events
           6             960   域名clableMemoryStream
           2              48   域名Reference`1[[域名域名xReplacement, 域名larExpressions]]
           1              64   CellStore`1[[域名lCoreValue, 域名us]]
           1              64   CellStore`1[[域名ct, 域名Lib]]
           1              64   FlagCellStore
           2             128   CellStore`1[[域名2, 域名Lib]]
           1              64   CellStore`1[[域名, 域名]]
           2              96   ColumnIndex
           2             144   域名.DynamicResolver
           1              24   域名Reference
           2              96   PageIndex
         302           12080   域名ap
           1             184   域名ventSource
           1              40   Interop+WinHttp+SafeWinHttpHandle
           8             256   域名rQueue+AppDomainTimerSafeHandle
           1             296   域名.HttpTelemetry
           1             224   域名ResolutionTelemetry
           1             184   域名ventSource
           1             184   域名ventSource
           1             360   域名域名ecurityTelemetry
           1              24   域名域名CredentialReference
           1             184   域名域名ollectionETWBCLProvider
           1              48   域名域名FreeCredential_SECURITY
           1              32   域名域名BCryptAlgorithmHandle

499 objects, 30,736 bytes

Generation 2:
0 objects, 0 bytes

TOTAL: 553 objects, 32,896 bytes

可能大家都会像我一开始有个疑问,你这个图片我看了...没有那么大,并且在windbg中也没有表现大小呀。首先我们先来看一下这个图片的质量。
图片的像素为2560x1440,位深为24目前已知这些信息,我们计算一下未压缩的图片大小。

2560x1440x24/8

10M左右一张图,已知图片数x10M=3G,其实对于这个问题来说,这并不属于内存泄漏。

标签:编程
湘ICP备14001474号-3  投诉建议:234161800@qq.com   部分内容来源于网络,如有侵权,请联系删除。