新聞中心
理解在CLR中Jit編譯發(fā)生的過程,了解這個(gè)首先要從CLR談起。CLR(公共語言運(yùn)行時(shí),Common Language Runtime)和Java虛擬機(jī)一樣也是一個(gè)運(yùn)行時(shí)環(huán)境,它負(fù)責(zé)資源管理(內(nèi)存分配和垃圾收集),并保證應(yīng)用和底層操作系統(tǒng)之間必要的分離。

CLR是如何找到托管代碼的入口方法并對(duì)其Jit的呢?Jit編譯的發(fā)生過程是怎么樣的呢?Jit編譯器和Metadata表又有什么關(guān)系呢?本文試圖尋找出答案,在此之前,不妨先了解一下CLR Header的大致結(jié)構(gòu)。
以如下代碼為例:
- Example
- using System;
- namespace CLRTesing
- {
- class Program
- {
- static void Main(string[] args)
- {
- System.Console.WriteLine("Hello World!");
- Console.ReadKey();
- new P().Display();
- }
- Program()
- {
- Console.WriteLine("Constructor.");
- Console.ReadKey();
- }
- static Program()
- {
- Console.WriteLine("Static constructor.");
- Console.ReadKey();
- }
- }
- class P
- {
- public void Display()
- {
- System.Console.WriteLine("P!");
- Console.ReadKey();
- new Q().Display();
- Console.ReadKey();
- }
- }
- class Q
- {
- public void Display()
- {
- System.Console.WriteLine("Q!");
- Console.ReadKey();
- }
- }
- }
編譯后通過dumpbin工具的到其CLR Header,如圖所示:
從圖中可以看到,CLR Header由以下幾個(gè)部分組成:
1、CB:表示CLR Header的大小,單位是byte;
2、Run time version:運(yùn)行時(shí)版本,包含兩部分MajorRuntimeVersion和MinorRuntimeVersion;
3、Metadata Directory:指出Metadata table的RVA和其大小;
4、Flag:這個(gè)標(biāo)識(shí)主要是供加載器使用,flag值為0x00000001表示當(dāng)前runtime image僅由IL代碼組成并且對(duì)CPU沒有特殊要求;值為0x00000002表示image只能被加載到32位機(jī)中,值為0x00010000表示運(yùn)行時(shí)和jit編譯器需要追蹤方法的調(diào)試信息;
5、EntryPointToken:Metadata 表中標(biāo)記為EntryPoint的方法的MethodDef;
6、Resources Directory:CLR的資源,也就是托管資源的RVA和大小,注意與PE文件中存儲(chǔ)Win32資源的section不同;
7、StrongNameSignature Directory:PE文件中供CLR加載器使用的哈希值所處RVA和大?。?
8、CodeManagerTable Directory:Code Manager 表的RVA和其大?。?
9、VTableFixups Directory:由非托管C++類型中虛方法的指針組成的數(shù)組;
10、ExportAddressTableJumps Directory:跳轉(zhuǎn)地址表的RVA和大小;
11、ManagedNativeHeader Directory:一般情況下為0。
以上結(jié)構(gòu)可以從CorHdr.h文件中看出,如果裝的是Visual Studio 2005,這個(gè)文件在\Microsoft Visual Studio 8\SDK\v2.0\include\。
查看托管PE文件的工具有很多,不用很復(fù)雜的,就園子里的大牛Anders Liu寫的CliPeViwer就很好用,用Reflector可以偷窺其代碼哦。
那么在上面這個(gè)結(jié)構(gòu)中我最關(guān)心的是Metadata directory和EntryPointToken,Metadata directory存提供了原數(shù)據(jù)所在內(nèi)存地址的范圍,EntryPointToken告訴我們在原數(shù)據(jù)表中哪個(gè)token標(biāo)識(shí)的方法是入口方法,這里一定是方法,所以這個(gè)token是以6開頭的一個(gè)數(shù)。
回到主題,我們從CLR已經(jīng)被載入內(nèi)存、mscorwks.dll中的_CorExeMain2方法接管主線程開始說起:
1、_CorExeMain2方法會(huì)調(diào)用System Domain中的SystemDomain::ExecuteMainMethod方法,然后由此方法再去調(diào)用其它方法(具體什么方法參見深入了解CLR的加載過程一文中的第8步), 通過MetaData表提供的接口查找包含.entrypoint的類型,接著返回入口方法(在C#中這個(gè)入口方法一定是Main方法)的一個(gè)MethodDesc類型的實(shí)例;獲取MethodDesc類型實(shí)例的這個(gè)過程我認(rèn)為是:CLR通過讀取MetaData表,定位入口方法所屬的類型,將包含該類型的Module載入,然后建立這個(gè)類型的EECLASS(EECLASS結(jié)構(gòu)中包含重要信息有:指向當(dāng)前類型父類的指針、指向方法表的指針、實(shí)例字段和靜態(tài)字段等)和這個(gè)類型所包含方法的Method Table(方法表由一個(gè)個(gè)Method Descripter組成,具體到內(nèi)存中就是指向若干MethodDesc類型實(shí)例的地址),通過EEClass::FindMethod方法找到并返回入口方法的MethodDesc類型實(shí)例。
MethodDesc這個(gè)類型很有意思,它有兩個(gè)重要的部分,一個(gè)部分叫做m_CodeOrIL,用來存儲(chǔ)編譯好的MSIL在內(nèi)存中的地址,初值為ffffffffffffffff,另一個(gè)部分叫做Stub,如果當(dāng)前代碼沒有被編譯為本地CPU指令,那么通過這個(gè)Stub會(huì)觸發(fā)對(duì)Jit編譯器的調(diào)用。
執(zhí)行上述代碼,
用Windbg 查看,如下:
- Windbg1
- 0:000> !name2ee *!CLRTesing.Program
- Module: 790c2000 (mscorlib.dll)
- --------------------------------------
- Module: 00a72c3c (Hello.exe)
- Token: 0x02000002
- MethodTable: 00a73048
- EEClass: 00a7129c
- Name: CLRTesing.Program
- 0:000> !name2ee *!CLRTesing.P
- Module: 790c2000 (mscorlib.dll)
- --------------------------------------
- Module: 00a72c3c (Hello.exe)
- Token: 0x02000003
- MethodTable:
- EEClass:
- Name: CLRTesing.P
- 0:000> !dumpmt -md 00a73048
- EEClass: 00a7129c
- Module: 00a72c3c
- Name: CLRTesing.Program
- mdToken: 02000002(D:\test\Hello\bin\Debug\Hello.exe)
- BaseSize: 0xc
- ComponentSize: 0x0
- Number of IFaces in IFaceMap: 0
- Slots in VTable: 7
- --------------------------------------
- MethodDesc Table
- Entry MethodDescJIT Name
- 79371278 7914b928 PreJIT System.Object.ToString()
- 7936b3b0 7914b930 PreJIT System.Object.Equals(System.Object)
- 7936b3d0 7914b948 PreJIT System.Object.GetHashCode()
- 793624d0 7914b950 PreJIT System.Object.Finalize()
- 00a7c011 00a73030 NONE CLRTesing.Program.Main(System.String[])
- 00a7c015 00a73038 NONE CLRTesing.Program..ctor()
- 00da0070 00a73040JIT CLRTesing.Program..cctor()
CLRTesing.Program類型的靜態(tài)構(gòu)造函數(shù)執(zhí)行時(shí),入口方法Main和CLRTesing.Program的實(shí)例構(gòu)造函數(shù)還沒有被Jit,Main方法中引用到的CLRTesing.P類型也沒有被加載,所以它的Method Table和EEClass結(jié)構(gòu)也沒有建立起來。
#p#
2、在Windbg中detach debuggee,隨便敲一個(gè)字符讓程序繼續(xù)運(yùn)行;接著,入口方法Main開始執(zhí)行,
因?yàn)镸ain方法第一次執(zhí)行,所以通過Stub,Jit編譯器會(huì)被喚起,由于Main方法引用了CLRTesing.P類型,那么在執(zhí)行前會(huì)將CLRTesing.P類型載入,并建立Method Table和其EEClass結(jié)構(gòu),當(dāng)然這個(gè)建立過程也要去查找MetaData表,我認(rèn)為這個(gè)過程是這樣的:
Main方法被調(diào)用,由于它沒有被Jit過,CLR會(huì)通過Main方法的MethodDesc結(jié)構(gòu)的Stub對(duì)Jit編譯器進(jìn)行調(diào)用,CLR通過MetaData表的接口找到Main方法對(duì)應(yīng)的Token,如下:
我們可以看到Main方法的RVA是0x00002050,于是去PE文件的.Text section中的Raw Data中查找image base+RVA這個(gè)位置處的IL代碼,接著Jit編譯器會(huì)對(duì)這段IL代碼進(jìn)行驗(yàn)證,驗(yàn)證過程通過調(diào)用CheckIL方法來實(shí)現(xiàn),這個(gè)方法的簽名可以是這樣的:
- CHECK CheckIL(RVA il);
- CHECK CheckIL(RVA il, COUNT_T size);
驗(yàn)證結(jié)束后把這段IL代碼編譯成本地CPU指令,將編譯后后的CPU指令存到內(nèi)存并修改Main方法的MethodDesc結(jié)構(gòu)中m_CodeOrIL和Stub的值,讓它們指向這個(gè)新的內(nèi)存地址,當(dāng)這個(gè)方法被再次調(diào)用的時(shí)候就會(huì)直接通過這個(gè)地址訪問到本地CPU指令而不會(huì)觸發(fā)第二次編譯。對(duì)于這個(gè)過程大家的看法呢?用Windbg查看各對(duì)象情況:
- Windbg2
- 0:000> !name2ee *!CLRTesing.Program
- Module: 790c2000 (mscorlib.dll)
- --------------------------------------
- Module: 00a72c3c (Hello.exe)
- Token: 0x02000002
- MethodTable: 00a73048
- EEClass: 00a7129c
- Name: CLRTesing.Program
- 0:000> !name2ee *!CLRTesing.P
- Module: 790c2000 (mscorlib.dll)
- --------------------------------------
- Module: 00a72c3c (Hello.exe)
- Token: 0x02000003
- MethodTable: 00a730b8
- EEClass: 00a71730
- Name: CLRTesing.P
- 0:000> !name2ee *!CLRTesing.Q
- Module: 790c2000 (mscorlib.dll)
- --------------------------------------
- Module: 00a72c3c (Hello.exe)
- Token: 0x02000004
- MethodTable:
- EEClass:
- Name: CLRTesing.Q
- 0:000> !dumpmt -md 00a73048
- EEClass: 00a7129c
- Module: 00a72c3c
- Name: CLRTesing.Program
- mdToken: 02000002(D:\test\Hello\bin\Debug\Hello.exe)
- BaseSize: 0xc
- ComponentSize: 0x0
- Number of IFaces in IFaceMap: 0
- Slots in VTable: 7
- --------------------------------------
- MethodDesc Table
- Entry MethodDescJIT Name
- 79371278 7914b928 PreJIT System.Object.ToString()
- 7936b3b0 7914b930 PreJIT System.Object.Equals(System.Object)
- 7936b3d0 7914b948 PreJIT System.Object.GetHashCode()
- 793624d0 7914b950 PreJIT System.Object.Finalize()
- 00da00b0 00a73030JIT CLRTesing.Program.Main(System.String[])
- 00a7c015 00a73038 NONE CLRTesing.Program..ctor()
- 00da0070 00a73040JIT CLRTesing.Program..cctor()
- 0:000> !dumpmt -md 00a730b8
- EEClass: 00a71730
- Module: 00a72c3c
- Name: CLRTesing.P
- mdToken: 02000003(D:\test\Hello\bin\Debug\Hello.exe)
- BaseSize: 0xc
- ComponentSize: 0x0
- Number of IFaces in IFaceMap: 0
- Slots in VTable: 6
- --------------------------------------
- MethodDesc Table
- Entry MethodDescJIT Name
- 79371278 7914b928 PreJIT System.Object.ToString()
- 7936b3b0 7914b930 PreJIT System.Object.Equals(System.Object)
- 7936b3d0 7914b948 PreJIT System.Object.GetHashCode()
- 793624d0 7914b950 PreJIT System.Object.Finalize()
- 00a7c04c 00a730a8 NONE CLRTesing.P.Display()
- 00a7c058 00a730b0 NONE CLRTesing.P..ctor()
我們可以發(fā)現(xiàn)Main方法已經(jīng)被Jit,且它引用的CLRTesing.P類型的相關(guān)結(jié)構(gòu)也已經(jīng)建立起來了,而CLRTesing.P類型的Display方法所引用的CLRTesing.Q類型沒有被載入。
總結(jié)一下,Jit編譯針對(duì)的對(duì)象總是方法,不論是入口方法還是其他方法的Jit過程都類似上述過程,Metadata這這里的作用不言而喻,可以說沒有Metadata的支持就無法進(jìn)行Jit,我覺得Meatadata在Jit編譯期間的作用至少有三個(gè):
1、Jit編譯器通過查找Metadata來找到入口方法;
2、Jit編譯器通過查找Metadata來定位待編譯方法并利用其RVA找到存儲(chǔ)于PE文件中的IL代碼在內(nèi)存中的實(shí)際地址;
3、Jit編譯器在找到IL代碼并準(zhǔn)備編譯為本地CPU指令前所進(jìn)行的IL代碼驗(yàn)證同樣會(huì)用到Metadata,例如,驗(yàn)證方法的合法性需要去核實(shí)方法參數(shù)數(shù)量是正確的、傳給方法的每個(gè)參數(shù)是否都有正確的類型、方法返回值是否正確等等。
文中是一些我通過Shared Source Common Language Infrastructure(SSCLI)看到的和感覺到的東西,希望能給大家理解Jit提供一點(diǎn)幫助,如果有錯(cuò)誤的地方也請(qǐng)大家指出,大家一起學(xué)習(xí)。
最后要說明的是,SSCLI里東西僅作為理解CLR使用,與MS真正實(shí)現(xiàn)CLR的過程可能不一樣。最后,大家在看SSCLI的時(shí)候可以使用Source Insight,個(gè)人感覺還挺好用。
SSCLI的下載地址是:http://www.microsoft.com/downloads/details.aspx?FamilyId=8C09FD61-3F26-4555-AE17-3121B4F51D4D&displaylang=en。
本文來自Leo Zhang的博客園文章《深入了解Jit編譯發(fā)生的過程》
新聞名稱:詳解CLR中Jit編譯發(fā)生的過程
標(biāo)題來源:http://m.5511xx.com/article/dhioccp.html


咨詢
建站咨詢
