An analysis of unmanaged memory leakage in a. Net desktop Qixia game

Time:2021-9-26

1: Background

1. Tell a story

To be honest, I didn’t intend to interpret this dump in the last article, but it deeply moved me on two points.

  1. I’ve heard countless times that unity can be used for game development, but seeing is better than hearing.

  2. There are many names in the game that are unique to Jin Yong’s martial arts novels. It’s so pleasing to the eye.

000000df315978a8 0 3 jade bone fan
000000df31597cd8 0 3 Yunlong gun
000000df31596d88 0 3 Yin wind claw
000000df315967a8 0 4 snow soul silk chain
000000df31596ad0 0 0 4 ethyl wood divine sword
000000df31596040 0 3-star Yaoguan
000000df31595328 0 3 black gold hammer
...

So for such a good dump, I have to leave something for it.

Well, in other words, this fate began last month. A friend said that its program uses a lot of virtual memory. Ask how to solve it, as shown in the figure below:

No matter what the problem is, it’s never wrong to catch more dumps. After several tosses, I sent a dump.

2: WinDbg analysis

1. Where is the leak

Analyze the memory problem, or that sentence. Divide it into two to see which memory leak is (managed or unmanaged).

Let’s take a look at the total memory of the process!address -summaryCommand.

0:087> !address -summary

--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free                                    458     7ffe`9e6a8000 ( 127.995 TB)          100.00%
Heap                                  48514        1`005fd000 (   4.006 GB)  72.51%    0.00%
                              2504        0`2c6ad000 ( 710.676 MB)  12.56%    0.00%
Stack                                   504        0`2a000000 ( 672.000 MB)  11.88%    0.00%
Image                                   410        0`0a971000 ( 169.441 MB)   3.00%    0.00%
Other                                    18        0`001dc000 (   1.859 MB)   0.03%    0.00%
TEB                                     168        0`00150000 (   1.312 MB)   0.02%    0.00%
PEB                                       1        0`00001000 (   4.000 kB)   0.00%    0.00%

--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_PRIVATE                           51581        1`5130f000 (   5.269 GB)  95.36%    0.00%
MEM_IMAGE                               416        0`0aa6b000 ( 170.418 MB)   3.01%    0.00%
MEM_MAPPED                              122        0`05bce000 (  91.805 MB)   1.62%    0.00%

--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE                                458     7ffe`9e6a8000 ( 127.995 TB)          100.00%
MEM_COMMIT                            51465        1`1c741000 (   4.445 GB)  80.45%    0.00%
MEM_RESERVE                             654        0`45207000 (   1.080 GB)  19.55%    0.00%

From the divinationMEM_COMMIT=4.4GNext, let’s look at the memory usage of the managed heap. You can use the command!eeheap -gcCommand.

0:087> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x000000df3118dc48
generation 1 starts at 0x000000df3118b098
generation 2 starts at 0x000000df30fc1000
ephemeral segment allocation context: none
         segment             begin         allocated              size
000000df30fc0000  000000df30fc1000  000000df3178cae0  0x7cbae0(8174304)
Large object heap starts at 0x000000df40fc1000
         segment             begin         allocated              size
000000df40fc0000  000000df40fc1000  000000df410637b8  0xa27b8(665528)
Total Size:              Size: 0x86e298 (8839832) bytes.
------------------------------
GC Heap Size:            Size: 0x86e298 (8839832) bytes.

From the divinationGC Heap Size= 8839832 Byte = 8M, I’ll go. That’s it. It’s a little joking, Obviously, this is an unmanaged memory leak. Since the direction has been set, check the unmanaged area!

2. Explore unmanaged leaks

As a rule of thumb, look for unmanaged leaks firstLoader heap, many programs are often caused by dynamically creating too many assemblies, such as the classic castle and XmlSerializer. Interested friends can find this information online, which can be used here!eeheap -loaderCommand view.

0:087> !eeheap -loader

--------------------------------------
Jit code heap:
LoaderCodeHeap:    0000000000000000(0:0) Size: 0x0 (0) bytes.
Total size:        Size: 0x0 (0) bytes.
--------------------------------------
Module Thunk heaps:
Module 00007ffda5fa1000: Size: 0x0 (0) bytes.
Module 00007ffd485c4148: Size: 0x0 (0) bytes.
Module 00007ffda2631000: Size: 0x0 (0) bytes.
Module 00007ffda5331000: Size: 0x0 (0) bytes.
Module 00007ffdac621000: Size: 0x0 (0) bytes.
Module 00007ffdac4e1000: Size: 0x0 (0) bytes.
Module 00007ffda48b1000: Size: 0x0 (0) bytes.
Module 00007ffda1791000: Size: 0x0 (0) bytes.
Module 00007ffd487b1858: Size: 0x0 (0) bytes.
Total size:              Size: 0x0 (0) bytes.
--------------------------------------
Module Lookup Table heaps:
Module 00007ffda5fa1000: Size: 0x0 (0) bytes.
Module 00007ffd485c4148: Size: 0x0 (0) bytes.
Module 00007ffda2631000: Size: 0x0 (0) bytes.
Module 00007ffda5331000: Size: 0x0 (0) bytes.
Module 00007ffdac621000: Size: 0x0 (0) bytes.
Module 00007ffdac4e1000: Size: 0x0 (0) bytes.
Module 00007ffda48b1000: Size: 0x0 (0) bytes.
Module 00007ffda1791000: Size: 0x0 (0) bytes.
Module 00007ffd487b1858: Size: 0x0 (0) bytes.
Total size:              Size: 0x0 (0) bytes.
--------------------------------------
Total LoaderHeap size:   Size: 0x99000 (626688) bytes total, 0x2000 (8192) bytes wasted.
=======================================

From the output:Total LoaderHeap size= 626K, it seems that you’ve lost your step this time. Then go to the difficult mode and look at the Windows NT heap. It’s used here!heap -sCommand.

0:087> !heap -s


************************************************************************************************************************
                                              NT HEAP STATS BELOW
************************************************************************************************************************
LFH Key                   : 0xb6c37b3e3a4a189e
Termination on corruption : ENABLED
          Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast 
                            (k)     (k)    (k)     (k) length      blocks cont. heap 
-------------------------------------------------------------------------------------
000000df2e680000 00000002 4145084 4130108 4144304   1537   775   260    1      4   LFH
000000df2e1f0000 00008000      64      4     64      2     1     1    0      0      
000000df2e830000 00001002    1860    172   1080     15     5     2    0      0   LFH
000000df2ec80000 00001002    1860    236   1080      5     7     2    0      0   LFH
000000df309e0000 00001002      60      8     60      2     1     1    0      0      
000000df30bb0000 00041002      60      8     60      5     1     1    0      0      
000000df49bd0000 00001002     840     44     60      3     3     1    0      0   LFH
000000df49b20000 00041002    1860     96   1080      8     3     2    0      0   LFH
000000df30b40000 00001002      60     20     60      9     2     1    0      0      
000000df30b30000 00001002    1860    152   1080     11     8     2    0      0   LFH
000000df4bbb0000 00001002    3904   1292   3124     49     6     3    0      0   LFH
000000df89920000 00001002    1860    372   1080     14     7     2    0      0   LFH
000000df89be0000 00001006    1860    280   1080     23     2     2    0      0   LFH
000000df56f40000 00001006   32372  26204  31592   1434    21     6    0     6b   LFH
000000df56f10000 00001006    1860    176   1080     21     3     2    0      0   LFH
000000df89ac0000 00001006    3904   2160   3124     67     4     3    0     2e   LFH
-------------------------------------------------------------------------------------

From the output information: the memory of the original program isheap=000000df2e680000It’s sucked away, so dig it deep. It’s used here!heap -stat -h 000000df2e680000Command to see the statistics of the heap.

0:087>  !ext.heap -stat -h 000000df2e680000
 heap @ 000000df2e680000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    2000 4cfd2 - 99fa4000  (68.76)
    58 9d7492 - 36201230  (24.17)
    12c 267e8 - 2d1c3e0  (1.26)
    21d1 c46 - 19f0b26  (0.72)
    4020 634 - 18dc680  (0.69)
    a0 26d00 - 1842000  (0.68)
    a 1d3ebb - 124734e  (0.51)
    10 f8d99 - f8d990  (0.43)
    6 16adae - 881214  (0.24)
    b b3508 - 7b4758  (0.22)
    7 115125 - 793803  (0.21)
    5 17b833 - 7698ff  (0.21)
    c 86027 - 6481d4  (0.18)
    9 afef9 - 62f6c1  (0.17)
    d 6a80f - 5688c3  (0.15)
    f 4f5a9 - 4a64e7  (0.13)
    e 54814 - 49f118  (0.13)
    8 8b092 - 458490  (0.12)
    13 3139b - 3a7481  (0.10)
    15 25d06 - 31a17e  (0.09)

From the output information, this heap is mainlysize=2000andsize=58It’s filled. After all, they account for the majority68.76 + 24.17 = 92.93, so it’s necessary to dig them. Next, use the command!heap -flt s 2000Find the first address of all these blocks in the heap.

0:087>  !ext.heap -flt s 2000
    _HEAP @ df2e680000
              HEAP_ENTRY Size Prev Flags            UserPtr UserSize - state
        000000df2e702dd0 0201 0000  [00]   000000df2e702de0    02000 - (busy)
        000000df2e72c7e0 0201 0201  [00]   000000df2e72c7f0    02000 - (busy)
        000000df517400c0 0201 0201  [00]   000000df517400d0    02000 - (busy)
        000000df517420d0 0201 0201  [00]   000000df517420e0    02000 - (busy)
        000000df517440e0 0201 0201  [00]   000000df517440f0    02000 - (busy)
        000000df517460f0 0201 0201  [00]   000000df51746100    02000 - (busy)
        000000df51748100 0201 0201  [00]   000000df51748110    02000 - (busy)
        000000df5174a110 0201 0201  [00]   000000df5174a120    02000 - (busy)
        000000df5174c120 0201 0201  [00]   000000df5174c130    02000 - (busy)
        000000df5174e130 0201 0201  [00]   000000df5174e140    02000 - (busy)
        000000df51750140 0201 0201  [00]   000000df51750150    02000 - (busy)
        ...

Heap above_ Entry is the first address of the block, because such a block probably has4cfd2=31.5wI can’t list them one by one. The next step is to use DC to observe the memory block contents of these blocks to find the rules. It must be too troublesome to manually. I still have to use the script. Here, I’d better take the first 1W items to check.

function show_all_blocksize() {

    var output = exec("!ext.heap -flt s 58").Take(10000);
    for (var line of output) {

        var heap_entry_address = line.trim().split(' ')[0];

        if (heap_entry_address.indexOf("00") == -1) continue;

        show_heap_entry(heap_entry_address);
    }
}

function show_heap_entry(heap_entry_address) {

    var pageIndex = (index++);

    var path = ".writemem D:\\file\\"+ pageIndex + ".txt " + heap_entry_address + " L?0x58";

    var output = exec(path);

    log("pageIndex=" + pageIndex);
}

After executing the script to generate TXT, the screenshot is as follows:

Through observation, it is found that there is a large amount of user information in the heap, and then use this information to verify friends.

After simple communication with my friends, I can only help here and close the case here.

3: Summary

The cause of this accident is the unmanaged leak caused by c# calling Lua without reasonable memory release. How to release it in the code layer depends on the luck of your friends.

Finally, a small colored egg. It’s very kind of my friend.

I’ve never seen such a big red envelope. I actually accepted it and gave it to the company’s R & D partnerA cup of afternoon tea for one person, say thanks to your friends here

图片名称

Recommended Today

Supervisor

Supervisor [note] Supervisor – H view supervisor command help Supervisorctl – H view supervisorctl command help Supervisorctl help view the action command of supervisorctl Supervisorctl help any action to view the use of this action 1. Introduction Supervisor is a process control system. Generally speaking, it can monitor your process. If the process exits abnormally, […]