如何通过WinDBG查找转储中的非托管内存中的内容

问题描述 投票:0回答:1

我在WinDbg命令中运行转储文件

!地址 - 简介

我的结果是这样的

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 );
}

}

和Windows 10中WinDbg预览的结果

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

我应该能够找到非托管分配的代码,分配1Gb内存。

heap windbg unmanaged dump sos
1个回答
6
投票

The basics

命令!address在非常低的级别上运行,几乎不在操作系统之上。但是,它会识别Windows附带的一点内存管理器:Windows堆管理器。

那么,你所看到的Heap是通过Windows堆管理器分配的内存。根据您的理解程度,这是本机堆。

任何其他堆管理器都将实现自己的内存管理。基本上它们都是相似的:它们从VirtualAlloc()获得大块内存,然后尝试对那个大块内的小块进行更好的管理。由于WinDbg不知道任何这些内存管理器,因此该内存被声明为<unknown>。它包括但不限于.NET的托管堆。有关其他潜在用途,请参阅this answer

Free是可能从操作系统声明的内存。这可能包括交换空间,而不仅仅是物理RAM。

Stack,我认为这很明显。

The heaps

我怎样才能找到堆中的内容?什么是对象或什么类型?

这个问题的答案很大程度上取决于你所谈论的堆。

Windows堆管理器(“本机堆”)只管理内存,不管理类型。在该级别上不可能区分相同大小但不同类型的两个对象。如果你有内存泄漏,你只能给出一个声明,如“我有一个n字节的泄漏”。要查找有关本机堆的更多信息,请从!heap -s开始查找其他!heap命令。

.NET托管堆保留了一个类型系统。要分析托管堆,需要WinDbg的扩展名为。通常你用.loadby sos clr加载它。它有一个命令!dumpheap -stat,可能会给你第一印象的能力。 (如果收到错误消息,请运行命令两次)

这应该为您提供足够的提示,以便进一步研究并在崩溃转储中找到更多详细信息。

Strange?

您似乎有230个堆栈,总共2.5 GB的内存。这是每个堆栈大约11 MB的内存。通常,这限制为1 MB。

您更新的示例代码

我编译了以下程序

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 GB(可能)的.NET内存和1 GB的本机内存。

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 GB。看看细节,我们看到只有一个大阵列和一些较小的阵列:

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 GB。这是有原因的。

如前所述,堆管理器善于将大块从VirtualAlloc()(64kB)分成更小的块。他们这样做是因为仅仅为4字节的int分配64kB将是一个很大的浪费。但是,不需要为大块创建堆管理结构。对于2 ^ 30 + 1字节的分配,OS将返回2 ^ 30 + 64kB,这意味着开销仅为0.006%。

这就是为什么你会发现> 512kB的分配不在通常的堆管理结构中,而是作为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 GB。

此时,值得注意的是用户模式堆栈跟踪数据库是无用的。它只适用于堆上的项目,但不适用于VirtualAlloc()。你不会弄清楚谁分配了1 GB块。

而且我忘了启用用户模式堆栈跟踪数据库。让我们这样做并交叉检查

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数据库仅为原生数据库而构建。这与使用k!dumpstack在.NET中使用不同堆栈的原因相同。

© www.soinside.com 2019 - 2024. All rights reserved.