CLR引擎初始化分析
在以前的一篇叫做<深入追踪Exe加载过程>的文章里面,从clix的launch函数开始,再到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类型,这个是在framework的cor.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
下面,来具体叙说下CLR的EE在启动的时候所做的事情,就一段一段的说吧,比较长,基本上对于每句都加上了注释:
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_fEEInit为true,这个是标识EE是否在启动的全局变量,每次只运行一个EE的启动,每次EE启动的时候,会lock上相关的资源并且检查g_fEEInit的值。
SetConsoleCtrlHandler方法设置了响应Ctrl键的方法。InitializeEventTracing方法调用的挺早,这个是为了尽早启动事件跟踪机制,这样就能够跟踪CLR在启动的时候的相关的事件。所以在设置了对Ctrl的响应模式以后立马就调用了这个方法。
而对于ETWTraceStartup trace(ETW_TYPE_STARTUP_EESTARTUP),这记录了CLR这个启动的这个事件。ETW_TYPE_STARTUP_EESTARTUP是eventtrace.h中记录的一系列不同的事件类型。在每次运行到某个特定的事件的时候,就trace下来。Eventtrace,实现了windows操作系统的事件记录功能。如果你打开响应的事件记录级别的话。在eventtrace中,还定义了许多关于GC,ThreadPool,Exceptions,Monitor,Start Up Event,Fusion 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一个机会来hook到ee的启动内部来监视相关的活动
GSCookies是Guard-stack cookies的缩写,启动检测buffer overrun的功能。
然后是Frame的初始化。这里的初始化主要是基础类的初始化,而EE需要的Frame主要是由JIT和Stub Manager生成放到Stack上面的。
再frame.h文件中,有一个关于Frame很清晰的架构图,可以参考一下。
接下来,就是初始化EE的Config文件。主要是从注册表里面获取相关的一些配置信息,譬如是不是该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是嘛玩意呢?
SString是EE中一个非常重要的东西。它的存在,主要是基于两个目的的:首先是提供一个相对来说针对所有的API来说比较标准的string class,和一个易用的,性能相对来说的比System.String更加底层的string实现类。另外一个功能,就是把string操作里面的所有不安全的功能都封装了起来。
SString里面包含的数据的编码方式,都是用的是Unicode。这种编码方式可以在需要的时候转换成为ASCII,UTF-8,或者是ANSI。
接下来,就是启动Fusion,fusion中主要是包含了配件相关的绑定,策略相关检查和全局配件缓存的实现代码。
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 cor的handle map,这个map,是在需要把完成的PE文件格式整到成一个内存image的时候用的,而不是system的load方法。
特别声明一下:
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来获取正在执行的Method的GCinfo,说是比较有用的东西,不过我没体会到…
接下来是和load的moudle相关的签名的初始化,然后是stub的初始化。然后把stub链接到特定CPU的架构。
然后是一些引用类型的初始化。不同的handle类型,强连接弱连接等,不包括pin。
接着初始化Remoting,以及Context上下文。
然后启动ThreadManager,启动线程管理。
然后是同步pEEShutDownEvent事件。
接着初始化Read Write Lock。Then,启动调试器响应处理逻辑,启动位于后台的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) 编辑 收藏 举报