Dela via


UMDH is not perfect to analyze native memory leaks in .NET applications

By using UMDH, you can identify the calls that result in the largest leak of native memory. The tool is a perfect killer of native memory leaks in native applications. However, if the native memory leak happens in a .NET application, UMDH is sometimes not that useful because of its incapability to interpret .NET call stack.

Native memory leaks in .NET applications are usually caused by .NET - native interop, thus the stack trace of the culprit calls should include some .NET functions and modules in most cases. Take the following C# code snippet that causes 100K native memory leak as an example.

   1: static void Main(string[] args)
  2: {
  3:     Console.ReadLine(); // Take a UMDH log here
  4:     for (int i = 1; i < 100; i++)
  5:     {
  6:         Marshal.AllocHGlobal(1000); // Leak 1KB memory
  7:     }
  8:     Console.ReadLine(); // Take a UMDH log here
  9: } 

If you take UMDH logs at the two Console.ReadLine() positions and compare the logs, you will get this stack trace from UMDH:

+ 182b8 ( 182b8 - 0) 63 allocs    BackTrace720EC8
+ 63 ( 63 - 0)    BackTrace720EC8    allocations

    ntdll!RtlAllocateHeap+00000274
    KERNELBASE!LocalAlloc+0000005F
    mscorlib.ni!???+00000000 : 584E5567
mscorlib.ni!???+00000000 : 58968C45

    mscorwks!CallDescrWorker+00000033
    mscorwks!CallDescrWorkerWithHandler+000000A3
    mscorwks!MethodDesc::CallDescr+0000019C
    mscorwks!MethodDesc::CallTargetWorker+0000001F
    mscorwks!MethodDescCallSite::CallWithValueTypes_RetArgSlot+0000001A
    mscorwks!ClassLoader::RunMain+00000223
    mscorwks!Assembly::ExecuteMainMethod+000000A6
    mscorwks!SystemDomain::ExecuteMainMethod+00000456
    mscorwks!ExecuteEXE+00000059
    mscorwks!_CorExeMain+0000015C
    MSCOREE!CorExeMain+00000034
    KERNEL32!BaseThreadInitThunk+0000000E
    ntdll!__RtlUserThreadStart+00000070
    ntdll!_RtlUserThreadStart+0000001B

Out of expectation, you do not see any meaning managed calls, such as Marshal.AllocHGlobal and the call of Program.Main, in the UMDH output. The call stack is almost useless to find the culprit .NET code because UMDH cannot walk .NET stacks. If you had dump from the time of the UMDH trace you might be able to retrospectively infer where in the managed code was making the call by doing things like

0:000> !ip2md 584E5567
MethodDesc: 582f6c28
Method Name: System.Runtime.InteropServices.Marshal.AllocHGlobal(IntPtr)
Class: 582db048
MethodTable: 5851a324
mdToken: 060031b1
Module: 582b1000
IsJitted: yes
CodeAddr: 584e5510

0:000> !ip2md 58968C45
MethodDesc: 582f6c34
Method Name: System.Runtime.InteropServices.Marshal.AllocHGlobal(Int32)
Class: 582db048
MethodTable: 5851a324
mdToken: 060031b2
Module: 582b1000
IsJitted: yes
CodeAddr: 58968c40

There is no method to find the real culprit function in the above example: Program.Main.

 

Jialiang ^JLG , CLR expert,  3 Star Contributor at forum 

If you want more information shared by our team, please follow us @  Twitter

Comments

  • Anonymous
    November 17, 2013
    I can only agree with the above observation. It get even worse if the leaking is from Managed C++ calling operator new(). Then the callstack simply stops at MSCVR.DLL!operator new() and no more hints are shown. (Apparently because the call to operator new() origins from JIT compiled code) Regards  Lars