Project Settings -> Debug -> Enable native code debugging
class Program { static void Main(string[] args) { ICallable target = new FirstCallableImpl(); CallInterface(target); ICallable target2 = new SecondCallableImpl(); CallInterface(target2); } [MethodImpl(MethodImplOptions.NoInlining)] private static void CallInterface(ICallable callable) { for (int i = 0; i < 1000000; i++) { callable.DoSomething(); // place breakpoint } } } interface ICallable { void DoSomething(); } class FirstCallableImpl : ICallable { public void DoSomething() { } } class SecondCallableImpl : ICallable { public void DoSomething() { } }
call dword ptr ds:[00450010h]
.Immediate window -> .load sos
!u 00457012 Unmanaged code 00457012 813908314400 cmp dword ptr [ecx],443108h 00457018 0F85F32F0000 jne 0045A011 0045701E E9BD901D00 jmp 006300E0
jmp 006300E0
is a call to the required interface method. Check: !u 006300E0 Normal JIT generated code ConsoleApplication1.FirstCallableImpl.DoSomething() Begin 006300e0, size 1 >>> 006300E0 C3 ret
cmp dword ptr [ecx],443108h
? !DumpMT 443108 EEClass: 00441378 Module: 00442c5c Name: ConsoleApplication1.FirstCallableImpl mdToken: 02000004 (C:\*path to project*\InterfaceStubsTest.exe) BaseSize: 0xc ComponentSize: 0x0 Number of IFaces in IFaceMap: 1 Slots in VTable: 6
jne 0045A011
is a fallback to a linear search, as it was before caching.Caching is very efficient if we call methods on collections, for example.
class Program { static void Main(string[] args) { var refTypeHolder = new HolderOf<object>(null); var intTypeHolder = new HolderOf<int>(0); // call JIT refTypeHolder.GetPointer(); intTypeHolder.GetPointer(); Console.Read(); // place breakpoint } } class HolderOf<T> { private readonly T _pointer; public HolderOf(T pointer) { _pointer = pointer; } public T GetPointer() { return _pointer; } }
!dumpheap
: .load sos.dll !dumpheap -type HolderOf PDB symbol for mscorwks.dll not loaded Address MT Size 02d332c8 00f531e0 12 02d332d4 00f53268 12 total 2 objects Statistics: MT Count TotalSize Class Name 00f53268 1 12 ConsoleApplication1.HolderOf`1[[System.Int32, mscorlib]] 00f531e0 1 12 ConsoleApplication1.HolderOf`1[[System.Object, mscorlib]] Total 2 objects
HolderOf<T>
class !dumpmt -md 00f53268 EEClass: 00f514cc Module: 00f52c5c Name: ConsoleApplication1.HolderOf`1[[System.Int32, mscorlib]] mdToken: 02000006 (C:\*path to samples*\InterfaceStubsTest.exe) BaseSize: 0xc ComponentSize: 0x0 Number of IFaces in IFaceMap: 0 Slots in VTable: 6 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 66ae6a30 66964968 PreJIT System.Object.ToString() 66ae6a50 66964970 PreJIT System.Object.Equals(System.Object) 66ae6ac0 669649a0 PreJIT System.Object.GetHashCode() 66b57940 669649c4 PreJIT System.Object.Finalize() 00f5c088 00f53250 JIT ConsoleApplication1.HolderOf`1[[System.Int32, mscorlib]]..ctor(Int32) 00f5c090 00f5325c NONE ConsoleApplication1.HolderOf`1[[System.Int32, mscorlib]].GetPointer()
!dumpmt -md 00f531e0 EEClass: 00f51438 Module: 00f52c5c Name: ConsoleApplication1.HolderOf`1[[System.Object, mscorlib]] mdToken: 02000006 (C:\*path to samples*\InterfaceStubsTest.exe) BaseSize: 0xc ComponentSize: 0x0 Number of IFaces in IFaceMap: 0 Slots in VTable: 6 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 66ae6a30 66964968 PreJIT System.Object.ToString() 66ae6a50 66964970 PreJIT System.Object.Equals(System.Object) 66ae6ac0 669649a0 PreJIT System.Object.GetHashCode() 66b57940 669649c4 PreJIT System.Object.Finalize() 00f5c068 00f53154 JIT ConsoleApplication1.HolderOf`1[[System.__Canon, mscorlib]]..ctor(System.__Canon) 00f5c070 00f53160 NONE ConsoleApplication1.HolderOf`1[[System.__Canon, mscorlib]].GetPointer()
HolderOf<T>.GetPointer()
. Consider: !dumpmd 00f5325c Method Name: ConsoleApplication1.HolderOf`1[[System.Int32, mscorlib]].GetPointer() Class: 00f514cc MethodTable: 00f53268 mdToken: 0600000b Module: 00f52c5c IsJitted: yes CodeAddr: 01090318
!dumpmd 00f53160 Method Name: ConsoleApplication1.HolderOf`1[[System.__Canon, mscorlib]].GetPointer() Class: 00f51438 MethodTable: 00f53178 mdToken: 0600000b Module: 00f52c5c IsJitted: yes CodeAddr: 010902b8
HolderOf <object> | HolderOf <int> | |
---|---|---|
MethodDesc | 00f53160 | 00f5325c |
CodeAddr | 01090318 | 010902b8 |
Initiation type | HolderOf`1 [[System .__ Canon, mscorlib]] | HolderOf`1 [[System.Int32, mscorlib]] |
[Serializable()] [ClassInterface(ClassInterfaceType.AutoDual)] [ComVisible(true)] internal class __Canon { }
In short, it is usually said that for reference types the medium uses the Canon System .__ type to share the code.
But that's not the point. Seriously.
class GenericClassOne<T> { private T field; } class GenericClassTwo<U> { private GenericClassThree<GenericClassOne<U>> field } class GenericClassThree<S> { private GenericClassTwo<GenericClassOne<S>> field } class Program { static void Main(string[] args) { Console.WriteLine((new GenericClassTwo<object>()).ToString()); Console.Read(); } }
enum ClassLoadLevel { CLASS_LOAD_BEGIN, CLASS_LOAD_UNRESTOREDTYPEKEY, CLASS_LOAD_UNRESTORED, CLASS_LOAD_APPROXPARENTS, CLASS_LOAD_EXACTPARENTS, CLASS_DEPENDENCIES_LOADED, CLASS_LOADED, CLASS_LOAD_LEVEL_FINAL = CLASS_LOADED, };
sscli20/clr/src/vm/Generics.cpp
file: BOOL Generics::CheckInstantiationForRecursion(const unsigned int nGenericClassArgs, const TypeHandle pGenericArgs[]) { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; if (nGenericClassArgs == 0) return TRUE; _ASSERTE(pGenericArgs); struct PerIterationData { const TypeHandle * genArgs; int index; int numGenArgs; }; PerIterationData stack[MAX_GENERIC_INSTANTIATION_DEPTH]; stack[0].genArgs = pGenericArgs; stack[0].numGenArgs = nGenericClassArgs; stack[0].index = 0; int curDepth = 0; // Walk over each instantiation, doing a depth-first search looking for any // instantiation with a depth of over 100, in an attempt at flagging // recursive type definitions. We're doing this to help avoid a stack // overflow in the loader. // Avoid recursion here, to avoid a stack overflow. Also, this code // doesn't allocate memory. while(curDepth >= 0) { PerIterationData * cur = &stack[curDepth]; if (cur->index == cur->numGenArgs) { // Pop curDepth--; if (curDepth >= 0) stack[curDepth].index++; continue; } if (cur->genArgs[cur->index].HasInstantiation()) { // Push curDepth++; if (curDepth >= MAX_GENERIC_INSTANTIATION_DEPTH) return FALSE; stack[curDepth].genArgs = cur->genArgs[cur->index].GetInstantiation(); stack[curDepth].numGenArgs = cur->genArgs[cur->index].GetNumGenericArgs(); stack[curDepth].index = 0; continue; } // Continue to the next item cur->index++; } return TRUE; }
class Program { static void Main(string[] args) { var refTypeHolder = new HolderOf(); Test(refTypeHolder); Test2(refTypeHolder); Console.Read(); } [MethodImpl(MethodImplOptions.NoInlining)] static void Test(HolderOf typeHolder) { for (int i = 0; i < 10; i++) { typeHolder.GetPointer<Program>(); } } // place breakpoint [MethodImpl(MethodImplOptions.NoInlining)] static void Test2(HolderOf typeHolder) { for (int i = 0; i < 10; i++) { typeHolder.GetPointer<object>(); } } // place breakpoint } class HolderOf { [MethodImpl(MethodImplOptions.NoInlining)] public void GetPointer<T>() { Console.WriteLine(typeof(T)); } }
00000045 mov ecx,dword ptr [ebp-3Ch] 00000048 mov edx,10031B8h 0000004d cmp dword ptr [ecx],ecx 0000004f call FFE8BF40
00000045 mov ecx,dword ptr [ebp-3Ch] 00000048 mov edx,1003574h 0000004d cmp dword ptr [ecx],ecx 0000004f call FFE8BE40
The ECX register contains a pointer to this (calling convention - FastCall), but after all, GetPointer () has zero arguments, what then is written to the EDX ?!
!dumpmd 10031B8 Method Name: ConsoleApplication1.HolderOf.GetPointer[[ConsoleApplication1.Program, InterfaceStubsTest]]() Class: 01001444 MethodTable: 01003118 mdToken: 0600000e Module: 01002c5c IsJitted: no CodeAddr: ffffffffffffffff
!dumpmd 1003574 Method Name: ConsoleApplication1.HolderOf.GetPointer[[System.Object, mscorlib]]() Class: 01001444 MethodTable: 01003118 mdToken: 0600000e Module: 01002c5c IsJitted: no CodeAddr: ffffffffffffffff
Source: https://habr.com/ru/post/253105/
All Articles