天天看點

C# IL代碼、JIT編譯器、MSIL、Ildasm.exe

《CLR via C#》

“雖然這樣說很難讓人信服,但許多人(包括我)都認為托管應用程式的性能實際上超過了非托管應用程式。有許多原因使我們對此深信不疑---例如,當JIT編譯器在運作時将IL代碼編譯成本地代碼時,編譯器對執行環境的認識比非托管編譯器更深刻。”

“JIT編譯器能判斷代碼是否運作在一個Intel Pentium 4 CPU上,并生成相應的本地代碼來利用Pentium 4支援的任何【特殊】指令,相反,非托管代碼通常是針對通用的、具有最小功能集合的CPU編譯的,不會使用提升應用程式性能的特殊指令。”

1. IL代碼:

IL是.NET架構中中間語言(Intermediate Language)的縮寫。使用.NET架構提供的編譯器可以直接将源程式編譯為.exe或.dll檔案,但此時編譯出來的程式代碼并不是CPU能直接執行的機器代碼,而是一種中間語言IL(Intermediate Language)的代碼。

優點:使用中間語言的優點有兩點,一是可以實作平台無關性,既與特定CPU無關;二是隻要把.NET架構某種語言編譯成IL代碼,就實作.NET架構中語言之間的互動操作。

2. JIT編譯器:

JIT編譯器,英文寫作Just-In-Time Compiler,中文意思是即時編譯器。

JIT編譯器能夠将MSIL編譯成為各種不同的機器代碼,以适應對應的系統平台,最終使得程式在目标系統中得到順利地運作。

3. MSIL

Microsoft Intermediate Language (MSIL)微軟中間語言。

MSIL 反彙程式設計式是 MSIL 彙程式設計式(Ilasm.exe) 的夥伴工具。 Ildasm.exe 采用包含 Microsoft中間語言 (MSIL) 代碼的可遷移可執行 (PE) 檔案,并建立相應的文本檔案作為 Ilasm.exe 的輸入。

類似Java位元組碼的語言,也是為了能在不同平台移植所生成的中間代碼。具體過程如下:

  MSIL是将.NET代碼轉化為機器語言的一個中間過程。它是一種介于進階語言和基于Intel的彙編語言的僞彙編語言。當使用者編譯一個.NET程式時,編譯器将源代碼翻譯成一組可以有效地轉換為本機代碼且獨立于CPU的指令。當執行這些指令時,實時(JIT)編譯器将它們轉化為CPU特定的代碼。由于公共語言運作庫支援多種實時編譯器,是以同一段msil代碼可以被不同的編譯器實時編譯并運作在不同的結構上。從理論上來說,MSIL将消除多年以來業界中不同語言之間的紛争。在.NET的世界中可能出現下面的情況一部分代碼可以用EFFIL實作,另一部分代碼使用C#或VB.NET完成的,但是最後這些代碼都将被轉換為中間語言。這給程式員提供了極大的靈活性,程式員可以選擇自己熟悉的語言,并且再也不用為學習不斷推出的新語言而煩惱了。

4. Ildasm.exe 

一.前言:

     微軟的IL反編譯實用程式——Ildasm.exe,可以對可執行檔案(ex,經典的控制台Hello World 的 exe 可執行檔案)抽取出 IL 代碼,并且給出命名空間以及類的視圖。在講述如何反編譯之前,有必要從虛拟CPU的角度來看CLR,這樣有助于先從正面了解代碼執行過程。

虛拟CPU: 

    .NET 程式,其核心皆為 CLR ,而同時CLR的功能卻與CPU非常相近,其中CLR執行IL代碼(或叫做,IL指令)、操作資料,隻不過操作的代碼不同:CPU操作機器語言,而CLR操作IL代碼。

由上,上述講解的是從IL--機器語言的過程,而Ildasm則可以實作将可執行程式(機器語言)--IL代碼,這就是Ildasm的主要功能。 

    在Anytao的《你必須知道的.NET》中對IL代碼專門做了說明,雖然暫時悟不透其"深遠意義",但我還是願意去開始我的IL之旅的,呵呵~。 在此我們先看,Anytao對于掌握(或者了解) IL代碼的重要性:

1.通用的語言基礎是.NET運作的基礎,當我們對運作結果有異議的時候,如何透過表面看本質,IL是必須的基礎; 

2. IL也是更好了解、認識CLR的基礎;

3.大量的執行個體分析是以IL為基礎的,是以了解IL,是讀懂他人代碼的必備基礎,同時自己也可以獲得潛移默化的提高; 

    有上述3條影響,足以讓任何一個有追求的人都鼓足勁,去開始IL之旅了(自然包括我,呵呵~)。

二 .Ildasm.exe 的使用方法:

在應用Ildasm.exe具體反編譯代碼之前,先附上MSDN對于用Ildasm.exe反編譯的經典幫助示例: 

C# IL代碼、JIT編譯器、MSIL、Ildasm.exe

然後我們用Ildasm.exe具體反編譯經典的"Hello World"控制台程式的可執行檔案,展現出來的視圖為:

C# IL代碼、JIT編譯器、MSIL、Ildasm.exe

分析具體IL代碼:

1.MANIFEST清單:

     MANIFEST是一個附加資訊清單,主要包含程式集的一些屬性,如程式集名稱、版本号、雜湊演算法等;

2.ConsoleApplication1.Program類:

    這才是我們介紹的主角。 

首先是Program類: 代碼為

.class private auto ansi beforefieldinit ConsoleApplication1.Program

       extends [mscorlib]System.Object

{

} // end of class ConsoleApplication1.Program

1).class,表示Program是一個類。并且它繼承自程式集—mscorlib的System.Object類;

2)private,表示通路權限;

3)auto,表示程式的記憶體加載全部由CLR來控制;

4)ansi,是為了在沒有托管代碼與托管代碼之間實作無縫轉換。這裡主要指C、C++代碼等;

5)beforefieldinit,是用來标記運作庫(CLR)可以在靜态字段方法生成後的任意時刻,來加載構造器(構造函數);

其次是 .otor方法,代碼為:

C# IL代碼、JIT編譯器、MSIL、Ildasm.exe

.method public hidebysig specialname rtspecialname 

        instance void  .ctor() cil managed

{

  // Code size       7 (0x7)

  .maxstack  8

  IL_0000:  ldarg.0

  IL_0001:  call       instance void [mscorlib]System.Object::.ctor()

  IL_0006:  ret

} // end of method Program::.ctor

C# IL代碼、JIT編譯器、MSIL、Ildasm.exe

1)cil managed:表示其中為IL代碼,訓示編譯器編譯為托管代碼;

2).maxstack:表示調用構造函數.otor期間的評估堆棧(Evaluation Stack) ;

3)IL_0000:标記代碼行開頭;

4)ldarg.0:表示轉載第一個成員參數,在執行個體方法中指的是目前執行個體的引用;

5)call:call一般用于調用靜态方法,因為靜态方法是在編譯期就确定的。而這裡的構造函數.otor()也是在編譯期就制定的。而另一指令callvirt則表示調用執行個體方法, 它是在運作時确定的,因為如前述,當調用方法的繼承關系時,就要比較基類與派生類的同名函數的實作方法(virtual和new),以确定調用的函數所屬的Method Table;

6)ret:表示執行完畢,傳回;

最後是Main()方法,代碼為:

C# IL代碼、JIT編譯器、MSIL、Ildasm.exe

.method private hidebysig static void  Main() cil managed

{

  .entrypoint

  // Code size       13 (0xd)

  .maxstack  8

  IL_0000:  nop

  IL_0001:  ldstr      "Hello world"

  IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)

  IL_000b:  nop

  IL_000c:  ret

} // end of method Program::Main

C# IL代碼、JIT編譯器、MSIL、Ildasm.exe

1) .entrypoint指令表示CLR加載程式時,是首先從.entrypoint開始的,即從Main方法作為程式的入口函數;

2)ldstr:表示将字元串壓棧,在這裡就是将"Hello World." 壓棧;

3)hidebysig:表示當把此類作為基類,存在派生類時,此方法不被繼承,同上構造函數;

   至此,我們對IL代碼的一些指令有了了解,也縱觀了IL世界裡的概況,呵呵~

常用IL指令擴充: 

一:建立對象執行個體的IL指令

關于建立對象的在記憶體配置設定的機制,在《記憶體探尋1之——值類型和引用類型的記憶體配置設定機制》 裡有了詳細的介紹。而常用的建立對象的IL指令使我們更好了解對象的步驟。其主要有4種:

1.newobj: 用于建立引用類型的對象;

2:ldstr:用于建立String對象變量;

3.newarr:用于建立數組型對象;

4:box:在值類型轉換為引用類型的對象時,将值類型拷貝紙托管堆上配置設定記憶體; 

二:通過IL代碼,更好地了解屬性 

我們在C++中,在典型的類中,都會定義用于控制有效性輸入的Set()函數,以及用于不同方式顯示的Get()函數。然而在C#中,它将Get()函數和Set()結合在一起,剛開始難免會為之混淆。然而若通過 Ildasm.exe對程式反編譯後觀察屬性的本質,即可看到其執行機制,如下圖示(注:選自網際網路):

C# IL代碼、JIT編譯器、MSIL、Ildasm.exe

由我們前面的分析IL代碼的方法,以及上圖的展示,我們可以看到屬性被重新分為Get()函數和Set()函數。ex,屬性Name,被分解為get_Name()函數和set_Name(String s)函數。這樣我們對其本質就一目了然了!至于其屬性的特殊表示形式,隻看做是Set()函數和Get()函數的完美結合體就可以了,這也是C#語言的優美展現啊,呵呵~

綜述之,我們對反編譯工具Ildasm.exe有了一定認識,最主要的,我們通過它反編譯的IL代碼,對基本的IL指令有了一定的了解,也對以後的在把IL代碼作為有力工具 使用的過程中,更向前了一步!然而,這些都還隻是IL的基礎,需要繼續深入,呵呵~

C# IL代碼、JIT編譯器、MSIL、Ildasm.exe