CLR引擎初始化分析

在以前的一篇叫做<深入追踪Exe加载过程>的文章里面,从clixlaunch函数开始,再到CorExeMain2函数,在CorExeMain2里面有一个叫做CoInitializeEE的函数:

       result = CoInitializeEE(COINITEE_DEFAULT | COINITEE_MAIN)

       一路找到EnsureEEStarted,这个是确保EE启动的方法。在这个方法里面,首先做一系列的状态判断,然后判断ee (Execute Engine)是否已经启动。如果没有启动,就启动EE

      

if (!g_fEEStarted && !g_fEEInit && SUCCEEDED (g_EEStartupStatus))

{

EEStartup(flags);

bStarted=g_fEEStarted;

hr = g_EEStartupStatus;

        }

else

{

hr = g_EEStartupStatus;

if (SUCCEEDED(g_EEStartupStatus))

{

hr = S_FALSE;

}

}

 

这里首先做了几个状态检查:

if (!g_fEEStarted && !g_fEEInit && SUCCEEDED (g_EEStartupStatus))

 

需要三个条件同时满足的时候,才会执行EEStartup函数来启动EE

1.         EE没有启动。

2.         EE没有被正在启动,当然,在执行这个函数之前是设置了Lock的。

3.         EEStartup在上一次启动的时候失败。

 

杀入EEStartup,找到最关键的几行代码:


      PAL_TRY

       {

           EEStartupHelper(fFlags);

       }

       PAL_EXCEPT_FILTER (FilterStartupException, NULL)

       {

           // The filter should have set g_EEStartupStatus to a failure HRESULT.

           _ASSERTE(FAILED(g_EEStartupStatus));

       }

PAL_ENDTRY


   这里使用了
PAL_TRY来确保这个这个过程的acid性质。现在这篇文章的主角登场了:

EEStartupHelper(fFlags);

首先说一下这个fFlags类型,这个是在frameworkcor.h里面的一个枚举类型函数:


typedef enum tagCOINITEE

{

       // Default initialization mode.

COINITEE_DEFAULT        = 0x0,  

// Initialization mode for loading DLL.        

COINITEE_DLL            = 0x1,            

// Initialize prior to entering the main routine  

COINITEE_MAIN           = 0x2        

} COINITIEE;

这里的fFlags=COINITEE_DEFAULT | COINITEE_MAIN

 

下面,来具体叙说下CLREE在启动的时候所做的事情,就一段一段的说吧,比较长,基本上对于每句都加上了注释:

 

       g_fEEInit = true;

       //set Ctrl event Control handler

    ::SetConsoleCtrlHandler(DbgCtrlCHandler, TRUE/*add*/);

 

    // Initialize event tracing early so we can trace CLR startup time events.

    InitializeEventTracing();

 

    ETWTraceStartup trace(ETW_TYPE_STARTUP_EESTARTUP);

    TIMELINE_AUTO(STARTUP, "EEStartup");

      

       首先置g_fEEInittrue,这个是标识EE是否在启动的全局变量,每次只运行一个EE的启动,每次EE启动的时候,会lock上相关的资源并且检查g_fEEInit的值。

       SetConsoleCtrlHandler方法设置了响应Ctrl键的方法。InitializeEventTracing方法调用的挺早,这个是为了尽早启动事件跟踪机制,这样就能够跟踪CLR在启动的时候的相关的事件。所以在设置了对Ctrl的响应模式以后立马就调用了这个方法。

       而对于ETWTraceStartup trace(ETW_TYPE_STARTUP_EESTARTUP),这记录了CLR这个启动的这个事件。ETW_TYPE_STARTUP_EESTARTUPeventtrace.h中记录的一系列不同的事件类型。在每次运行到某个特定的事件的时候,就trace下来。Eventtrace,实现了windows操作系统的事件记录功能。如果你打开响应的事件记录级别的话。在eventtrace中,还定义了许多关于GCThreadPoolExceptionsMonitorStart Up EventFusion building log响应的事件。有兴趣的可以去看看。最后的一行,是一个简单的定时的手动的事件日志记录,这两行放在了一起。这个主要是用于profiling的用途。

      

       接下来的第二段启动代码:


      
IPCFuncCallSource::DoThreadSafeCall();

 

       //GSCookies (guard-stack cookies) for detecting buffer overruns

       InitGSCookie();

       Frame::Init();

 

       // Initialize EEConfig

       if (!g_pConfig)

       {

           IfFailGo(EEConfig::Setup());

           initEEConfig = TRUE;

   }

 

       GetStartupInformation();

 

       // get any configuration information from the registry

       if (initEEConfig)

        IfFailGo(g_pConfig->SetupConfiguration());

 

 

首先说下IPCFuncCallSource是干嘛的,他的主要用途,是为了支持一个跨越进程的函数之间的相互调用。如果在EE启动的时候,有一部分代码需要被别的进程hook到,就需要在这段代码的前后都调用这样的一个方法来制定可以safeCall的范围。因此,在代码的下面也就可以再看到这样的相同的一行。这里调用这样的一个方法,主要是为了给性能监视程序,PerMon.exe一个机会来hookee的启动内部来监视相关的活动

GSCookiesGuard-stack cookies的缩写,启动检测buffer overrun的功能。

然后是Frame的初始化。这里的初始化主要是基础类的初始化,而EE需要的Frame主要是由JITStub Manager生成放到Stack上面的。

frame.h文件中,有一个关于Frame很清晰的架构图,可以参考一下。

接下来,就是初始化EEConfig文件。主要是从注册表里面获取相关的一些配置信息,譬如是不是该Jit啊,是不是需要检查一下GC啊,等等。

下面的几行,也是和读取和配置配置信息相关的,不多说了。

 

Go on 第三波,还有很多呢,对了,声明下,clr instense和一些调试信息,还有一些记录到log文件里面的信息我都没列出来:

 

     // SString

    SString::Startup();

 

    // Fusion

    {

        ETWTraceStartup traceFusion(ETW_TYPE_STARTUP_FUSIONINIT);

        IfFailGoLog(InitializeFusion());

}

 

// A class to maintain a pool of available events.

       //static EventStore s_EventStore;

    InitEventStore();

 

    if (g_pConfig != NULL)

    {

        IfFailGoLog(g_pConfig->sync());       

    }

// Initialize all our InterProcess Communications with COM+

IfFailGoLog(InitializeIPCManager());

 

首先是SString的初始化。String地球人都知道SString是嘛玩意呢?

SStringEE中一个非常重要的东西。它的存在,主要是基于两个目的的:首先是提供一个相对来说针对所有的API来说比较标准的string class,和一个易用的,性能相对来说的比System.String更加底层的string实现类。另外一个功能,就是把string操作里面的所有不安全的功能都封装了起来。

SString里面包含的数据的编码方式,都是用的是Unicode。这种编码方式可以在需要的时候转换成为ASCIIUTF-8,或者是ANSI

接下来,就是启动Fusionfusion中主要是包含了配件相关的绑定,策略相关检查和全局配件缓存的实现代码。

InitEventStore是一个实现了把现有的Event都存储到一个pool里面的class。在来不及都处理的时候,形成一个队列。

InitializeIPCManager实现了对进程间通信的初始化,主要是COM+的使用。主要分配一个IPCManager的实现,然后和相关的接口hook到一起,同时调用相关的函数来激活IPC相关的模块。

 

Then,第四段:

// Set up the cor handle map. This map is used to load assemblies in

    // memory instead of using the normal system load

    PEImage::Startup();

 

       // Implementation of the ICodeInfo interface

    EECodeInfo::Init();

    HardCodedMetaSig::Init();

    Stub::Init();

       // StubLinker with extensions for generating X86 code.

    StubLinkerCPU::Init();

 

    // weak_short, weak_long, strong; no pin

       //Wraps handle table to implement various handle types (Strong, Weak, etc.)

    Ref_Initialize();

 

    // Initialize remoting

    CRemotingServices::Initialize();

 

    // Initialize contexts

    Context::Initialize();

 

       // Initialize ThreadManager,One-time initialization. Called during Dll initialization.

    InitThreadManager();

 

       // Event to synchronize EE shutdown.

    g_pEEShutDownEvent = new CLREvent();

       // If TRUE, initial state is signalled

    g_pEEShutDownEvent->CreateManualEvent(FALSE);

 

    // Initialize RWLocks(Read Write Lock)

    CRWLock::ProcessInit();

 

    // Initialize debugger manager

    CCLRDebugManager::ProcessInit();

 

       CLR的启动过程中,每个地方都是重要的部分..

       首先调用一个静态方法,来set corhandle map,这个map,是在需要把完成的PE文件格式整到成一个内存image的时候用的,而不是systemload方法。

       特别声明一下:

       PEImage是一个PE文件,它由“simulated loadlibrary”机制load,一个PEImage可以以两种方式被加载,一种是FLAT方式,这种加载方式加载以后,其布局和在磁盘上面的布局相同。另外一种加载方式是MAPPED方式。这种方式对PE Sections的各个部分都map到了不同的虚拟地址上面。

       也可以参考rick的最新的一篇博文也是讲的这个问题:

       http://www.cnblogs.com/rick/archive/2008/05/15/1199197.html

       昨天刚发的,呵呵。

 

       EECodeInfo::Init初始化了一个实现了ICodeInfo的类,这个interface的主要作用,是来让ICodeManager来获取正在执行的MethodGCinfo,说是比较有用的东西,不过我没体会到

       接下来是和loadmoudle相关的签名的初始化,然后是stub的初始化。然后把stub链接到特定CPU的架构。

       然后是一些引用类型的初始化。不同的handle类型,强连接弱连接等,不包括pin

       接着初始化Remoting,以及Context上下文。

       然后启动ThreadManager,启动线程管理。

       然后是同步pEEShutDownEvent事件。

       接着初始化Read Write LockThen,启动调试器响应处理逻辑,启动位于后台的Debugger Thread

 

 

       到这里,EE的启动,就完成了一半的工作。恩,仅仅是一半。

 

       EE启动的过程中所做的事情认识的越深刻了解的越详细,对了解EE的各个部分的功能就越容易,条理就越清晰,架构就越熟悉。

       只有完全熟悉了EE的启动过程中所做的事情,才能说:welcome to the EE World.

      

       这个对以后通过编写代码来调用CLR的特定的功能也是非常有帮助的。同时,对于Crack和加密解密的研究也是非常有帮助的。

 

       To be continued..

 

       5/16/2008 10:53:19 AM 首发 http://sscli.cnblogs.com

posted on 2008-05-16 10:59  lbq1221119  阅读(3462)  评论(10编辑  收藏  举报

导航