《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反編譯的經典幫助示例:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZwpmLlxWaw12bj9CXn5Watdmbp12ZuFWevwVbvN2Xzd2bsJmbj9CXt92YuM3ZvxmYuNmLzV2Zh1Wavw1LcpDc0RHaiojIsJye.jpg)
然後我們用Ildasm.exe具體反編譯經典的"Hello World"控制台程式的可執行檔案,展現出來的視圖為:
分析具體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方法,代碼為:
.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
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()方法,代碼為:
.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
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對程式反編譯後觀察屬性的本質,即可看到其執行機制,如下圖示(注:選自網際網路):
由我們前面的分析IL代碼的方法,以及上圖的展示,我們可以看到屬性被重新分為Get()函數和Set()函數。ex,屬性Name,被分解為get_Name()函數和set_Name(String s)函數。這樣我們對其本質就一目了然了!至于其屬性的特殊表示形式,隻看做是Set()函數和Get()函數的完美結合體就可以了,這也是C#語言的優美展現啊,呵呵~
綜述之,我們對反編譯工具Ildasm.exe有了一定認識,最主要的,我們通過它反編譯的IL代碼,對基本的IL指令有了一定的了解,也對以後的在把IL代碼作為有力工具 使用的過程中,更向前了一步!然而,這些都還隻是IL的基礎,需要繼續深入,呵呵~