讀懂IL代碼就這麼簡單 (一)

一前言

  感謝 @冰麟輕武 指出文章的錯誤之處,現已更正html

  對於IL代碼沒了解以前總感受很神奇,初一看徹底不知所云,只聽高手們說,瞭解IL代碼你能更加清楚的知道你的代碼是如何運行相互調用的,此言一出不明覺厲框架

而後開始接觸IL,瞭解了一段時後才發現原來讀懂IL代碼並不難。進入正題ide

   1.1  什麼是IL函數

  IL是.NET框架中中間語言(Intermediate Language)的縮寫。使用.NET框架提供的編譯器能夠直接將源程序編譯爲.exe或.dll文件,但此時編譯出來的程序代碼並非CPU能直接執行的機器代碼,而是一種中間語言IL(Intermediate Language)的代碼(來源百度)lua

   1.2 爲何要了解ILspa

    在不少時候不明白代碼是如何操做時就能夠經過IL指令來解釋,好比,裝箱,拆箱是否只是聽別人說或者書上講是怎麼怎麼實現的,本身是否證明過呢?瞭解IL指令你可清楚看到是每一步是如何處理的.net

   1.3  怎麼學IL線程

   世上有個定律叫「二八定律」 ,80%的功能,只要用20%的技術就能夠完成,但要完成另外20%可能就須要80%技術了,對於IL代碼也是如此,有200多個指令,咱們只須要用到其20%的指令就能夠解決咱們80%的問題了,因此我不會寫太多,只是讓你們能看懂普通的程序代碼編譯成IL代碼後就好了,還有就是要多看,IL代碼的每一條指令都是特定的意思,看得多了天然就懂了,當對本身代碼有疑問時嘗試看看它對應的IL代碼,也許你會了解得更多。3d

 IL指令大全  點這裏code

 IL代碼編譯器 ILDasm   點這裏

二 如何查看IL代碼

  2.1 步驟

   1 編寫代碼並編譯經過

   2  找到源文件的obj文件下的 .exe文件

     3 導入到ILDasm中反編譯成IL代碼

上圖

1 -2步                                                     3  導入到ILDasm中                      

 

           

  ILDasm中圖標含義

 

 

 

三  如何讀IL(大體瞭解)

  以上步驟完成後咱們就能夠看到代碼被編譯後的IL代碼,如下部份將會對每一條IL指令作詳細的解釋

C#代碼 

1         static void Main(string[] args)
2         {
3             int i = 1;
4             int j = 2;
5             int k = 3;
6             Console.WriteLine(i+j+k);
7         }

 

IL代碼

 // Call  Stack是一個棧,而Call Stack中的Record Frame則是一個局部變量列表,用於存儲 .locals init (int32 V_0,int32 V_1,int32 V_2)初始化後的參數 V_0,V_1,V_2

因圖中沒有把Record Frame 標記出來,因此本身畫了一張圖

 

// Evaluation Stack 是一個棧 ldc.i4.2 這種指令都會先把值壓入棧中等待操做

在第四段時你們能夠理解得更清楚一點

另外@Learning hard 指出IL指令中第 9 11 13行容易讓人誤解值是從Record Frame中加載的

現強調IL指令中 第 9 11 13行的ldc.i4.1,ldc.i4.2,ldc.i4.3 執行這幾條指令時 值是尚未加載到Record Frame中的,可是MSDN也沒有指出從哪裏加載

因此只能根據我的的想法解釋,程序在編譯後值類型數據會存在線程棧中,因此我認爲此時的9 11 13行的值是從線程棧中取的

 1  .method private hidebysig static void  Main(string[] args) cil managed
 2 {
 3   .entrypoint  //程序入口
 4   // Code size       19 (0x13)
 5   .maxstack  3  //定義函數代碼所用堆棧的最大深度,也指Evaluation Stackk中最多能同時存在3個值
6 //如下咱們把它看作是完成代碼中的初始化 7 .locals init (int32 V_0,int32 V_1,int32 V_2) //定義 int 類型參數 V_0,V_1,V_2 (此時已經把V_0,V_1,V_2存入了Call Stack中的Record Frame中) 8 IL_0000: nop //即No Operation 沒有任何操做,咱們也不用管它
9 IL_0001: ldc.i4.1 //加載第一個變量"i"的值      (壓入Evaluation Stack中) 10 IL_0002: stloc.0 //從棧中把"i"的值彈出並賦值給Record Frame中第0個位置(V_0)   
11   IL_0003:  ldc.i4.2    //加載第二個變量"j"的值       (壓入Evaluation Stack中) 
12   IL_0004:  stloc.1     //從棧中把"j"的值彈出並賦值給Record Frame中第1個位置(V_1)
13   IL_0005:  ldc.i4.3    //加載第三個變量"k"的值       (壓入Evaluation Stack中)
14   IL_0006:  stloc.2     //從棧中把 "k"的值彈出並賦值給Record Frame中第2個位置(V_2)
15 
16    //上面代碼初始化完成後要開始輸出了,因此要把數據從Record Frame中取出
17 
18   IL_0007:  ldloc.0     //取Record Frame中位置爲0的元素(V_0)的值("i"的值)並壓入棧中  (至關於Copy一份值Call Stack中V_0的值。V_0自己的值是不變的)
19   IL_0008:  ldloc.1     //取Record Frame中位置爲1的元素(V_1)的值("j"的值)並壓入棧中     (同上)
20   IL_0009:  add         // 作加法操做
21   IL_000a:  ldloc.2     // 取出Record Frame中位置爲2的元素(V_2)的值("k"的值)並壓入棧中
22   IL_000b:  add         // 作加法操做
23   IL_000c:  call       void [mscorlib]System.Console::WriteLine(int32) //調用輸出方法
24   IL_0011:  nop
25   IL_0012:  ret         //即爲  return  標記 返回值
26 } // end of method Program::Main

 指令詳解

.maxstack:評估堆棧(Evaluation Stack)可容納數據項的最大個數

.locals init (int32 V_0,int32  V_1,int32 V_2):定義變量並存入Call Stack中的Record Frame中

nop:即No Operation 沒有任何操做,咱們也不用管它,

ldstr.:即Load String 把字符串加壓入Evaluation Stack中 

stloc.:把Evaluation Stack中的值彈出賦值到Call Stack中的Record Frame中

ldloc.:把Call Stack中的Record Frame中指定位置的值取出(copy)存入 Evaluation Stack中   以上兩條指令爲相互的操做stloc賦值,ldloc取值

call:  調用指定的方法

ret: 即return  標記返回

  每一句IL代碼都加了註釋後,是否是以爲IL代碼其實並不難呢,由於它的每一條指令都是固定的,你只要記住了,看IL代碼就比較輕鬆了。

 

四 如何讀IL(深刻了解)

4.1 提出問題

  有了上面的一點IL基礎後,如今咱們來深刻一點點,

  有以下幾個問題:

  1  當 ldc.i4.1 這一指定加載 「i」 這個變量後並無立刻賦值給Record Frame中的元素,而是要執行 stloc.0 後才賦值,那沒賦值前是存在哪裏的呢?

  2 ldloc.0  把元素取出來後,存在哪裏的?

  3 add操做完成後值存在哪裏?

4.2 概念引入

  Managed Heap:這是動態配置(Dynamic Allocation)的記憶體,由 Garbage Collector(GC)在執行時自動管理,整個 Process 共用一個

  Managed Heap(我理解爲託管堆,存儲引用類型的值)。

  Evaluation Stack:這是由 .NET CLR 在執行時自動管理的記憶體,每個 Thread 都有本身專屬的 Evaluation Stack(我理解爲相似一個臨時存放值類型數據的線程棧)

  Call Stack:這是由 .NET CLR 在執行時自動管理的記憶體,每個 Thread 都有本身專屬的 Call Stack。每呼叫一次 method,就會使得 Call Stack 上多了一個 Record     Frame;呼叫完畢之後,此 Record Frame 會被丟棄(我理解爲一個局部變量表,用於存放.locals init(int32 V_0)指令的參數值如:V_0)

 

4.3  IL指令詳解

 對三個名詞作解釋後如今咱們再來仔細看看執行IL指令時,對應的變量是如何存放的

IL_0001:  ldc.i4.1    //加載第一個變量i  
首先對 ldc.i4.1 作下細解:變量的值爲1 時IL指令就是ldc.i4.1 ,變量值爲2 時IL指令就是ldc.i4.2,依此類推一直到ldc.i4.8
當爲-1 時IL指令爲ldc.i4.M1,當超過8時就是一個統一指令 ldc.i4.S

IL_0001: ldc.i4.1 //加載第一個變量i

當執行這一條指令時會把變量i 的值壓入Evaluation Stack中作臨時存儲



IL_0002:  stloc.0     //把i 賦值給Call Stack中第0個位置
當執行這一條指信時會把Evaluation Stack 中的 i 彈出賦值給Record Frame中的第0個位置



IL_0007:  ldloc.0     //取出Record Frame位置爲0的元素 (i)
當執行這條指令時會將 Record Frame中的位置爲0的元素的值取出(copy)壓入Evaluation Stack 等待作加法的指令 Add

  IL_000b:  add         // 作加法操做
  add這一操做完成後,會把結果存在Evaluation Stack中等待下一步的指令操做
4.4 問題回答
  以上內容看完開始的問題相應也解決了
  1 ldc.i4.1 把值取出來後先存在 Evaluation Stack中 執行了stloc.0 後纔會存入Record Frame中指定的元素中
  2 ldloc.0 把取出來後也是先壓入 Evaluation Stack 等持指令
  3 add 操做完成後值是暫存於 Evaluation Stack中的


以上把IL指令是如何操做內存中的值作了一點很基本的介紹,讓你們在瞭解IL指令時,知道是如何操做內存中的值的。我想對於理解IL指令或許更透徹一點。



五 總結



  這一篇只寫了IL中最基本的幾個指令,而後講解了IL指令是如何操做內存中數據的。古人云:水得一口一口喝,路得一步一步走,步子邁得大了容易扯着蛋,慢慢來內容雖然少了點,可是還會有下篇的。下一篇仍是會寫IL的一些基本指令,我會結合我本身的理解,儘可能把文字寫得通俗一點,讓你們更容易理解。

另外本人水平有限,不免會有理解錯誤的地方,若有發現,請指出!我會立刻修改,以避免誤導他人。

若是您以爲本文能給您帶來一點收穫不妨點下 推薦 讓更多的人瞭解IL,您的推薦是我源源不斷的寫做力
若是以爲個人博客還不錯,那就關個注吧~

成長在於積累

 

 參考資料:《你必須知道的.net》,MSDN

Record Frame
相關文章
相關標籤/搜索