头图

Hello everyone, I am Feng Hui, a researcher in this issue of Microsoft MVP Labs. This article mainly introduces how to use Windbg to analyze the memory problems in the application process, from the exploration of managed heap to unmanaged heap and the allocation of memory, let's explore together.

image.png

Recently, several friends have used our 161d2e54c9b0ee that the memory has skyrocketed during the export process. Next, let’s use windbg to see what caused it.

Let's first look at the current application memory footprint address -summary

0:000> !address -summary
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free                                    581     7df8`ef0c9000 ( 125.972 TB)           98.42%
<unknown>                              1678      206`ffb9e000 (   2.027 TB)  99.99%    1.58%
Image                                   950        0`064fd000 ( 100.988 MB)   0.00%    0.00%
Heap                                     58        0`050f6000 (  80.961 MB)   0.00%    0.00%
Stack                                   156        0`04380000 (  67.500 MB)   0.00%    0.00%
Other                                    11        0`019ad000 (  25.676 MB)   0.00%    0.00%
TEB                                      52        0`00068000 ( 416.000 kB)   0.00%    0.00%
PEB                                       1        0`00001000 (   4.000 kB)   0.00%    0.00%
--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_MAPPED                              282      200`038a6000 (   2.000 TB)  98.64%    1.56%
MEM_PRIVATE                            1674        7`07184000 (  28.111 GB)   1.35%    0.02%
MEM_IMAGE                               950        0`064fd000 ( 100.988 MB)   0.00%    0.00%
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE                                581     7df8`ef0c9000 ( 125.972 TB)           98.42%
MEM_RESERVE                             295      205`f8659000 (   2.023 TB)  99.79%    1.58%
MEM_COMMIT                             2611        1`188ce000 (   4.384 GB)   0.21%    0.00%
--- Protect Summary (for commit) - RgnCount ----------- Total Size -------- %ofBusy %ofTotal
PAGE_READWRITE                         1595        1`0dc6c000 (   4.215 GB)   0.20%    0.00%
PAGE_EXECUTE_READ                       156        0`04d66000 (  77.398 MB)   0.00%    0.00%
PAGE_READONLY                           600        0`03851000 (  56.316 MB)   0.00%    0.00%
PAGE_NOACCESS                            99        0`021f2000 (  33.945 MB)   0.00%    0.00%
PAGE_EXECUTE_READWRITE                   19        0`0027b000 (   2.480 MB)   0.00%    0.00%
PAGE_WRITECOPY                           90        0`001a0000 (   1.625 MB)   0.00%    0.00%
PAGE_READWRITE | PAGE_GUARD              52        0`0009e000 ( 632.000 kB)   0.00%    0.00%
--- Largest Region by Usage ----------- Base Address -------- Region Size ----------
Free                                    189`0413c000     7c6b`01ed4000 ( 124.418 TB)
<unknown>                              7dfb`2a153000      1f9`bd2ef000 (   1.976 TB)
Image                                  7ffc`883c1000        0`009ba000 (   9.727 MB)
Heap                                    183`0e9a1000        0`00f01000 (  15.004 MB)
Stack                                    37`62980000        0`0017b000 (   1.480 MB)
Other                                   183`77707000        0`01775000 (  23.457 MB)
TEB                                      37`62600000        0`00002000 (   8.000 kB)
PEB                                      37`627dd000        0`00001000 (   4.000 kB)

MEM_COMMIT occupies 4.384G, then we use eeheap -gc to check the managed heap.

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

According to the memory, it seems that the problem is not here, a large amount of memory still appears in unmanaged. Let’s take a look at the Windows NT heap. In fact, most user heap allocators in Windows are built on the NT heap manager API (RtlAllocateHeap/RtlFreeHeap) in ntdll.dll, such as malloc/free and new in C. /delete, there are also SysAllocString in the COM framework and LocalAlloc, GlobalAlloc and HeapAlloc in Win32. Although these allocators will create different heaps to store their memory, they will eventually call NT in ntdll.dll Heap to achieve.

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      
-------------------------------------------------------------------------------------

The output results are as shown above, the NT heap content is so small... What is the reason... Okay, according to Maoni, it seems that there is a problem with the verification.

All user mode allocations on Windows ultimately obtain memory through VirtualAlloc, whether it is bitmaps or GC heap.

The difference is whether you call VirtualAlloc directly or use other methods to call it. If it is unmanaged memory, GC does not control it, and of course it does not know their existence.

GC does not manage these memory, so the code we wrote is still problematic. Let's go back and consider one more thing, "When the export is in progress, the memory will increase a lot, and the memory will decrease after the export is completed." Let's take a look at the code, as shown below. In fact, what we now understand is that these memories must have been "held" during our execution and have not been released.

app.MapGet("/excel", async content =>
    string path = Path.Combine(Directory.GetCurrentDirectory(), "test.xlsx");
    List<TestDto> list = new();
    for (int i = 0; i < 400; i++)
        list.Add(new TestDto
            ImageUrl = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic_source%2F53%2F0a%2Fda%2F530adad966630fce548cd408237ff200.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1641193100&t=417a589da8c9ba3103ed74c33fbd6c70"
    Stopwatch stopwatch = Stopwatch.StartNew();
    ExcelExporter exporter = new ExcelExporter();
    await exporter.Export(path, list);
    stopwatch.Stop();
    await content.Response.WriteAsync(stopwatch.Elapsed.TotalSeconds.ToString());
});

According to the performance of memory and our theory, we continue to use Windbg to investigate. Now we can actually find that these objects were eventually recovered by the GC. With the theory, we continue to conceive. The GC knows which objects can be terminated, right? And they call their finalizers when they become unreachable, and use the finalization queue to record these finalized objects in the GC. So can we check it? As shown below, let's take a look.

0:000> !finalizequeue
----------------------------------
Statistics for all finalizable objects (including all objects ready for finalization):
              MT    Count    TotalSize Class Name
00007ffc2dc23818        1           24 System.Net.Security.SafeCredentialReference
00007ffc2dac4238        1           24 System.WeakReference
00007ffc2d6eb908        1           24 System.WeakReference`1[[Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions, Microsoft.AspNetCore.Server.Kestrel.Core]]
00007ffc2d6e4120        1           24 System.WeakReference`1[[System.Runtime.Loader.AssemblyLoadContext, System.Private.CoreLib]]
00007ffc2d572b68        1           24 System.WeakReference`1[[Microsoft.Extensions.DependencyInjection.ServiceProvider, Microsoft.Extensions.DependencyInjection]]
00007ffc2d429258        1           24 System.WeakReference`1[[System.IO.FileSystemWatcher, System.IO.FileSystem.Watcher]]
00007ffc2dd15c20        1           32 Microsoft.Win32.SafeHandles.SafeBCryptAlgorithmHandle
00007ffc2d6de4d8        1           32 Internal.Cryptography.Pal.Native.SafeLocalAllocHandle
00007ffc2d68fa00        1           32 Internal.Cryptography.Pal.Native.SafeCertStoreHandle
00007ffc2d3a5cc0        1           32 System.Net.Quic.Implementations.MsQuic.Internal.SafeMsQuicRegistrationHandle
00007ffc2db390c8        1           40 Interop+WinHttp+SafeWinHttpHandle
00007ffc2d69a420        1           40 Internal.Cryptography.Pal.Native.SafeCertContextHandle
00007ffc2d5bea18        1           40 System.Diagnostics.EventLog
00007ffc2dc29a38        1           48 System.Net.Security.SafeFreeCredential_SECURITY
00007ffc2d963f80        2           48 System.WeakReference`1[[System.Text.RegularExpressions.RegexReplacement, System.Text.RegularExpressions]]
00007ffc2d7a3750        2           48 System.WeakReference`1[[Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection, Microsoft.AspNetCore.Server.Kestrel.Core]]
00007ffc2d685e10        1           56 System.Runtime.CompilerServices.ConditionalWeakTable`2+Container[[System.Buffers.TlsOverPerCoreLockedStacksArrayPool`1+ThreadLocalArray[[System.Char, System.Private.CoreLib]][], System.Private.CoreLib],[System.Object, System.Private.CoreLib]]
00007ffc2d44c4d0        1           56 System.Runtime.CompilerServices.ConditionalWeakTable`2+Container[[System.Buffers.TlsOverPerCoreLockedStacksArrayPool`1+ThreadLocalArray[[System.Byte, System.Private.CoreLib]][], System.Private.CoreLib],[System.Object, System.Private.CoreLib]]
00007ffc2d96be68        1           64 CellStore`1[[System.Uri, System.Private.Uri]]
00007ffc2d96b780        1           64 FlagCellStore
00007ffc2d96af48        1           64 CellStore`1[[System.Object, System.Private.CoreLib]]
00007ffc2d96a5b8        1           64 CellStore`1[[OfficeOpenXml.ExcelCoreValue, Magicodes.IE.EPPlus]]
00007ffc2d6ddab8        2           64 Internal.Cryptography.Pal.Native.SafeChainEngineHandle
00007ffc2d69d528        2           64 Internal.Win32.SafeHandles.SafeRegistryHandle
00007ffc2d685bc8        2           64 Microsoft.Win32.SafeHandles.SafeWaitHandle
00007ffc2d685280        3           72 System.Threading.ThreadInt64PersistentCounter+ThreadLocalNodeFinalizationHelper
00007ffc2d5f5f50        3           72 System.Runtime.InteropServices.PosixSignalRegistration
00007ffc2d4299d0        1           72 Microsoft.Win32.SafeHandles.SafeFileHandle
00007ffc2d6e40b8        1           80 System.Runtime.Loader.DefaultAssemblyLoadContext
00007ffc2dac9ed0        2           96 PageIndex
00007ffc2d96d0c8        2           96 ColumnIndex
00007ffc2d464470        3          120 System.Gen2GcCallback
00007ffc2d40a620        1          120 System.IO.FileSystemWatcher
00007ffc2d96bc18        2          128 CellStore`1[[System.Int32, System.Private.CoreLib]]
00007ffc2dac20c8        2          144 System.Reflection.Emit.DynamicResolver
00007ffc2d680f10        3          144 System.Threading.LowLevelLock
00007ffc2d683c48        3          168 System.Threading.ThreadPoolWorkQueueThreadLocals
00007ffc2d681e80        1          176 System.Threading.LowLevelLifoSemaphore
00007ffc2dc25ef0        1          184 System.Collections.Concurrent.CDSCollectionETWBCLProvider
00007ffc2db8e658        1          184 System.Net.NetEventSource
00007ffc2db8c378        1          184 System.Net.NetEventSource
00007ffc2db38f90        1          184 System.Net.NetEventSource
00007ffc2d90c658        1          184 Microsoft.IO.RecyclableMemoryStreamManager+Events
00007ffc2d689b48        1          184 Microsoft.AspNetCore.Certificates.Generation.CertificateManager+CertificateManagerEventSource
00007ffc2d66f9f8        1          184 System.Diagnostics.Tracing.FrameworkEventSource
00007ffc2d66b720        1          184 System.Net.NetEventSource
00007ffc2d44d128        1          184 System.Buffers.ArrayPoolEventSource
00007ffc2d2e2ec8        1          184 System.Diagnostics.Tracing.NativeRuntimeEventSource
00007ffc2d694e10        1          192 System.Threading.Tasks.TplEventSource
00007ffc2d572ab0        1          192 Microsoft.Extensions.DependencyInjection.DependencyInjectionEventSource
00007ffc2d505f00        1          200 Microsoft.Extensions.Logging.EventSource.LoggingEventSource
00007ffc2db8ade8        1          224 System.Net.NameResolutionTelemetry
00007ffc2d428b08        7          224 System.Threading.PreAllocatedOverlapped
00007ffc2d563c78        1          232 System.Diagnostics.DiagnosticSourceEventSource
00007ffc2d61fe88        1          240 Microsoft.AspNetCore.Hosting.HostingEventSource
00007ffc2db6b788        8          256 System.Threading.TimerQueue+AppDomainTimerSafeHandle
00007ffc2d690270        1          280 System.Net.Sockets.SocketsTelemetry
00007ffc2db6bc80        1          296 System.Net.Http.HttpTelemetry
00007ffc2d68b998        1          336 Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelEventSource
00007ffc2dc21998        1          360 System.Net.Security.NetSecurityTelemetry
00007ffc2d2dae28        1          384 System.Diagnostics.Tracing.RuntimeEventSource
00007ffc2d66ad60       10          480 System.Net.Sockets.SafeSocketHandle
00007ffc2d2e0240       21          504 System.WeakReference`1[[System.Diagnostics.Tracing.EventSource, System.Private.CoreLib]]
00007ffc2d2b0538        9          648 System.Threading.Thread
00007ffc2d77a188        2          704 Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal.SocketReceiver
00007ffc2d90cec0        6          960 Microsoft.IO.RecyclableMemoryStream
00007ffc2d5fc658       10         1280 System.Net.Sockets.Socket
00007ffc2d68d898        4         1536 System.Net.Sockets.Socket+AwaitableSocketAsyncEventArgs
00007ffc2d2dc778       42         4704 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
00007ffc2daec058      356        14240 System.Drawing.Bitmap
Total 553 objects

WOW! ! ! , Look at the above 356 System.Drawing.Bitmap waiting for recycling, it seems that this is our influencing factor, let's check the code.

try
    cell.Value = string.Empty;
    Bitmap bitmap;
    if (url.IsBase64StringValid())
        bitmap = url.Base64StringToBitmap();
        bitmap = Extension.GetBitmapByUrl(url);
    if (bitmap == null)
        cell.Value = ExporterHeaderList[colIndex].ExportImageFieldAttribute.Alt;
        ExcelPicture pic = CurrentExcelWorksheet.Drawings.AddPicture(Guid.NewGuid().ToString(), bitmap);
        AddImage((rowIndex + (ExcelExporterSettings.HeaderRowIndex > 1 ? ExcelExporterSettings.HeaderRowIndex : 0)),
            colIndex - ignoreCount, pic, ExporterHeaderList[colIndex].ExportImageFieldAttribute.YOffset, ExporterHeaderList[colIndex].ExportImageFieldAttribute.XOffset);
        CurrentExcelWorksheet.Row(rowIndex + 1).Height = ExporterHeaderList[colIndex].ExportImageFieldAttribute.Height;
        pic.SetSize(ExporterHeaderList[colIndex].ExportImageFieldAttribute.Width * 7, ExporterHeaderList[colIndex].ExportImageFieldAttribute.Height);
catch (Exception)
    cell.Value = ExporterHeaderList[colIndex].ExportImageFieldAttribute.Alt;

Use the Bitmap object in the ExcelPicture object. For online image sources, we will read and store it in the Bitmap, but we found that the object was not released, so a large number of Bitmaps have not been released. We use Let's deal with it.

using (ExcelPicture pic = CurrentExcelWorksheet.Drawings.AddPicture(Guid.NewGuid().ToString(), bitmap))
    AddImage((rowIndex + (ExcelExporterSettings.HeaderRowIndex > 1 ? ExcelExporterSettings.HeaderRowIndex : 0)),
        colIndex - ignoreCount, pic, ExporterHeaderList[colIndex].ExportImageFieldAttribute.YOffset, ExporterHeaderList[colIndex].ExportImageFieldAttribute.XOffset);
    CurrentExcelWorksheet.Row(rowIndex + 1).Height = ExporterHeaderList[colIndex].ExportImageFieldAttribute.Height;
    pic.SetSize(ExporterHeaderList[colIndex].ExportImageFieldAttribute.Width * 7, ExporterHeaderList[colIndex].ExportImageFieldAttribute.Height);
}

A new object with a finalizer must be added to the finalization queue. This behavior is also called "registering for finalization". Of course, I also recommend that you choose to use the SOSEX extension plug-in, which provides content similar to finalization, which seems to be more intuitive, as shown below.

download http://www.stevestechspot.com/default.aspx


:000> .load D:\sosex_64\sosex.dll
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   System.Drawing.Bitmap
54 objects, 2,160 bytes
Generation 1:
       Count      Total Size   Type
---------------------------------------------------------
           1             184   Microsoft.AspNetCore.Certificates.Generation.CertificateManager+CertificateManagerEventSource
           1             336   Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelEventSource
           4            1536   System.Net.Sockets.Socket+AwaitableSocketAsyncEventArgs
           1              32   Internal.Cryptography.Pal.Native.SafeCertStoreHandle
           1             280   System.Net.Sockets.SocketsTelemetry
           1             192   System.Threading.Tasks.TplEventSource
           1              40   Internal.Cryptography.Pal.Native.SafeCertContextHandle
           2              64   Internal.Win32.SafeHandles.SafeRegistryHandle
           2              64   Internal.Cryptography.Pal.Native.SafeChainEngineHandle
           1              32   Internal.Cryptography.Pal.Native.SafeLocalAllocHandle
           1              80   System.Runtime.Loader.DefaultAssemblyLoadContext
           1              24   System.WeakReference`1[[System.Runtime.Loader.AssemblyLoadContext, System.Private.CoreLib]]
           1              24   System.WeakReference`1[[Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions, Microsoft.AspNetCore.Server.Kestrel.Core]]
           2             704   Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal.SocketReceiver
           2              48   System.WeakReference`1[[Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.KestrelConnection, Microsoft.AspNetCore.Server.Kestrel.Core]]
           1             184   Microsoft.IO.RecyclableMemoryStreamManager+Events
           6             960   Microsoft.IO.RecyclableMemoryStream
           2              48   System.WeakReference`1[[System.Text.RegularExpressions.RegexReplacement, System.Text.RegularExpressions]]
           1              64   CellStore`1[[OfficeOpenXml.ExcelCoreValue, Magicodes.IE.EPPlus]]
           1              64   CellStore`1[[System.Object, System.Private.CoreLib]]
           1              64   FlagCellStore
           2             128   CellStore`1[[System.Int32, System.Private.CoreLib]]
           1              64   CellStore`1[[System.Uri, System.Private.Uri]]
           2              96   ColumnIndex
           2             144   System.Reflection.Emit.DynamicResolver
           1              24   System.WeakReference
           2              96   PageIndex
         302           12080   System.Drawing.Bitmap
           1             184   System.Net.NetEventSource
           1              40   Interop+WinHttp+SafeWinHttpHandle
           8             256   System.Threading.TimerQueue+AppDomainTimerSafeHandle
           1             296   System.Net.Http.HttpTelemetry
           1             224   System.Net.NameResolutionTelemetry
           1             184   System.Net.NetEventSource
           1             184   System.Net.NetEventSource
           1             360   System.Net.Security.NetSecurityTelemetry
           1              24   System.Net.Security.SafeCredentialReference
           1             184   System.Collections.Concurrent.CDSCollectionETWBCLProvider
           1              48   System.Net.Security.SafeFreeCredential_SECURITY
           1              32   Microsoft.Win32.SafeHandles.SafeBCryptAlgorithmHandle