- PVSM.RU - https://www.pvsm.ru -
Продолжаем тему интересного на .Net, от чего мир Java будет посмеиваться (хотя у них это также возможно сделать), а приверженцы С++ говорить: «чего они только не сделают чтобы не учить C++».
В данной заметке мы напишем по сути – простенькое ядрышко профилировщика памяти для платформы .Net, который будет снимать дамп с SOH кучи (а в перспективе и с LOH).
Для написания статьи нам понадобится код из статьи Получение указателя на объект .Net [1] и Ручное клонирование потока [2] (измерение размера объектов).
Наши цели на сегодня:
Ссылка на проект в GitHub: DotNetEx [3]
Как и в прошлый раз, будем решать проблемы по мере их поступления:
Как мы, наверное, знаем, в .Net существует два вида куч. Это куча для маленьких объектов и куча для больших объектов (> 85K). Они отличаются прежде всего организацией объектов внутри себя. Если в SOH объекты выделяются друг за другом, то в LOH все основано на связанных списках и таблице свободных промежутков между занятыми участками. Это то, что мы знаем. Но правда заключается в другом. Во-первых SOH не может быть непрерывной ввиду возможности наткнуться на занятый участок виртуальной памяти при ее расширении. Второе – на самом деле то, что объекты выделяются непрерывно друг за другом – красивое допущение, поскольку есть pinned объекты, которые нельзя двигать, а значит при сжатии кучи возникнут пустые промежутки. А это значит что SOH также содержит таблицу пустот. И по итогу это должно означать конкретно для нас что: (а) в памяти может быть несколько зон виртуальной памяти, которые выделены под кучу, (б) вероятнее всего они выделены через VirtualAlloc, (в) объекты находятся не непрерывно, а значит наш алгоритм не может на это полагаться.
Для упрощения примера, давайте будем снимать дамп только с объектов своей кучи. Для того чтобы это сделать, мы возмем указатель на любой объект и при помощи WinApi функции VirtualQuery попробуем получить регион виртуальной памяти, которая была выделена под эту кучу
public static void GetManagedHeap(out IntPtr heapsOffset, out IntPtr lastHeapByte)
{
// получаем указатель на любой объект. Для этого просто выделим его
var offset = EntityPtr.ToPointer(new object());
var memoryBasicInformation = new WinApi.MEMORY_BASIC_INFORMATION();
unsafe
{
WinApi.VirtualQuery(offset, ref memoryBasicInformation,
(IntPtr)Marshal.SizeOf(memoryBasicInformation));
heapsOffset = (IntPtr)memoryBasicInformation.AllocationBase;
lastHeapByte = (IntPtr)((long)offset + (long)memoryBasicInformation.RegionSize);
}
}
Прекрасно! Теперь мы знаем участок памяти, на котором искать объекты.
[StructLayout(LayoutKind.Explicit)]
public unsafe struct EntityInfo
{
[FieldOffset(0)]
public int SyncBlockIndex;
[FieldOffset(4)]
public MethodTableInfo *MethodTable;
}
[StructLayout(LayoutKind.Explicit)]
public struct RefTypeInfo
{
[FieldOffset(0)]
public EntityInfo BasicInfo;
[FieldOffset(8)]
public byte fieldsStart;
}
Далее посмотрим, как выглядит MathodTableInfo:
[StructLayout(LayoutKind.Explicit)]
public unsafe struct MethodTableInfo
{
#region Basic Type Info
[FieldOffset(0)]
public MethodTableFlags Flags;
[FieldOffset(4)]
public int Size;
[FieldOffset(8)]
public short AdditionalFlags;
[FieldOffset(10)]
public short MethodsCount;
[FieldOffset(12)]
public short VirtMethodsCount;
[FieldOffset(14)]
public short InterfacesCount;
[FieldOffset(16)]
public MethodTableInfo *ParentTable;
#endregion
[FieldOffset(20)]
public ObjectTypeInfo *ModuleInfo;
[FieldOffset(24)]
public ObjectTypeInfo *EEClass;
}
Здесь — только часть всей информации с этой структуры. На самом деле она более обширная. Нам тут важно поле EEClass, которое ведет на структуру описания типа. Его я практически не изучал. Выглядит содержимое примерно так:
[StructLayout(LayoutKind.Explicit)]
public unsafe struct ObjectTypeInfo
{
[FieldOffset(0)]
public ObjectTypeInfo *ParentClass;
[FieldOffset(16)]
public MethodTableInfo *MethodsTable;
}
Поскольку для нас важно только поле MethodsTable, я только его и нашел. Остальное — пропустил. Зачем оно нам? Это — обратная ссылка на MethodsTable, который своим полем EEClass ссылается сюда.
Таким вот не хитрым образом мы нашли несколько не точный, однако прекрасно работающий метод детектирования .Net объекта. Выглядит он так:
private static unsafe bool IsCorrectMethodsTable(IntPtr mt)
{
if (mt == IntPtr.Zero) return false;
if (PointsToAllocated(mt))
if (PointsToAllocated((IntPtr) ((MethodTableInfo*) mt)->EEClass))
if (PointsToAllocated((IntPtr) ((MethodTableInfo*) mt)->EEClass->MethodsTable))
return ((IntPtr) ((MethodTableInfo*) mt)->EEClass->MethodsTable == mt) ||
((IntPtr) ((MethodTableInfo*) mt)->ModuleInfo == MscorlibModule);
return false;
}
private static bool PointsToAllocated(IntPtr ptr)
{
if (ptr == IntPtr.Zero) return false;
return !WinApi.IsBadReadPtr(ptr, 32);
}
Для каждого указателя проверяется, указывает ли он на выделенный участок памяти. Это первый барьер. Второй барьер — если у теоретического объекта первое поле указывает на нечто, что мы изначально интерпретируем как MethodsTable. Проверяем что поле EEClass указывает на выделенный участок памяти и интерпретируем этот указатель как указатель на структуру ObjectTypeInfo, после чего проверяем, равен ли указатель на MethodsTable нашему найденному указателю (есть ли обратная ссылка?) Если все норм, объект найден.
Осталось только пройтись по всем участкам памяти и попробовать распознать там объекты. Я не буду выкладывать эту простыню, поскольку там также есть код для регистрации найденного и вывода на экран. Скажу только что задача решена, ссылка вывода из программы — ниже.
Ссылка на полный код примера: GitHub/DotNetEx/AdvSample [4]
00606 : System.String
00583 : System.Object
00277 : System.RuntimeType
00072 : System.Array+SZArrayEnumerator
00046 : System.Char[]
00041 : System.Int32[]
00033 : System.String[]
00032 : System.Object[]
00030 : System.Version
00029 : System.Byte[]
00024 : System.Text.StringBuilder
00023 : System.Collections.Hashtable+bucket[]
00020 : System.Security.PermissionSet
00020 : System.Collections.Hashtable
00020 : System.Reflection.AssemblyName
00014 : System.Reflection.RuntimeAssembly
00014 : System.Security.Permissions.EnvironmentPermission
00013 : System.Collections.Hashtable+SyncHashtable
00012 : System.Globalization.CompareInfo
00012 : System.Collections.Generic.Dictionary`2+Entry[[System.Type, mscorlib, Ve
rsion=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Securit
y.Policy.EvidenceTypeDescriptor, mscorlib, Version=4.0.0.0, Culture=neutral, Pub
licKeyToken=b77a5c561934e089]][]
00011 : System.Globalization.CultureInfo
00010 : System.Collections.ArrayList
00010 : System.Int32
00010 : System.Threading.ThreadStart
00010 : System.Internal.HandleCollector+HandleType
00010 : System.Internal.HandleCollector+HandleType
00009 : System.Security.Permissions.SecurityPermission
00009 : System.EventHandler
00009 : Microsoft.Win32.SafeHandles.SafeRegistryHandle
00009 : Microsoft.Win32.RegistryKey
00008 : System.Threading.Thread
00008 : Microsoft.Win32.SafeHandles.SafeWaitHandle
00008 : System.Security.Policy.EvidenceTypeDescriptor
00008 : System.Runtime.InteropServices.HandleRef
00008 : System.UInt16
00006 : System.Runtime.Remoting.Metadata.SoapTypeAttribute[]
00005 : System.Type[]
00005 : System.Threading.ReaderWriterLock
00005 : System.Reflection.CustomAttributeRecord[]
00005 : System.Globalization.TextInfo
00005 : System.Security.Permissions.EnvironmentStringExpressionSet
00005 : System.WeakReference
00005 : System.Threading.ThreadHelper
00004 : System.Security.FrameSecurityDescriptor
00004 : System.Reflection.RuntimeModule
00004 : System.Reflection.RuntimeConstructorInfo
00004 : System.Guid
00004 : Microsoft.Win32.SafeHandles.SafePEFileHandle
00004 : System.Security.Policy.Evidence
00004 : System.Collections.Generic.Dictionary`2[[System.Type, mscorlib, Version=
4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Security.Poli
cy.EvidenceTypeDescriptor, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKey
Token=b77a5c561934e089]]
00004 : System.Security.Policy.AssemblyEvidenceFactory
00004 : System.Security.Policy.Evidence+EvidenceUpgradeLockHolder
00003 : System.Security.Util.TokenBasedSet
00003 : System.Globalization.CultureData
00003 : System.Reflection.MemberFilter
00003 : System.Reflection.MethodBase[]
00003 : System.RuntimeType[]
00003 : System.Runtime.Remoting.Lifetime.LeaseLifeTimeServiceProperty
00003 : System.Attribute[]
00003 : System.Threading.ManualResetEvent
00003 : System.IO.PathHelper
00003 : System.Security.Permissions.UIPermission
00002 : System.AppDomainSetup
00002 : System.Security.PermissionToken
00002 : System.Runtime.Remoting.Contexts.IContextProperty[]
00002 : System.Reflection.TypeFilter
00002 : System.Collections.Queue
00002 : System.WeakReference[]
00002 : System.Char
00002 : System.Security.Policy.StrongName[]
00002 : System.Reflection.RuntimeMethodInfo
00002 : System.Threading.SynchronizationContext
00002 : System.Internal.HandleCollector+HandleType[]
00002 : System.Globalization.NumberFormatInfo
00002 : Microsoft.Win32.SystemEvents+SystemEventInvokeInfo[]
00002 : System.Text.EncoderReplacementFallback
00002 : System.IO.UnmanagedMemoryStream
00002 : System.Security.Permissions.FileIOAccess
00002 : System.Security.Util.StringExpressionSet
00001 : System.Exception
00001 : System.OutOfMemoryException
00001 : System.StackOverflowException
00001 : System.ExecutionEngineException
00001 : System.AppDomain
00001 : System.Security.PermissionTokenFactory
00001 : System.Security.PermissionToken[]
00001 : System.Globalization.CalendarData[]
00001 : System.Globalization.CalendarData
00001 : System.__Filters
00001 : System.DefaultBinder
00001 : System.RuntimeType+RuntimeTypeCache+MemberInfoCache`1[[System.Reflection
.RuntimeConstructorInfo, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyTo
ken=b77a5c561934e089]]
00001 : System.Reflection.RuntimeConstructorInfo[]
00001 : System.Reflection.ConstructorInfo[]
00001 : System.Collections.Generic.List`1[[System.Reflection.MethodBase, mscorli
b, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
00001 : System.Signature
00001 : System.Reflection.ParameterInfo[]
00001 : System.Int32[][]
00001 : System.Runtime.Remoting.Proxies.ProxyAttribute
00001 : System.Runtime.Remoting.DomainSpecificRemotingData
00001 : System.Runtime.Remoting.Channels.ChannelServicesData
00001 : System.Runtime.Remoting.Activation.LocalActivator
00001 : System.Runtime.Remoting.Activation.ActivationListener
00001 : System.Runtime.Remoting.Contexts.ContextAttribute[]
00001 : System.Runtime.Remoting.Contexts.Context
00001 : System.Runtime.Remoting.Messaging.ConstructorCallMessage
00001 : System.Runtime.Remoting.Metadata.RemotingTypeCachedData
00001 : System.Collections.Generic.Dictionary`2[[System.RuntimeType, mscorlib, V
ersion=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Runtim
eType, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e0
89]]
00001 : System.Collections.Generic.Dictionary`2+Entry[[System.RuntimeType, mscor
lib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.
RuntimeType, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c56
1934e089]][]
00001 : System.AttributeUsageAttribute
00001 : System.Runtime.Remoting.Metadata.SoapTypeAttribute
00001 : System.Runtime.Remoting.Activation.ConstructionLevelActivator
00001 : System.Runtime.Remoting.RemotingConfigHandler+RemotingConfigInfo
00001 : System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Versio
n=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Globalizati
on.CultureData, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5
c561934e089]]
00001 : System.Runtime.Remoting.ObjectHandle
00001 : System.Diagnostics.TraceSwitch
00001 : System.Collections.Generic.Dictionary`2[[System.Int16, mscorlib, Version
=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.IntPtr, msco
rlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
00001 : System.Collections.Generic.GenericEqualityComparer`1[[System.Int16, msco
rlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
00001 : System.Collections.Generic.ObjectEqualityComparer`1[[System.IntPtr, msco
rlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
00001 : System.Threading.Mutex
00001 : System.Runtime.CompilerServices.RuntimeHelpers+CleanupCode
00001 : System.Threading.Mutex+MutexCleanupInfo
00001 : System.Threading.Mutex+MutexTryCodeHelper
00001 : System.Threading.EventWaitHandle
00001 : System.Threading.HostExecutionContextManager
00001 : System.Collections.Generic.ObjectEqualityComparer`1[[System.Type, mscorl
ib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
00001 : System.Security.Policy.ApplicationTrust
00001 : System.Security.Policy.PolicyStatement
00001 : System.Collections.Generic.List`1[[System.Security.Policy.StrongName, ms
corlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
00001 : System.SZArrayHelper+SZGenericArrayEnumerator`1[[System.Security.Policy.
StrongName, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561
934e089]]
00001 : System.Collections.ObjectModel.ReadOnlyCollection`1[[System.Security.Pol
icy.StrongName, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5
c561934e089]]
00001 : Microsoft.Win32.Win32Native+OSVERSIONINFO
00001 : Microsoft.Win32.Win32Native+OSVERSIONINFOEX
00001 : System.OperatingSystem
00001 : System.__ComObject
00001 : System.Collections.Queue+SynchronizedQueue
00001 : System.Threading.AutoResetEvent
00001 : System.Threading.ContextCallback
00001 : System.RuntimeMethodInfoStub
00001 : System.RuntimeType+RuntimeTypeCache+MemberInfoCache`1[[System.Reflection
.RuntimeMethodInfo, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b
77a5c561934e089]]
00001 : System.Reflection.RuntimeMethodInfo[]
00001 : System.Collections.Generic.List`1[[System.Attribute, mscorlib, Version=4
.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
00001 : System.Drawing.SizeF
00001 : System.Drawing.Point
00001 : System.Windows.Forms.Application+ThreadContext
00001 : System.Windows.Forms.WindowsFormsSynchronizationContext
00001 : System.EventArgs
00001 : System.Windows.Forms.NativeMethods+WNDCLASS_D
00001 : Microsoft.Win32.UserPreferenceChangedEventHandler
00001 : System.Random
00001 : System.Collections.Generic.ObjectEqualityComparer`1[[System.Object, msco
rlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
00001 : System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Versio
n=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object[], m
scorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
00001 : Microsoft.Win32.SystemEvents
00001 : System.Runtime.Remoting.Messaging.LogicalCallContext
00001 : System.Text.UTF8Encoding
00001 : Microsoft.Win32.NativeMethods+WNDCLASS
00001 : System.Internal.HandleCollector+HandleType[]
00001 : System.IntPtr[]
00001 : System.Security.Util.URLString
00001 : System.Security.Permissions.FileIOPermission
00001 : System.Security.Util.LocalSiteString
00001 : System.Security.Util.DirectoryString
00001 : Microsoft.Win32.SafeHandles.SafeFileHandle
00001 : System.Text.SBCSCodePageEncoding
00001 : System.Text.InternalEncoderBestFitFallback
00001 : System.Text.InternalDecoderBestFitFallback
00001 : Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
00001 : Microsoft.Win32.SafeHandles.SafeFileMappingHandle
00001 : System.IO.__ConsoleStream
00001 : System.Text.EncoderNLS
00001 : System.IO.StreamWriter+MdaHelper
00001 : System.IO.TextWriter+SyncTextWriter
00001 : System.Diagnostics.Stopwatch
00001 : System.Predicate`1[[System.Int64, mscorlib, Version=4.0.0.0, Culture=neu
tral, PublicKeyToken=b77a5c561934e089]]
00001 : System.DBNull
Objects total: 2294. Time taken: 437
Конечно! Ведь мы смотрим на виртуальную память. Это раз… А второе… между доменами нет границ, объекты выделяются друг за другом даже при пересечении границы доменов. Разница — в коде. Потому можно, например, передать между доменами объект без сериализации.
Автор: sidristij
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/net/78997
Ссылки в тексте:
[1] Image: http://habrahabr.ru/company/luxoft/blog/219619/
[2] Image: http://habrahabr.ru/company/luxoft/blog/238947/
[3] Image: https://github.com/mumusan/dotnetex
[4] GitHub/DotNetEx/AdvSample: https://github.com/mumusan/dotnetex/blob/master/Advanced/03_catchingObjects/Program.cs
[5] Источник: http://habrahabr.ru/post/247447/
Нажмите здесь для печати.