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