CLR Gcroot运行过程(Source Code)

444 阅读2分钟

首先是从mark_phase标记阶段开始的(gc.cpp->19472,coreclr 2.2.5)  

void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p)
{
        GCScan::GcScanRoots(GCHeap::Promote,
                                condemned_gen_number, max_generation,
                                &sc);
}

mark_phase里面调用了gcscanroots函数,并且传递了一个函数回调函数,promote,这个其实是过滤获取到的Object,对其进行标记。

void GCToEEInterface::GcScanRoots(promote_func* fn, int condemned, int max_gen, ScanContext* sc)
{
    while ((pThread = ThreadStore::GetThreadList(pThread)) != NULL)
    {
        STRESS_LOG2(LF_GC | LF_GCROOTS, LL_INFO100, "{ Starting scan of Thread %p ID = %x\n", pThread, pThread->GetThreadId());

        if (GCHeapUtilities::GetGCHeap()->IsThreadUsingAllocationContextHeap(
            pThread->GetAllocContext(), sc->thread_number))
        {
            sc->thread_under_crawl = pThread;
#ifdef FEATURE_EVENT_TRACE
            sc->dwEtwRootKind = kEtwGCRootKindStack;
#endif // FEATURE_EVENT_TRACE
            ScanStackRoots(pThread, fn, sc);
#ifdef FEATURE_EVENT_TRACE
            sc->dwEtwRootKind = kEtwGCRootKindOther;
#endif // FEATURE_EVENT_TRACE
        }
        STRESS_LOG2(LF_GC | LF_GCROOTS, LL_INFO100, "Ending scan of Thread %p ID = 0x%x }\n", pThread, pThread->GetThreadId());
    }
}

gcscanroots里面遍历循环所有线程thread,然后调用扫描栈函数scanstackroots。

static void ScanStackRoots(Thread * pThread, promote_func* fn, ScanContext* sc)
{
      pThread->StackWalkFrames( GcStackCrawlCallBack, &gcctx, flagsStackWalk);
}

这里面调用了stackwalkframes函数

StackWalkAction Thread::StackWalkFrames(PSTACKWALKFRAMESCALLBACK pCallback,
                               VOID *pData,
                               unsigned flags,
                               PTR_Frame pStartFrame)
{
     return StackWalkFramesEx(&rd, pCallback, pData, flags, pStartFrame);
}

StackWalkAction Thread::StackWalkFramesEx(
                    PREGDISPLAY pRD,        // virtual register set at crawl start
                    PSTACKWALKFRAMESCALLBACK pCallback,
                    VOID *pData,
                    unsigned flags,
                    PTR_Frame pStartFrame
                )
{
         StackFrameIterator iter;
        if (iter.Init(this, pStartFrame, pRD, flags) == TRUE)
        {
            while (iter.IsValid())
            {
                retVal = MakeStackwalkerCallback(&iter.m_crawl, pCallback, pData DEBUG_ARG(iter.m_uFramesProcessed));
                if (retVal == SWA_ABORT)
                {
                    break;
                }

                retVal = iter.Next();
                if (retVal == SWA_FAILED)
                {
                    break;
                }
            }
        }

}

这个stackwalkframesex里面会遍历传进来的线程的栈帧,然后进行处理。首先要做的是实例化stackframeiterator,然后遍历循环,利用MakeStackwalkerCallback来处理,用iter.next循环到当前线程的下一帧。

StackWalkAction Thread::MakeStackwalkerCallback(
    CrawlFrame* pCF, 
    PSTACKWALKFRAMESCALLBACK pCallback, 
    VOID* pData 
    DEBUG_ARG(UINT32 uFramesProcessed))
{
     StackWalkAction swa = pCallback(pCF, (VOID*)pData);
     return swa;
}

这里面调用了毁掉函数pcallback,传递了两个参数,CrawlFrame帧栈和GCCONTEXT数据

StackWalkAction GcStackCrawlCallBack(CrawlFrame* pCF, VOID* pData)
{
     ICodeManager * pCM = pCF->GetCodeManager();
                pCM->EnumGcRefs(pCF->GetRegisterSet(),
                            pCF->GetCodeInfo(),
                            flags,
                            GcEnumObject,
                            pData,
                            relOffsetOverride);

}

继续看EnumGcRefs,这个函数看名字就知道是便利托管堆帧栈

bool EECodeManager::EnumGcRefs( PREGDISPLAY     pRD,
                                EECodeInfo     *pCodeInfo,
                                unsigned        flags,
                                GCEnumCallback  pCallBack,
                                LPVOID          hCallBack,
                                DWORD           relOffsetOverride)
{
       if (!gcInfoDecoder.EnumerateLiveSlots(
                        pRD,
                        reportScratchSlots,
                        flags,
                        pCallBack,
                        hCallBack
                        ))
    {
        return false;
    }
}

bool GcInfoDecoder::EnumerateLiveSlots(
                PREGDISPLAY         pRD,
                bool                reportScratchSlots,
                unsigned            inputFlags,
                GCEnumCallback      pCallBack,
                void *              hCallBack
                )
{
       GcSlotDecoder slotDecoder;
       slotDecoder.DecodeSlotTable(m_Reader);
                 ReportSlotToGC(slotDecoder,
                                               slotIndex,
                                               pRD,
                                               reportScratchSlots,
                                               inputFlags,
                                               pCallBack,
                                               hCallBack
                                               );
}

    inline void ReportSlotToGC(
                    GcSlotDecoder&      slotDecoder,
                    UINT32              slotIndex,
                    PREGDISPLAY         pRD,
                    bool                reportScratchSlots,
                    unsigned            inputFlags,
                    GCEnumCallback      pCallBack,
                    void *              hCallBack
                    )
    {       ReportStackSlotToGC(
                            spOffset,
                            spBase,
                            pSlot->Flags,
                            pRD,
                            inputFlags,
                            pCallBack,
                            hCallBack
                            );
   }

不解释,他直接是调用,我们进去看看

void GcInfoDecoder::ReportStackSlotToGC(
                                INT32           spOffset,
                                GcStackSlotBase spBase,
                                unsigned        gcFlags,
                                PREGDISPLAY     pRD,
                                unsigned        flags,
                                GCEnumCallback  pCallBack,
                                void *          hCallBack)
{
    GCINFODECODER_CONTRACT;

    OBJECTREF* pObjRef = GetStackSlot(spOffset, spBase, pRD);
    _ASSERTE( IS_ALIGNED( pObjRef, sizeof( Object* ) ) );

#ifdef _DEBUG
    LOG((LF_GCROOTS, LL_INFO1000, /* Part One */
             "Reporting %s" FMT_STK,
             ( (GC_SP_REL        == spBase) ? "" :
              ((GC_CALLER_SP_REL == spBase) ? "caller's " :
              ((GC_FRAMEREG_REL  == spBase) ? "frame " : "<unrecognized GcStackSlotBase> "))),
             DBG_STK(spOffset) ));

    LOG((LF_GCROOTS, LL_INFO1000, /* Part Two */
         "at" FMT_ADDR "as ", DBG_ADDR(pObjRef) ));

    VALIDATE_ROOT((gcFlags & GC_CALL_INTERIOR), hCallBack, pObjRef);

    LOG_PIPTR(pObjRef, gcFlags, hCallBack);
#endif

    gcFlags |= CHECK_APP_DOMAIN;

    pCallBack(hCallBack, pObjRef, gcFlags DAC_ARG(DacSlotLocation(GetStackReg(spBase), spOffset, true)));
}

ReportStackSlotToGC函数,通过寄存rbp+XX 指向的地址,获取到当前Object的,引用类型地址,然后把传给标记函数Promote,让其进行对象存活标记。

至于这个对象是怎么获取到,它是通过GCToken的gcinfo 信息位获取的,里面包含了对象相对于rbp寄存器的偏移地址。

gcroot,看似很神秘,其实东西就这么多。如果你有任何疑问可以加入QQ群:

676817308一起探讨学习。也可以扫码关注我,里面有更多技术分享