I: Background
1. Storytelling
existNET Advanced Debugging
One of the biggest dangers of Bitmap is that it can cause programs to throw unthinkableOutOfMemoryException
NET 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 is0000020759db0000
The 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.