這期內(nèi)容當中小編將會給大家?guī)碛嘘P如何深入了解.NET編譯器中CLR加載過程,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
成都創(chuàng)新互聯(lián)公司專注于延長企業(yè)網(wǎng)站建設,響應式網(wǎng)站,商城建設。延長網(wǎng)站建設公司,為延長等地區(qū)提供建站服務。全流程按需定制,專業(yè)設計,全程項目跟蹤,成都創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務
以下說明CLR加載過程所使用的工具是VS2005+sos.dll,示例程序代碼如下: using System;
using System.Collections.Generic; using System.Text; namespace hello { class Program { static void Main(string[] args) { Int32 a = 1; Int32 b = 2; b = a + b; Console.WriteLine(b); Console.ReadKey(); } } }
那么CLR加載過程是怎樣的呢?
1、當你雙擊一個.exe文件時,Windows操作系統(tǒng)提供的PE Loader會將該exe文件載入內(nèi)存;
(1)、首先明確一點,PE Loader問什么能加載exe文件呢?因為exe文件就是一種PE文件,PE(Portable Execute)文件是微軟Windows操作系統(tǒng)上的程序文件,EXE、DLL、OCX、SYS、COM都是PE文件。
(2)、有必要了解一下PE文件的結(jié)構:
圖 1
1) Dos stub
由100個左右的字節(jié)所組成,用來輸出類似“這個程序不能在DOS下運行!”這樣的錯誤信息;
2) PE Signature
DWORD類型,PE文件簽名,用來表示這是個PE文件,用ASCII碼表示;
3) File Header
包含PE文件最基本信息,通過dumpbin可以看到,如圖2所示 從這里可以看到:CPU類型為
圖2
4) Optional Header
用來存儲除了基本信息以外的其他重要信息,具體含義大家可以查閱PE文件格式的相關資料,我這里對一些關心的域根據(jù)圖3進行一下說明:
-- entry point,指明這個PE文件的入口地址,是一個RVA(相對虛擬地址);
1 不需要子系統(tǒng)(比如設備驅(qū)動)
2 在Windows圖形用戶界面子系統(tǒng)下運行
3 在Windows字符子系統(tǒng)下運行(控制臺程序)
5 在OS/2字符子系統(tǒng)下運行(僅對OS/2 1.x)
7 在 Posix 字符子系統(tǒng)下運行
所以可以看到我們的程序是一個控制臺程序。
-- 最后定義了一些數(shù)據(jù)目錄,具體內(nèi)容不再贅述。
圖 3
5) section header
Section header可以有一個或多個,見圖4、圖5、圖6。
-- name,表示這個section的名字,例如這個section的名字為.text;
-- virtual address,保存section中數(shù)據(jù)被載入內(nèi)存后的RVA;
-- file pointer to raw data,從文件開頭到section中數(shù)據(jù)的偏移量。
圖 4圖 6
-- CLR頭,從圖7可以找到隨托管代碼IL同時生成的元數(shù)據(jù)表的RVA。
圖 7
2、PE loader通過查找CLR頭發(fā)現(xiàn)該目錄不為空,則自動將mscoree.dll載入進程地址空間中,mscoree.dll一定是唯一的,且總是處于系統(tǒng)目錄的system32下,例如我的機器為C:\WINDOWS\system32目錄下。.net 2.0的mscoree.dll的大小只有256k左右,這個dll被叫做shim,它的作用是連接PE文件和CLR之間的一個橋梁。
3、PE loader接著會找到entry point,例如本例中圖3所示,這個PE文件的入口點地址為0040251E,然后通過這個地址來查找.text section的原始數(shù)據(jù)表,由圖6所示,0040251E這個地址開始的6個字節(jié)的內(nèi)容為【FF 25 00 20 40 00】,這個內(nèi)容就是由編譯器寫入PE文件的.text section的重要信息,F(xiàn)F在x86匯編語言與機器碼對照表中代表無條件轉(zhuǎn)移指令Jmp,這條指令的作用是無條件跳轉(zhuǎn)到00402000地址處,從圖3可以看到image base 是00400000,2000是import address table的RVA地址,由圖7可以看到,此時程序會跳轉(zhuǎn)到00402000這個地址所引用的mscoree.dll的_CorExeMain(_CorExeMain為mscoree.dll的入口方法)方法,所有的托管應用都會通過上述過程找到并執(zhí)行_CorExeMain方法;
4、_CorExeMain方法會幫助程序找到并載入適當?shù)腃LR版本,在.net 2.0以后實現(xiàn)CLR的程序集為mscorwks.dll或mscorsvr.dll,例如,在我的機器上mscorwks.dll的位置是:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\;
5、啟動CLR服務,開始初始化工作,這個初始化工作包括:
-- 分配一塊內(nèi)存空間,建立托管堆及其它必要的堆,由GC監(jiān)控整個托管堆
-- 創(chuàng)建線程池
-- 創(chuàng)建應用程序域(AppDomain):利用sos.dll可以查看CLR創(chuàng)建了哪些AppDomain。
用VS2005打開我們的程序,在即時窗口中敲入:.load sos.dll。
在VS2005的即時窗口中敲入:后的結(jié)果),但是依然可以說明問題:
圖 8
由圖8可見,CLR創(chuàng)建了System Domain、Shared Domain和Domain1,這個Domain1是默認Appdomain。
6、接下來就會向默認AppDomain中載入mscorlib.dll,由圖八可見,任何托管代碼,CLR在創(chuàng)建好默認AppDomain后,第一個載入的組件一定是mscorlib.dll,實際上這個組件定義了System.Object、所有基元類型:如System.Int32等,利用sos.dll可以看到有哪些類被載入,依據(jù)Domain 1里的Module地址,在即時窗口敲入命令!dumpmodule -mt
圖 9
從圖9可以看到System.Object被第一個加載進來,接著是System.ICloneable、System.IEnumerable、System.Collection.ICollection、System.Collection.IList、System.Array……
7、產(chǎn)生主線程后可能會觸發(fā)一些mscorlib.dll里的類型并加載入內(nèi)存,接著,當你的PE文件:hello.exe被載入后,默認Appdomain的名字被改為你的PE文件的名字,載入過程完成后的結(jié)果可見圖8。
8、包含在mscorwks.dll中的_CorExeMain2方法接管主線程,它將調(diào)用System Domain中的SystemDomain::ExecuteMainMethod方法,然后由此方法調(diào)用類型加載器的ClassLoader::LoadTypeHandleFromToken方法,該方法會讀取程序集中的元數(shù)據(jù)表,并在里面查找包含.entrypoint的類型,并返回由EECLASS結(jié)構表示的該類型的實例,EECLASS結(jié)構中包含重要信息有:指向當前類型父類的指針、指向方法表的指針、實例字段和靜態(tài)字段等。
(1)、在即時窗口敲入命令0097c
圖 10
從圖10可以看到在當前模塊中所定義的類型:hello.Program和所引用的類型:System.Object和System.Console。
(2)、在即時窗口敲入命令后,有如下結(jié)果:
圖 11
由圖11可以得到如下信息:為hello.Program類型分配的EECLASS在內(nèi)存中的地址為00971260,通過這個地址查看其信息,發(fā)現(xiàn)hello.Program的父類地址為:
圖 12
方法表Method Table的地址為00972ff8。
(3)、方法表里存的是什么呢?其實是當前類型中所有定義和引用到的方法的入口點,這個入口點被叫做Method descriptors,從圖11可以看到。
(4)、實際上Method descriptors被分為兩個部分,第一部分是m_CodeOrIL,在當前方法沒有被JIT的時候,m_CodeOrIL存的是這個方法的MSIL的RVA,也就是從這個RVA可以找到當前方法的MSIL代碼;第二部分是對JIT編譯器的一個Stub(存根),當方法是第一次被調(diào)用的時候,CLR會通過這個Stub調(diào)用mscorjit.dll組件,通過m_CodeOrIL里存儲的RVA,找到這個方法對應的MSIL代碼,然后將其編譯為本地CPU指令,假設這里存到地址RVA1,最后將m_CodeOrIL和Stub的值都修改為RVA1,那么當這個方法第二次被調(diào)用的時候?qū)苯油ㄟ^RVA1去尋找本地代碼,換句話說只有當方法第一次被調(diào)用的時候才會被Jit編譯器編譯,之后則直接使用編譯好的本地代碼。同時這也說明托管代碼被編譯了兩次,第一次編譯是將托管代碼編譯為MSIL代碼,并同時生成Metadata元數(shù)據(jù)文件,第二次編譯發(fā)生在方法被調(diào)用時由Jit編譯器完成。
(5)、在即時窗口敲入命令!dumpmd 00972fe8和!dumpmd 00972f0可以看到已經(jīng)被Jit過的和還沒有被Jit的方法的信息:圖 13
被Jit過得方法則會修改m_CodeOrIL,如Main方法的m_CodeOrIL被指向地址00e50070,而沒有被Jit的方法m_CodeOrIL的值為ffffffffffffffff。
(6)、在即時窗口敲入命令
圖 14
圖14列出helloProgram.Main方法的本地代碼。而如果在即時窗口敲入命令!u ffffffffffffffff則顯示Unmanaged code。
9、進入Main方法,進而執(zhí)行后續(xù)程序。
最后,從上述分析也可以看出,.NET的幾個核心組件的被調(diào)用順序大致是: mscoree.dll -----> mscorwks.dll(mscorsvr.dll) -----> mscorlib.dll ----->mscorjit.dll。
一般來說調(diào)試.NET程序使用VS2005就可以了,但是要想得到更詳細的信息,如內(nèi)存情況等就需要借助其他工具了,個人覺得sos.dll和Windbg是很好的工具,Windbg可以在http://www.microsoft.com/whdc/devtools/debugging/default.mspx下載,而如果你裝的是VS2005 Team Version,那么自帶sos.dll。
上述就是小編為大家分享的如何深入了解.NET編譯器中CLR加載過程了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注創(chuàng)新互聯(lián)行業(yè)資訊頻道。