Как найти то, что находится в неуправляемой памяти в Dump от WinDBG - PullRequest
0 голосов
/ 07 марта 2019

Я запускаю файл дампа в команде WinDbg

! Address -summary

I результаты примерно такие

Usage Summary   RgnCount    Total Size  %ofBusy %ofTota

    Free            3739    7ff5`dbbae000 ( 127.960 Tb)                 99.97%
    <unknown>       1677    5`680a1000 (  21.626 Gb)    53.31%       0.02%
    Heap            20349   4`0049f000 (  16.005 Gb)    39.45%       0.01%
    Stack           230 0`a3e90000 (   2.561 Gb)    6.31%        0.00%

Как мне найти то, что в куче? Что такое объекты или типы?

Управляемая куча, а куча управляемая куча?

Очень сложно задавать подобные вопросы, поэтому я добавил дополнительную информацию

Вот мой пример кода C #

class Program
{

    public static int[] arr;
    public static AllocateUnmanagedMemory cls;


    static void Main(string[] args)
    {
        const int GBSize = 1 * 1024 * 1024 * 1024/ sizeof(int);

        Console.WriteLine("Allocating");

        arr = new int[GBSize];

        cls = new AllocateUnmanagedMemory();

        cls.UnmanagedAllocation();


        Console.ReadLine();
    }
}

Вот код неуправляемого распределения:

с использованием системы; использование System.Runtime.InteropServices;

открытый класс AllocateUnmanagedMemory {

static IntPtr pointer;

public void UnmanagedAllocation()
{
    pointer = Marshal.AllocHGlobal(1024 * 1024 * 1024 );
}

}

И результаты WinDbg Preview в Windows 10

-- Usage Summary RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free             59          762f7000 (   1.847 GB)           46.17%
<unknown>        98          4493e000 (   1.072 GB)  49.76%   26.79%
Heap             15          40158000 (   1.001 GB)  46.50%   25.03%
Image            174           2db2000 (  45.695 MB)   2.07%    1.12%
MappedFile       15           1c51000 (  28.316 MB)   1.28%    0.69%
Stack            24            800000 (   8.000 MB)   0.36%    0.20%

Я должен быть в состоянии найти этот код для неуправляемого распределения, выделенного 1 ГБ памяти.

1 Ответ

4 голосов
/ 08 марта 2019

Основы

Команда !address работает на очень низком уровне, чуть выше операционной системы. Тем не менее, он распознает немного менеджера памяти, который поставляется с Windows: Windows Heap Manager.

Итак, вы видите Heap, то есть память, которая была выделена через диспетчер кучи Windows. На вашем уровне понимания, это собственная куча .

Любые другие менеджеры кучи будут реализовывать свое собственное управление памятью. По сути, все они работают одинаково: они получают большие блоки памяти из VirtualAlloc(), а затем пытаются лучше обрабатывать небольшие блоки в этом большом блоке. Поскольку WinDbg не знает ни одного из этих менеджеров памяти, эта память объявляется как <unknown>. Он включает, но не ограничивается управляемой кучей .NET. Для других потенциальных применений см. этот ответ .

Free - это память, которая потенциально может быть запрошена операционной системой. Это может включать пространство подкачки, а не только физическую память.

Stack, ну, это очевидно, я думаю.

Кучи

Как я могу найти то, что в куче? Что такое объекты или типы?

Ответ на этот вопрос сильно зависит от того, о какой куче вы говорите.

Windows Heap Manager («собственная куча») просто управляет памятью и не управляет типами. На этом уровне невозможно различить два объекта одинакового размера, но разного типа. Если у вас есть утечка памяти, вы можете дать только утверждение типа «У меня есть утечка n байтов». Чтобы узнать больше о собственной куче, начните с !heap -s и найдите другие команды !heap.

Управляемая куча .NET сохраняет систему типов. Чтобы проанализировать управляемую кучу, вам нужно расширение для WinDbg с именем . Обычно вы загружаете его на .loadby sos clr. У него есть команда !dumpheap -stat, которая может дать вам первое представление о его возможностях. (Запустите команду дважды, если вы получите сообщение об ошибке)

Это должно дать вам достаточно советов для дальнейшего исследования и получения дополнительной информации в вашей аварийной копии.

Странно?

Похоже, у вас 230 стеков с общим объемом памяти 2,5 ГБ. Это около 11 МБ памяти на стек. Обычно это ограничено 1 МБ.

Ваш обновленный пример кода

Я скомпилировал следующую программу

using System;
using System.Runtime.InteropServices;
namespace SO55043889
{
    class Program
    {
        public static int[] arr;
        static IntPtr pointer;
        static void Main()
        {
            const int GBSize = 1 * 1024 * 1024 * 1024/ sizeof(int);
            Console.WriteLine("Allocating");
            arr = new int[GBSize];
            pointer = Marshal.AllocHGlobal(1024 * 1024 * 1024 );
            Console.ReadLine();
            Console.WriteLine(pointer.ToInt32() + arr[0]);
        }
    }
}

Я запустил приложение и подключился к процессу с помощью WinDbg. Я взял дамп, используя

0:000> .dump /ma SO55043889.dmp

и теперь мы можем проанализировать это так:

0:000> !address -summary
[...]
<unknown>                               106          474f4000 (   1.114 GB)  51.58%   27.86%
Heap                                     13          401e1000 (   1.002 GB)  46.38%   25.05%
[...]

Итак, мы видим 1 ГБ (потенциально) памяти .NET и 1 ГБ встроенной памяти.

0:000> .loadby sos clr
0:000> !dumpheap -stat
c0000005 Exception in C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dumpheap debugger extension.
  PC: 04f6fa73  VA: 00000000  R/W: 0  Parameter: 00000000
0:000> *** This is normal, just do it again
0:000> !dumpheap -stat
[...]
70d20958       12   1073742400 System.Int32[]
Total 335 objects

На стороне .NET имеется 12 int [], что в общей сложности составляет ~ 1 ГБ из управляемой кучи. Глядя на детали, мы видим, что есть только один большой массив и несколько меньших:

0:000> !dumpheap -type System.Int32[]
 Address       MT     Size
020e1ff8 70d20958      300     
020e2130 70d20958       24     
020e2184 70d20958       40     
020e2228 70d20958       80     
020e2d9c 70d20958       16     
020e2dac 70d20958       16     
020e2df8 70d20958       16     
020e386c 70d20958       24     
020e3d54 70d20958       16     
020e3d64 70d20958       16     
020e3d74 70d20958       16     
04811010 70d20958 1073741836     

Statistics:
      MT    Count    TotalSize Class Name
70d20958       12   1073742400 System.Int32[]
Total 12 objects

Это не то, что вы хотели знать. Я только что показал вам, как это просто на стороне .NET.

Теперь родная сторона:

0:004> !heap -s
LFH Key                   : 0x7f8d0cc6
Termination on corruption : ENABLED
  Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast 
                    (k)     (k)    (k)     (k) length      blocks cont. heap 
-----------------------------------------------------------------------------
Virtual block: 80010000 - 80010000 (size 00000000)
00550000 00000002    1024    504   1024     14    17     1    1      0   LFH
002d0000 00001002      64     16     64      2     2     1    0      0      
00820000 00041002     256      4    256      2     1     1    0      0      
00750000 00001002      64     20     64      7     2     1    0      0      
00710000 00001002     256      4    256      0     1     1    0      0      
001e0000 00041002     256      4    256      2     1     1    0      0      
-----------------------------------------------------------------------------

Мы не можем видеть 1 ГБ здесь. И этому есть причина.

Как объяснено ранее, менеджеры кучи способны делить большие блоки из VirtualAlloc() (которые составляют 64 КБ) на более мелкие части. Они делают это, потому что было бы большой тратой выделить 64 КБ только на 4 байта int. Однако нет необходимости создавать структуру управления кучей для больших блоков. При выделении 2 ^ 30 + 1 байт ОС вернула бы 2 ^ 30 + 64 КБ, что означает, что издержки составляют всего 0,006%.

Вот почему вы найдете выделения> 512 КБ не внутри обычных структур управления кучей, а как Virtual block, что означает, что диспетчер кучи Windows просто перенаправил запрос на VirtualAlloc().

Здесь есть еще одна проблема: выход для size не работает. Там написано

(size 00000000)

что явно не соответствует действительности. Давайте посмотрим на это сами:

0:004> !address 80010000 
    Usage:                  Heap
    Base Address:           80010000
    End Address:            c0011000
    Region Size:            40001000
    [...]

    0:004> ? c0011000-80010000
    Evaluate expression: 1073745920 = 40001000

Здесь мы видим, что End Adress - Base Address равен Region Size, а размер составляет 1 ГБ.

На данный момент стоит отметить, что база данных трассировки стека в пользовательском режиме бесполезна. Это относится только к предметам в куче, но не VirtualAlloc(). Вы не узнаете, кто выделил блок размером 1 ГБ.

И я все равно забыл включить базу данных трассировки стека в пользовательском режиме. Давайте сделаем это и сделаем перекрестную проверку

0:000> !gflag
Current NtGlobalFlag contents: 0x00001000
ust - Create user mode stack trace database

А теперь должны быть следы стека для небольших кусков памяти. В этом примере я использую произвольный блок размером 0x208:

0:000> !heap -flt s 208
    _HEAP @ 2a0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        002c9818 0044 0000  [00]   002c9830    00208 - (busy)
        002cd1e8 0044 0044  [00]   002cd200    00208 - (busy)
        002d5ad0 0044 0044  [00]   002d5ae8    00208 - (busy)
        002f0c48 0044 0044  [00]   002f0c60    00208 - (busy)
        0032c210 0044 0044  [00]   0032c228    00208 - (busy)
        00351c90 0044 0044  [00]   00351ca8    00208 - (busy)
0:000> *** Use any UserPtr number, I use the last one
0:000> !heap -p -a 00351ca8    
    address 00351ca8 found in
    _HEAP @ 2a0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        00351c90 0044 0000  [00]   00351ca8    00208 - (busy)
        779dd909 ntdll!RtlAllocateHeap+0x00000274
        71e18bc7 clr!EEHeapAlloc+0x0000002c
        71e18c0a clr!EEHeapAllocInProcessHeap+0x0000005b
        71e18ba6 clr!ClrAllocInProcessHeap+0x00000023
        71e2dd26 clr!StackingAllocator::AllocNewBlockForBytes+0x00000082
        71e2dd76 clr!operator new+0x00000063
        71e93ace clr!MethodTableBuilder::BuildMethodTableThrowing+0x00000059
        71e94590 clr!ClassLoader::CreateTypeHandleForTypeDefThrowing+0x0000083a
        71e2e956 clr!ClassLoader::CreateTypeHandleForTypeKey+0x000000ad
        71e2e99a clr!ClassLoader::DoIncrementalLoad+0x000000c2
        71e2e418 clr!ClassLoader::LoadTypeHandleForTypeKey_Body+0x00000505
        71e2e5a7 clr!ClassLoader::LoadTypeHandleForTypeKey+0x000000b5
        71e2f723 clr!ClassLoader::LoadTypeDefThrowing+0x00000318
        71e2a974 clr!ClassLoader::LoadTypeDefOrRefThrowing+0x0000024c
        71f57811 clr!Assembly::GetEntryPoint+0x0000022f
        71f856e0 clr!Assembly::ExecuteMainMethod+0x000000b3
        71f855ed clr!SystemDomain::ExecuteMainMethod+0x00000631
        71f858d3 clr!ExecuteEXE+0x0000004c
        71f85819 clr!_CorExeMainInternal+0x000000dc
        71f55a0c clr!_CorExeMain+0x0000004d
        7251d93b mscoreei!_CorExeMain+0x0000010e
        72597f16 MSCOREE!ShellShim__CorExeMain+0x00000099
        72594de3 MSCOREE!_CorExeMain_Exported+0x00000008
        77999802 ntdll!__RtlUserThreadStart+0x00000070
        779997d5 ntdll!_RtlUserThreadStart+0x0000001b

Еще одно замечание: если вы измените программу, чтобы иметь меньшие блоки памяти, например,

for (int i = 0; i < 1000; i++)
{
    pointer = Marshal.AllocHGlobal(3*1024 );
}

Вы увидите распределение в куче:

0:004> ? 3*0n1024
Evaluate expression: 3072 = 00000c00
0:004> !heap -flt c00
cound not parse flt criteria -flt c00
0:004> !heap -flt s c00
    _HEAP @ 67c0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        0686b668 0183 0000  [00]   0686b680    00c00 - (busy)
        0686efa8 0183 0183  [00]   0686efc0    00c00 - (busy)
[...]

И вы увидите следы стека

0:004> !heap -p -a 4d0fdf18    
    address 4d0fdf18 found in
    _HEAP @ 67c0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        4d0fdf00 0191 0000  [00]   4d0fdf18    00c00 - (busy)
        779dd909 ntdll!RtlAllocateHeap+0x00000274
        768f5aae KERNELBASE!LocalAlloc+0x0000005f
        70c6ad4f mscorlib_ni+0x003fad4f
        7138c4da mscorlib_ni+0x00b1c4da
        71e0ebb6 clr!CallDescrWorkerInternal+0x00000034
        71e11e10 clr!CallDescrWorkerWithHandler+0x0000006b
        71e17994 clr!MethodDescCallSite::CallTargetWorker+0x0000016a
        71f85026 clr!RunMain+0x000001ad
        71f85707 clr!Assembly::ExecuteMainMethod+0x00000124
[...]

Но вы не увидите вызовов управляемого метода. Это связано с тем, что база данных USt была создана только для собственного использования. По той же причине у вас есть разные стеки в .NET, использующие k или !dumpstack.

...