在以前的一篇叫做<深入追蹤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))<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
{
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