淺析MSIL中間語言——基礎篇(轉)

來自:https://www.cnblogs.com/dwlsxj/p/MSIL.html
1、開篇

  研究MSIL純屬於我的喜愛,說在前面MSIL應用於開發的地方不多,可是很大程度上可以幫着咱們理解底層的原理,這是我瞭解MSIL的主要緣由。託管代碼表示應用程序的方法的功能,它們以微軟的中間語言(Microsoft intermediate language,MSIL)或公共語言運行(common intermediate language,CIL)的抽象二進制形式進行編碼。html

  MSIL代碼由CLR「託管」。CLR託管至少包括三個主要的活動:類型控制,結構化異常和垃圾收集。類型控制設計在執行期間項類型的驗證和轉換。託管異常處理在功能上與「非託管的」結構化異常處理相似,但它是由CLR執行的而不是有操做系統執行的。垃圾收集涉及對再也不使用的對象進行自動標識和釋放。編程

  在CLR環境下,.NET應用程序由一個或多個託管可執行體組成,其中每個都攜帶着源數據和託管代碼。託管的可執行體成爲模塊,主要包括兩個組件:元數據和MSIL代碼。CLR處理這兩個組件的主要子系統是加載程序(Loader)和JIT(Just-in-time,即時)編譯器。windows

  接下來咱們主要講的是託管可執行體裏面的MSIL(中間語言),再講MSIL時,先把微軟的整個框架體系簡單歸納下:(見下圖一)框架

  簡要概述下整個過程,首先是咱們編寫的C#源文件hello.cs經過C#編譯器進行編譯,編譯成.NET 的PE文件結構,也就是exe文件格式,當程序運行時,Windows的Loader加載器不會負責該程序的內存分配,線程管理等工做,而是隻負責跳轉到CLR的執行引擎(EE)中,將控制權交由CLR,由CLR進行分配內存,線程管理,異常處理等。ide

  經過查看.NET 的PE文件導入表中只有一個API,exe對應mscoree.dll的_CorExeMain;而dll對應mscoree.dll的_CorDllMain。這就說明windows的loader加載器載入.NET PE後,只負責跳轉到相應的DLL,隨後改程序邊運行在EE的監管中。查看導入表以下圖所示:工具

 

2、初探MSIL

  我以爲學習每一項知識時,一些技巧性的東西是靠一步一步去積累的,可是我認爲底層的探索也是學習當中必不可缺的一部分。有些時候底層的知識能夠呈現出原理性的東西,越接近底層的知識就越靠近實現部分,好了廢話也很少說接下來咱們就來探討MSIL中間語言,MSIL中間語言是基於堆棧的面嚮對象語言。學習

  藉助一個簡單的例子進行分析MSIL中間語言,每一個編程技術人員都是從Hello World開始那咱們也從Hello World開始。 編碼

複製代碼
1 class Program 2  { 3 static void Main(string[] args) 4  { 5 string hello = "Hello World"; 6  Console.WriteLine(hello); 7  Console.Read(); 8  } 9 }
複製代碼

  輸出結果很明顯是:Hello World,以下圖所示:lua

  

  接下來咱們將要分析該程序的MSIL代碼,經過ILDASM.EXE工具將exe文件進行反編譯成MSIL中間語言。以下圖所示:spa

  簡要概述:

  關鍵字:.method表示方法的意思,.method private hidebysig static void  Main(string[] args) cil managed表示的意思就是static void main(string[] args)

  .entrypoint標誌方法的入口

  .maxstack表示分配堆棧大小

  .locals init中存放的是當前方法的局部變量,這裏面是string類型,它的名稱叫hello。Init指令表示對變量應以對應的類型默認值進行初始化,一般狀況下變量名能夠省略,在代碼中將以零基索引來引用

  例如:stloc.0表示將Envaluation Stack中的一個棧頂數值保存到局部變量0(Call Stack)中。

  先介紹幾個關於MSIL內部知識點:

  ①.Managed Heap:這是動態配置(Dynamic Allocation)的記憶體,由 Garbage Collector(GC)在執行時自動管理,整個 Process 共用一個 Managed Heap,能夠理解爲引用類型的東西都放在這個Managed Heap中。

  ②.Call Stack:這是由 .NET CLR 在執行時自動管理的記憶體,每一個Thread都有本身的Call Stack堆棧。每調用一次method,就會使得Call Stack上多了一個Record Frame;調用完畢以後,此Record Frame會被丟棄。通常來講,Record Frame內記錄着method參數(Parameter)、返回位址(Return Address)、以及局部變量(Local Variable)。.NET CLR都是使用0, 1, 2…編號的方式來識別局部變量。

  ③.Evaluation Stack:這是由.NET CLR在執行時自動管理的記憶體,每一個Thread都有本身專屬的Evaluation Stack。壓入的到Evaluation Stack的值,當方法調用結束時必須保持這個堆棧的平衡,這裏面存放例如局部變量值,以及引用類型的地址。

  指令ldc是將參數存儲至堆棧Evaluation Stack

  指令stloc是將變量存儲至堆棧Call Stack

  技巧:ld開頭就是加載數據到Evaluation Stack中,而st開頭就是將Envaluation Stack中的數據保存到Call StackCall Stack存放局部變量值。

  接下來咱們將演示代碼的堆棧狀況。

  首先進入的是IL_0000段的代碼爲nop,這段代碼代表了沒有任何操做。

   

  接下來就要到了IL_0001段代碼爲ldstr 「hello World」,ldstr加載字符串是將字符串的引用放在了Envaluation Stack中,而真正的字符串放在了Managed Heap中,詳情請見下圖:

  

   接下來就要運行到了stloc.0這條指令的意思就是講參數保存到局部變量中。

  

  將Envaluation Stack中的值保存到 Call Stack中,由於Envaluation Stack中存放的是「hello World」字符串的地址,因此V0存放的也是字符串的地址。

接下來要運行到了ldloc.0加載到Envaluation Stack中局部變量0的地址。

 

  接下來運行MSIL的call語句,從 Evaluation Stack 中取出一個值,此值爲 Reference Type,調用 mscorlib.dll 所提供的 System.Console::WriteLine(string),注意這裏用的call,由於這個是靜態方法(static method),而不能用CallVirt方法。結構圖以下所示:

  接下來就要調用靜態方法System.Console::Read()等待用戶輸入以後,將輸入值放入到Envaluation中去,最後再用pop指令將數值從Envaluation Stack中彈出來,最後就到達了ret這個地方,此指令的意思是:結束這次調用(也就是 Main 的調用)。此時會檢查 Evaluation Stack 內剩下的資料,因爲 Main() 告知不須要傳出值(void),因此 Evaluation Stack 內必須是空的,本範例符合這樣的狀況,因此此時能夠順利結束這次調用。而 Main 的調用一結束,程序也隨之結束。

3、結束語

  經過這篇文章能夠清晰的瞭解MSIL中間語言的運行機制,是基於堆棧的形式操做。再次聲明學習MSIL只是因爲我的興趣,但願各位可以提出寶貴的意見以及上述有錯的地方可以指正,小丁虛心求教。

相關文章
相關標籤/搜索