CLR裡的MethodTable,MethodDescChunk,MethodDesc,FixUpPreCode都是什麼意思

冬夏與春秋發表於2021-06-18

 

 

一:看下面一些概念

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());
        }
    }

 

相關文章