Location>code7788 >text

Talking about Bitmap in C# is terrifying.

Popularity:442 ℃/2024-08-26 13:50:42

I: Background

1. Storytelling

existNET Advanced DebuggingOne of the biggest dangers of Bitmap is that it can cause programs to throw unthinkableOutOfMemoryExceptionNET developers often fall into it can not extricate themselves from the pain, based on this, this one I analyze from the perspective of dump to give you a deep dive into the story behind the Bitmap.

II: The Story Behind Bitmap

1. How much memory can a Bitmap eat?

I'm sure many of you know that bitmap eats unmanaged memory, but I'm sure there are many of you who don't know that this thing can eat tens, if not hundreds, of times the size of the bitmap itself. Maybe it's a bit abstract to say so, but let's take an example to illustrate, the reference code generated by chatgpt is as follows:


static void Main(string[] args)
{
    // Create a new Bitmap object, size 100x100 pixels
    Bitmap bitmap = new Bitmap(21000, 21000);

    // Get the Graphics object for the Bitmap to draw on.
    using (Graphics g = (bitmap))
    {
        // Set the background color to blue
        ().

        // Example: Drawing a red circle on a Bitmap
        // Set the brush color to red
        using (Pen pen = new Pen(, 10000)) // 10 is the thickness of the pen
        {
            // Draw the circle with center (50, 50) and radius 30
            (pen, 10000, 10000, 15000, 15000);
        }

        // Example: drawing text on a Bitmap
        // Set the font
        using (Font font = new Font("Arial", 1600))
        {
            // Set the brush color to white
            using (Brush brush = new SolidBrush())
            {
                // Draw the text on the Bitmap at position (10, 70)
                ("Hello, Bitmap!", font, brush, new PointF(100, 700));
            }
        }
    }

    // Save the Bitmap to a file
    ("", ).

    ();

    // Release the Bitmap resource
    (); // Free the Bitmap resource.

    ("Bitmap saved as "); // Release the Bitmap resource.

    (); ("Bitmap saved as "); ()
    ().
}

exist(); Before adding a(); Intentionally do not destroy the bitmap to observe the memory consumption, really do not know, a look shocked, actually ate up to 1.7G of memory.

Next, press Enter to observe the size of the bitmap on the disk, actually as small as breathless 2M, the gap between the smack of 1000 times ah, screenshots are as follows:

This is the horror of bitmap, and what many programmers wonder about.

2. where the Bitmap eats up memory

Even though many of you know that it is unmanaged memory, it is necessary to show it with data, which is very simple to do with the!address -summary Observe the commit memory with!eeheap -gc Just observe the hosting pile.


0:006> !address -summary

--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_MAPPED                              168      200`03998000 (   2.000 TB)  88.58%    1.56%
MEM_PRIVATE                              96       42`01319000 ( 264.019 GB)  11.42%    0.20%
MEM_IMAGE                               265        0`03820000 (  56.125 MB)   0.00%    0.00%

--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE                                 73     7dbd`f7b1f000 ( 125.742 TB)           98.24%
MEM_RESERVE                              83      241`94389000 (   2.256 TB)  99.92%    1.76%
MEM_COMMIT                              446        0`74148000 (   1.814 GB)   0.08%    0.00%

0:006> !eeheap -gc

========================================
Number of GC Heaps: 1
----------------------------------------
....
------------------------------
GC Allocated Heap Size:    Size: 0x1d7f8 (120824) bytes.
GC Committed Heap Size:    Size: 0x45000 (282624) bytes.

It's clear from the trigramsMEM_COMMIT=1.814 GB at the same timeGC Committed Heap Size=2.8M , proper non-custodial leaks.

3. Can you find the memory segment to which the Bitmap belongs?

To find out how much memory is occupied by the bitmap, if you are debugging with windbg, you can use theKERNELBASE!VirtualAlloc The next bp breakpoint is sufficient, refer to the following:


0:000> k 5
 # Child-SP          RetAddr               Call Site
00 00000010`5257e198 00007ffb`c2ec7662     KERNELBASE!VirtualAlloc
01 00000010`5257e1a0 00007ffb`c2ec684b     gdiplus!GpMemoryBitmap::AllocBitmapData+0xc6
02 00000010`5257e1e0 00007ffb`c2e8a355     gdiplus!GpMemoryBitmap::AllocBitmapMemory+0x3f
03 00000010`5257e220 00007ffb`c2e8a47a     gdiplus!GpMemoryBitmap::InitNewBitmap+0x49
04 00000010`5257e260 00007ffb`c2e8a2cb     gdiplus!CopyOnWriteBitmap::CopyOnWriteBitmap+0x8a
...

But unfortunately you get a dump file, you can not use bp breakpoints, so what to do? As long as you have accumulated enough blessings in your life, you will not have to go to the end of the road, first of all, you can dig up the Bitmap from the hosting class.


0:000> !DumpObj /d 000001ef0b809648
Name:        
MethodTable: 00007ffa86f0cf90
EEClass:     00007ffa86f34760
Tracked Type: false
Size:        40(0x28) bytes
File:        D:\code\MyCode\ConsoleApplication1\bin\x64\Debug\net8.0\
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffa86e370a0  400019c       18          1 instance 000001EF08B222F0 _nativeImage
00007ffa86d85fa8  400019d        8          0 instance 0000000000000000 _userData
00007ffa86fc01a8  400019e       10        []  0 instance 0000000000000000 _rawData
00007ffa86f0cee8  4000014       10   1   static 0000000000000000 s_defaultTransparentColor

The field layout of the bitmap holds a reference to the native bitmap using the _nativeImage field, as evidenced by the screenshot below.

Having said that, what am I trying to say? Although I don't know the underlying source code of gdiplus, one thing I can confirm is that the ptr returned by VirtualAlloc and the _nativeImage here must have an offset relationship, possibly a first-class relationship, possibly a second-class relationship, which is summarized as follows under my memory address inspection:

  • In a Windows 10 x64 environment the offset is+0x570
  • In Windows 10 x86 environments the offset is+0x2e8

Next you can easily do the verification in windbg by first intercepting VirtualAlloc to find the large address segment.


0:000> bp KERNELBASE!VirtualAlloc ".if (@rdx>=0x200000) {  .printf  \"============ %lu bytes  ================\\n\",@rdx; k } .else {gc}"
breakpoint 0 redefined

0:000> g
============ 1764000000 bytes  ================
 # Child-SP          RetAddr               Call Site
00 00000060`d9f7e7b8 00007ffb`c2ec7662     KERNELBASE!VirtualAlloc
01 00000060`d9f7e7c0 00007ffb`c2ec684b     gdiplus!GpMemoryBitmap::AllocBitmapData+0xc6
02 00000060`d9f7e800 00007ffb`c2e8a355     gdiplus!GpMemoryBitmap::AllocBitmapMemory+0x3f
03 00000060`d9f7e840 00007ffb`c2e8a47a     gdiplus!GpMemoryBitmap::InitNewBitmap+0x49
04 00000060`d9f7e880 00007ffb`c2e8a2cb     gdiplus!CopyOnWriteBitmap::CopyOnWriteBitmap+0x8a
05 00000060`d9f7e8c0 00007ffb`c2e8a1b4     gdiplus!GpBitmap::GpBitmap+0x6b
06 00000060`d9f7e900 00007ffa`86e91f95     gdiplus!GdipCreateBitmapFromScan0+0xc4

0:000> pt
KERNELBASE!VirtualAlloc+0x5a:
00007ffb`c25df28a c3              ret

0:000> r
rax=0000020759db0000 rbx=0000000000014820 rcx=00007ffbc4acd3c4
rdx=0000000000000000 rsi=000000000026200a rdi=000001c6c4bb2d20
rip=00007ffbc25df28a rsp=00000060d9f7e7b8 rbp=0000000000005208
 r8=00000060d9f7e778  r9=0000000000005208 r10=0000000000000000
r11=0000000000000246 r12=0000000000005208 r13=0000000000000004
r14=0000000000005208 r15=0000000069248100
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
KERNELBASE!VirtualAlloc+0x5a:
00007ffb`c25df28a c3              ret

0:000> !address 0000020759db0000

Usage:                  <unknown>
Base Address:           00000207`59db0000
End Address:            00000207`c2ff9000
Region Size:            00000000`69249000 (   1.643 GB)
State:                  00001000          MEM_COMMIT
Protect:                00000004          PAGE_READWRITE
Type:                   00020000          MEM_PRIVATE
Allocation Base:        00000207`59db0000
Allocation Protect:     00000004          PAGE_READWRITE


Content source: 1 (target), length: 69249000

You can see from the trigram that the first address of the allocated address segment is0000020759db0000The parse is coming.Bitmap._nativeImage+0x570 Just do a validation at the place, you can see the echo, the output is as follows:


0:000> !DumpObj /d 000001c6c7409648
Name:        
MethodTable: 00007ffa86f4cf90
EEClass:     00007ffa86f74760
Tracked Type: false
Size:        40(0x28) bytes
File:        D:\code\MyCode\ConsoleApplication1\bin\x64\Debug\net8.0\
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffa86e770a0  400019c       18          1 instance 000001C6C4BB25B0 _nativeImage
00007ffa86dc5fa8  400019d        8          0 instance 0000000000000000 _userData
00007ffa870001a8  400019e       10        []  0 instance 0000000000000000 _rawData
00007ffa86f4cee8  4000014       10   1   static 0000000000000000 s_defaultTransparentColor

0:000> dp 000001C6C4BB25B0+0x570 L2
000001c6`c4bb2b20  00000207`59db0000 00000000`00000003

III: Summary

Bitmap can be very harmful when used incorrectly, so it's important to keep that in mind!Early release If you are unfortunate enough to have a lot of memory eaten, it is important to understand whether or not those unknown large memory segments are associated with Bitmap, so that you can find the real culprit as early as possible.
图片名称