一:看下面一些概念
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()); } }