分析.NET應用程式啟動過程的最佳方式就是觀察一個簡單的.NET指令行程式。程式的源代碼和程式集分别位于以下檔案夾中:
源代碼檔案:C:\\adnd\\chapter1\\MDASample
程式集檔案:C:\\adndbin\\01mdasample.exe
如果運作上面的程式,它會成功執行,如清單2-2所示。
清單2-2執行02simple.exe

由于.NET應用程式在執行時要預先加載CLR,那麼Windows如何知道加載并初始化CLR?我們可能會做出一種假設:系統開發人員對 Windows加載器進行了改動以識别.NET程式集,并且當檢測到.NET程式集時自動啟動CLR。盡管這種假設隻有部分是正确的,但在.NET之前的 Windows上執行.NET應用程式時,必須首先打更新檔。回答這個問題的關鍵在于:對PE格式進行了擴充。在前面已經提到過,PE格式是Windows 可執行程式的檔案格式,用來管理PE檔案中代碼的執行。可執行程式包括EXE、DLL、OBJ、SYS等檔案。為了支援.NET,在PE檔案格式中增加了 對程式集的支援,如圖2-3所示。
現在,我們來分析當加載器遇到一個.NET應用程式時将發生哪些動作。在這個示例中使用02simple.exe,這個程式位于 C:\\ADNDBin。需要注意的是,這個示例程式是在Windows 2000上運作。之是以要在一個舊版本的Windows上運作,是因為在Windows 2000之後的版本中增加了一些改動,而這些改動将影響Windows加載器加載.NET程式集的方式(在本章的後面将進行介紹)。為了更好地說明這些概 念,我使用了一個工具dumpbin.exe,它能夠解析PE檔案格式并且以簡潔易讀的形式轉儲出PE檔案的内容。在02simple.exe上運作 dumpbin.exe的結果被儲存在一個檔案中:
第一部分值得注意的資訊是在Optional Header Values段中,如下所示:
上面的Entry Point域對應于PE檔案中的AddressOfEntryPoint域,值為0x00402464。要找出位置0x00402464所對應的代碼,需遙看PE映像中的.text段,具體來說就是如下面清單中的RAW DATA段:
上面資訊中的粗體位元組對應于AddressOfEntryPoint ,這些位元組對應的機器指令為:
這裡有一個問題:402000 表示什麼意思?事實上,0x402000指向的是PE映像檔案中的另一部分,也就是import段,在這個段中列出的是PE檔案依賴的所有子產品。在加載 時,系統将修正導入函數的實際位址,并執行正确的調用。要找到0x402000指向的内容,我們可以檢視PE檔案的導入段,可以發現以下内容:
可以看到,0x402000指向的是mscoree.dll(Micorsoft對象運作時執行引擎,Microsoft Object Runtime Execution Engine),這個庫中包含了一個導出函數_CorExeMain。然後,前面的JMP 指令可以轉換為以下僞碼:
我們已經看到了,_CorExeMain是mscoree.dll的一部分,這個函數也是在加載.NET程式集時第一個被調用的函數。 mscoree.dll(和_CorExeMain)的主要作用就是啟動CLR。 mscoree.dll在啟動CLR時将執行一系列的工作:
1)通過檢視PE檔案中的中繼資料,找出.NET程式集是基于哪個版本的CLR建構的。
2)找出作業系統中正确版本CLR的路徑。
3)加載并初始化CLR。
在CLR被初始化之後,在PE映像的CLR頭中就可以找到程式集的入口點(Main())。然後,JIT開始編譯并執行入口點。到目前為止,我們所 談到的CLR還隻是一個邏輯元件,而并沒有提到它的各項功能具體由哪些映像來實作。CLR的大部分功能是由mscorwks.dll來實作的。而且,在任 何一台機器上都可能有多個版本的mscorwks.dll。例如,如果安裝了.NET 1.1和.NET 2.0,那麼在機器上将存在以下CLR DLL:
C:\\Windows\\Microsoft.NET\\Framework\\v1.1.4322\\mscorwks.dll
C:\\Windows\\Microsoft.NET\\Framework\\v2.0.50727\\mscorwks.dll
之是以要安裝多個版本,是因危同的.NET應用程式可能需遙同版本的CLR。基于CLR 1.0編寫的應用程式需要正确地加載1.0 版本的CLR,即使有.NET 2.0也不行。這種機制本質上是實作.NET中的平行執行模型(Side-By-Side Execution Model)。mscoree.dll的作用是通過檢視PE映像檔案中的CLR頭來找出程式集需要使用哪個版本的CLR。具體來 說,mscoree.dll将檢視CLR頭中的MajorRuntimeVersion和MinorRuntimeVersion兩個域,并且加載正确版 本的CLR。
到目前為止,我們已經介紹了.NET程式集(字尾名為.EXE)的整個啟動流程。正如非托管的Windows應用程式能夠支援像動态庫這種可執行形 式,.NET也有着同樣的方式。就加載器而言,.NET庫與.NET可執行程式之間的唯一差别就是,在PE映像中導入的函數不是_CorExeMain, 而是_CorDllMain。
在加載mscoree.dll時有一個值得注意的問題,即為什麼需要非托管的存根函數來調用_CorExeMain?因為PE映像檔案包含了一 個.NET頭,那麼Windows加載器是否也可以将這個PE映像識别為一個.NET程式集并且自動加載mscoree.dll?是的,的确如此。在 Windows XP以及之後的版本中對Windows加載器進行了更新,使其能夠識别出一個.NET程式集的PE映像,并且自動加載CLR。
.NET程式集的加載算法總結如下:
1)使用者執行一個.NET程式集。
2)Windows加載器檢視AddressOfEntryPoint 域,并找到PE映像檔案的.text段。
3)位于AddressOfEntryPoint 位置上的位元組隻是一個JMP指令,這個指令跳轉到mscoree.dll中的一個導入函數。
4)将執行控制轉移到mscoree.dll中的函數_CorExeMain中,這個函數将啟動CLR并且把執行控制轉移到程式集的入口點。
PE檔案格式是一種多用途的格式(從它可以很容易支援.NET程式集的特性也說明了這一點),它包含了與被加載和執行的PE映像相關的大量資訊。本 節内容重點介紹了如何通過擴充PE檔案格式來支援.NET程式集的執行。接下來,我們将深入分析CLR中其他的關鍵内容,首先介紹應用程式域。
chaunceyhao