一:看下面一些概念
1MethodTable
MethodTable可以说在CLR里面无处不在,这个东西主要是作为对象的数据类型存在,主要包含了EEClass
模块地址,类型名称,模块路径等。
2.EEClass
EEclass描述了实例对象和类型里面的函数描述符起始块地址,起始块以MethodDescChunk描述。EEclass
成员变量m_pChunks存放了起始块的地址。
3.MethodDesc
看这个名称就知道,它是函数描述符。主要包含了函数名称,函数所述模块,函数是否被jit编译等属性。
4.FixUpPreCode
从字面理解修理预代码,其实它也是这个功能。就是它描述了托管函数被编译前和编译后的变化。编译前
会调用非托管PrecodeFixupThunk函数,编译后直接跳到编译的结果。
二:它们是如何组织的
MethodTable会通过CLR生成实例化对象。在这个生成的过程中,会初始化EEClass。然后会给MethodDescChunks
以及MethodDesc分配相应的地址,MethodDesc跟MethodDescChunks地址是连接在一起的,类似于MethodDescChunks
->MethodDes(1)->MehtodDesc(2)->MethodDesc(3)...... 这种形式。下面就是把MethodDescChunks当MethodDesc分配
完成的时候,会遍历MethodDesc,没遍历一个,就会出初始化一个FixUpPreCode结构体。用以描述函数没被编译之前
的状态。他们的关联方式如下图所示:
三:CLR是如何被运行出来的
作为一个.Net 程式核心部分,这一块是一个重点也是一个难点
主要的步骤有以下几步:
1.CLR会加载当前项目bin/debug/文件夹下面的【目名称.ConsoleApp15.runtimeconfig.json】的这个文件,以读取runtime 的配置
2.CLR 会获取当前runtime运行时的指针
3.通过runtime运行时指针函数,传递进去需要加载的模块,类名称,以及函数名称,获取到需要调用的函数指针
4.获取到函数指针之后,就可以释放掉上面运行步骤所占用的内存
5.调用获取到的函数指针调用函数,比如Main函数,这个是程式入口。
到了这一步不多说了,因为所有的.net程序都是从Main函数开始的。这样流程就会被C#代码所接管。
四:代码是如何构造的
由于CLR代码长达几十万行甚至几百万行,所以无法一一展示。
这里取一部分看看是如何执行的
1.MethodTable的构建过程(MethodTablebuilder.cpp 12163行)
MethodTableBuilder builder( NULL, pClass, &GetThread()->m_MarshalAlloc, pamTracker); pMT = builder.BuildMethodTableThrowing( pAllocator, pLoaderModule, pModule, cl, pInterfaceBuildInfo, pLayoutRawFieldInfos, pParentMethodTable, &genericsInfo, parentInst, (WORD)cInterfaces); END_SO_INTOLERANT_CODE; RETURN(TypeHandle(pMT));
2.EEclass构建过程(methodtablebuilder.cpp 11957行)
EEClass * pClass = MethodTableBuilder::CreateClass( pModule, cl, fHasLayout, fIsDelegate, fIsEnum, &genericsInfo, pAllocator, pamTracker);
3.MethodDescchunks和MethodDesc的构建过程是在buildmethodtablethrowing函数里面调用 AllocAndInitMethodDescs();,先看看 AllocAndInitMethodDescs();函数(methodtablebuilder.cpp 1666行)
VOID MethodTableBuilder::AllocAndInitMethodDescs() { STANDARD_VM_CONTRACT; if (sizeOfMethodDescs != 0) { AllocAndInitMethodDescChunk(startIndex, NumDeclaredMethods() - startIndex, sizeOfMethodDescs); } }
4.继续看 AllocAndInitMethodDescChunk函数(methodtablebuilder.cpp 6836行),很明显看到了里面构建了methoddescchunks和methoddesc。
VOID MethodTableBuilder::AllocAndInitMethodDescChunk(COUNT_T startIndex, COUNT_T count, SIZE_T sizeOfMethodDescs) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(sizeOfMethodDescs <= MethodDescChunk::MaxSizeOfMethodDescs); } CONTRACTL_END; void * pMem = GetMemTracker()->Track( GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(TADDR) + sizeof(MethodDescChunk) + sizeOfMethodDescs))); // Skip pointer to temporary entrypoints MethodDescChunk * pChunk = (MethodDescChunk *)((BYTE*)pMem + sizeof(TADDR)); COUNT_T methodDescCount = 0; SIZE_T offset = sizeof(MethodDescChunk); #ifdef _PREFAST_ #pragma warning(push) #pragma warning(disable:22019) // Suppress PREFast warning about integer underflow #endif // _PREFAST_ for (COUNT_T i = 0; i < count; i++) #ifdef _PREFAST_ #pragma warning(pop) #endif // _PREFAST_ { bmtMDMethod * pMDMethod = (*bmtMethod)[static_cast<SLOT_INDEX>(startIndex + i)]; MethodDesc * pMD = (MethodDesc *)((BYTE *)pChunk + offset); pMD->SetChunkIndex(pChunk); InitNewMethodDesc(pMDMethod, pMD); #ifdef _PREFAST_ #pragma warning(push) #pragma warning(disable:22018) // Suppress PREFast warning about integer underflow #endif // _PREFAST_ offset += pMD->SizeOf(); #ifdef _PREFAST_ #pragma warning(pop) #endif // _PREFAST_ methodDescCount++; // If we're a value class, we want to create duplicate slots // and MethodDescs for all methods in the vtable // section (i.e. not non-virtual instance methods or statics). // In the name of uniformity it would be much nicer // if we created _all_ value class BoxedEntryPointStubs at this point. // However, non-virtual instance methods only require unboxing // stubs in the rare case that we create a delegate to such a // method, and thus it would be inefficient to create them on // loading: after all typical structs will have many non-virtual // instance methods. // // Unboxing stubs for non-virtual instance methods are created // in code:MethodDesc::FindOrCreateAssociatedMethodDesc. if (NeedsTightlyBoundUnboxingStub(pMDMethod)) { MethodDesc * pUnboxedMD = (MethodDesc *)((BYTE *)pChunk + offset); ////////////////////////////////// // Initialize the new MethodDesc // <NICE> memcpy operations on data structures like MethodDescs are extremely fragile // and should not be used. We should go to the effort of having proper constructors // in the MethodDesc class. </NICE> memcpy(pUnboxedMD, pMD, pMD->SizeOf()); // Reset the chunk index pUnboxedMD->SetChunkIndex(pChunk); if (bmtGenerics->GetNumGenericArgs() == 0) { pUnboxedMD->SetHasNonVtableSlot(); } ////////////////////////////////////////////////////////// // Modify the original MethodDesc to be an unboxing stub pMD->SetIsUnboxingStub(); //////////////////////////////////////////////////////////////////// // Add the new MethodDesc to the non-virtual portion of the vtable if (!bmtVT->AddUnboxedMethod(pMDMethod)) BuildMethodTableThrowException(IDS_CLASSLOAD_TOO_MANY_METHODS); pUnboxedMD->SetSlot(pMDMethod->GetUnboxedSlotIndex()); pMDMethod->SetUnboxedMethodDesc(pUnboxedMD); offset += pUnboxedMD->SizeOf(); methodDescCount++; } } _ASSERTE(offset == sizeof(MethodDescChunk) + sizeOfMethodDescs); pChunk->SetSizeAndCount((ULONG)sizeOfMethodDescs, methodDescCount); GetHalfBakedClass()->AddChunk(pChunk); }
5.关于构建fixupprecode (methodtablebuilder.cpp 10497行)
{ for (MethodDescChunk *pChunk = GetHalfBakedClass()->GetChunks(); pChunk != NULL; pChunk = pChunk->GetNextChunk()) { // Make sure that temporary entrypoints are create for methods. NGEN uses temporary // entrypoints as surrogate keys for precodes. pChunk->EnsureTemporaryEntryPointsCreated(GetLoaderAllocator(), GetMemTracker()); } }