[你必须知道的.NET]第二十五回:认识元数据和IL(中)
| 說在,開篇之前 |
| 書接上回[第二十四回:認識元數據和IL(上)],我們對PE文件、程序集、托管模塊,這些概念與元數據、IL的關系進行了必要的鋪墊,同時順便熟悉了以ILDASM工具進行反編譯的基本方法認知,下面是時候來了解什么是元數據,什么是IL這個話題了,我們繼續。? 很早就有說說Metadata(元數據)和IL(中間語言)的想法了,一直在這篇開始才算腳踏實地的對這兩個階級兄弟投去些細關懷,雖然來得沒有《第一回:恩怨情仇:is和as》那么迅速,但是Metadata和IL卻是絕對重量級的內容,值得我們在任何時間關注,本文就是開始。? ??????????????????????????????????????????????????????????????????????????????????????www.anytao.com |
?
3 元數據是什么?
元數據,就是描述數據的數據。這一概念并非CLR之獨創,Metadata存在于任何對數據和數據關系中,例如程序集清單信息也被稱為程序集元數據。而不同系統的元數據也相應具有本身的特點,.NET元數據也是如此。那么,CLR元數據描述的是哪些內容呢?正如前文的描述一樣,編譯之后,類型信息將以元數據的形式保存在PE格式文件中。.NET是基于面向對象的,所以元數據描述的主要目標就是面向對象的基本元素:類、類型、屬性、方法、字段、參數、特性等,主要包括:
- 定義表,描述了源代碼中定義的類型和成員信息,主要包括:TypeDef、MehodDef、FieldDef、ModuleDef、PropertyDef等。
- 引用表,描述了源代碼中引用的類型和成員信息,引用元素可以是同一程序集的其他模塊,也可以是不同程序集的模塊,主要包括:AssemblyRef、TypeRef、ModuleRef、MethodsRef等。
- 指針表,使用指針表引用未知代碼,主要包括:MethodPtr、FieldPtr、ParamPtr等。
- 堆,以stream的形式保存的信息堆,主要包括:#String、#Blob、#US、#GUIDe等。
如前文所述,我們以ILDasm.exe可以通過反編譯的方式,通過執行Ctrl+M快捷鍵來獲取該程序集所使用的MetaData信息列表,在.NET中每個模塊包含了44個CLR元數據表,如下:
| 表記錄 | 元數據表 | 說明 |
| 0(0) | ModuleDef | 描述當前模塊 |
| 1(0x1) | TypeRef | 描述引用Type,為每個引用到類型保存一條記錄 |
| 2(0x2) | TypeDef | 描述Type定義,每個Type將在TypeDef表中保存一條記錄 |
| 3(0x3) | FieldPtr | 描述字段指針,定義類的字段時的中間查找表 |
| 4(0x4) | FieldDef | 描述字段定義 |
| 5(0x5) | MethodPtr | 描述方法指針,定義類的方法時的中間查找表 |
| 6(0x6) | MethodDef | 描述方法定義 |
| 7(0x7) | ParamPtr | 描述參數指針,定義類的參數時的中間查找表 |
| 8(0x8) | ParamDef | 描述方法的參數定義 |
| 9(0x9) | InterfaceImpl | 描述有哪些類型實現了哪些接口 |
| 10(0xa) | MemberRef | 描述引用成員的情況,引用成員可以是方法、字段還有屬性。 |
| 11(0xb) | Constant | 描述了參數、字段和屬性的常數值 |
| 12(0xc) | CustomAttribute | 描述了特性的定義 |
| 13(0xd) | FieldMarshal | 描述了與非托管代碼交互時,參數和字段的傳遞方式。 |
| 14(0xe) | DeclSecurity | 描述了對于類、方法和程序集的安全性 |
| 15(0xf) | ClassLayout | 描述類加載時的布局信息 |
| 16(0x10) | FieldLayout | 描述單個字段的偏移或序號 |
| 17(0x11) | StandAloneSig | 描述未被任何其他表引用的簽名 |
| 18(0x12) | EventMap | 描述類的事件列表 |
| 19(0x13) | EventPtr | 描述了事件指針,定義事件時的中間查找表 |
| 20(0x14) | Event??????????????? | 描述事件 |
| 21(0x15) | PropertyMap????????? | 描述類的屬性列表 |
| 22(0x16) | PropertyPtr????????? | 描述了屬性指針,定義類的屬性時的中間查找表 |
| 23(0x17) | Property???????????? | 描述屬性 |
| 24(0x18) | MethodSemantics????? | 描述事件、屬性與方法的關聯 |
| 25(0x19) | MethodImpl?????????? | 描述方法的實現 |
| 26(0x1a) | ModuleRef??????????? | 描述外部模塊的引用 |
| 27(0x1b) | TypeSpec???????????? | 描述了對TypeDef或者TypeRef的說明 |
| 28(0x1c) | ImplMap????????????? | 描述了程序集使用的所有非托管代碼的方法 |
| 29(0x1d) | FieldRVA???????????? | 字段表的擴展,RVA給出了一個字段的原始值位置 |
| 30(0x1e) | ENCLog?????????????? | 描述在Edit-And-Continue模式中哪些元數據被修改過 |
| 31(0x1f) | ENCMap?????????????? | 描述在Edit-And-Continue模式中的映射 |
| 32(0x20) | Assembly???????????? | 描述程序集定義 |
| 33(0x21) | AssemblyProcessor??? | 未使用 |
| 34(0x22) | AssemblyOS?????????? | 未使用 |
| 35(0x23) | AssemblyRef????????? | 描述引用的程序集 |
| 36(0x24) | AssemblyRefProcessor | 未使用 |
| 37(0x25) | AssemblyRefOS??????? | 未使用 |
| 38(0x26) | File???????????????? | 描述外部文件 |
| 39(0x27) | ExportedType???????? | 描述在同一程序集但不同模塊,有哪些類型 |
| 40(0x28) | ManifestResource???? | 描述資源信息 |
| 41(0x29) | NestedClass????????? | 描述嵌套類型定義 |
| 42(0x2a) | GenericParam???????? | 描述了泛型類型定義或者泛型方法定義所使用的泛型參數 |
| 43(0x2b) | MethodSpec?????????? | 描述泛型方法的實例化 |
| 44(0x2c) | GenericParamConstraint | 描述了每個泛型參數的約束 |
然后是6個命名堆:
| 堆 | 說明 |
| #String | 一個AscII string數組,被元數據表所引用,來表示方法名、字段名、類名、變量名以及資源相關字符串,但不包含string literals。 |
| #Blob | 包含元數據引用的二進制對象,但不包含用戶定義對象 |
| #US | 一個unicode string數組,包含了定義在代碼中的字符串(string literals),這些字符串可以直接由ldstr指令加載獲取,還記得嗎?我們在《第二十二回:字符串駐留(上)---帶著問題思考》中對字符串創建過程的論述嗎? |
| #GUID | 保存了128byte的GUID值,由元數據表引用 |
| #~ | 一個特殊堆,包含了所有的元數據表,會引用其他的堆。 |
| #- | 一個未壓縮的#~堆。除了#-堆,其他堆都是壓縮的。 |
Note:對于#String和#US,一個簡單的區別就是:
string hello = "Hello, World";變量hello名,將保存在#String,而代碼中字符串信息“Hello, World”則被保存在#US中。
關于元數據信息的詳細描述,例如每個表包含哪些列,不同表間的關系,請參考[Standard ECMA-335]和[The .NET File Format]。
在PE文件格式中,Metadata有著復雜的結構,我試圖以數據庫管理數據的角度出發來理解元數據的結構和關系,所以表示元數據的邏輯結構被成為元數據表,類似于數據庫表有主鍵和Sechema,元數據表以RID(表索引)和元-元數據表示類同的概念,以TypeDef表為例,通過數據引用關系同時與Field、Method、TypeRef等表發生關聯,其他表間又有類似的關系,從而形成一個復雜的類數據庫結構:
因此,元數據是保存了類型的編譯后數據,是.NET程序運行的基礎,我們可以在運行時動態的以反射的方式獲取元數據信息,而這些信息在.NET Framework中以System.Type、MethodInfo等封裝,例如截取MSDN中一個類間關系的簡單示例:
對于每個CLR類型而言都可以通過Object.GetType方法返回其Type,從而任意的取到所有的運行時元數據信息:
// Release : code04, 2009/02/21 // Author : Anytao, http://www.anytao.com // List : Program.cs private static void ShowMemberInfo() {var assems = AppDomain.CurrentDomain.GetAssemblies();foreach (Assembly ass in assems){foreach (Type t in ass.GetTypes()){foreach (MemberInfo mi in t.GetMembers()){Console.WriteLine("Name:{0}, Type:{1}", mi.Name, mi.MemberType.ToString());}}} }執行上述方法,將獲取一個長長的列表,看到很多熟悉的符號:-)
4 IL是什么?
IL,又稱為CIL或者MSIL,翻譯為中文就是中間語言,由ECMA組織(Standard ECMA-335)提供完整的定義和規范。顧名思義,中間語言正如它的名稱所言,任何與CLR兼容的編譯器所生成的都是中間語言代碼,這是實現CLR跨語言的基礎結構之一。IL就像一座橋梁,其指令集獨立于CPU指令而存在,可以由JIT編譯器在運行時翻譯為本地代碼執行,連接了任何遵守CLS規范的高級語言,為.NET平臺提供了最基本的支持。在[你必須知道的.NET]一書中,我用一整章(第3章 “一切從IL開始”)的篇幅對IL的基本內容進行了相應的介紹,所以關于IL的基礎內容例如基本類型、IL分析方法、常見指令、基本運算等,就不在本文有所贅述,只對IL基本內容進行一點小結:
- IL是一種面向對象的機器語言,因此具有面向對象語言的所有特性,類、對象、繼承、多態等仍然是IL語言的基本概念。
- IL指令獨立于CPU指令,CLR通過JIT編譯機制將其轉換為本地代碼。
- IL和元數據是了解CLR運行機制的重要內容,對于我們打開CLR神秘面紗有著重要的意義。
如前文[初次接觸]部分論述的一樣,可以通過ILDasm.exe或者Reflector工具對托管代碼執行反編譯來查看其IL代碼,對于很多情況下IL代碼分析可以解決很多高級語言隱藏的語法糖游戲,例如C#3.0提出的自動屬性、隱式類型、匿名類型、擴展方法等都可以很快從IL分析中找到答案,所以適當的了解IL是必要的。那么我們在下面JIT編譯時的一個片段來了解IL代碼對于托管程序執行的作用。
另外,Metadata描述了靜態的結構,而IL闡釋了動態的執行,而IL代碼是通過一個4字節大小的地址引用元數據表的。該引用被稱為元數據符號(Metadata Token,也就是記錄元數據表的位置信息),在ILdasm.exe工具中選中“Show token values”,就可以在IL代碼中看到IL代碼通過Metadata Token引用元數據表的情況:
.method /*06000003*/ private hidebysig static void Main(string[] args) cil managed {.entrypoint// Code size 36 (0x24).maxstack 2.locals /*11000002*/ init ([0] int32 id,[1] class Anytao.Insidenet.MetadataIL.One/*02000004*/ one,[2] class Anytao.Insidenet.MetadataIL.Two/*02000002*/ two)IL_0000: nopIL_0001: ldc.i4.1IL_0002: stloc.0IL_0003: newobj instance void Anytao.Insidenet.MetadataIL.One/*02000004*/::.ctor() /* 06000007 */IL_0008: stloc.1IL_0009: ldloc.1IL_000a: ldloc.0IL_000b: callvirt instance void Anytao.Insidenet.MetadataIL.One/*02000004*/::set_ID(int32) /* 06000006 */IL_0010: nopIL_0011: newobj instance void Anytao.Insidenet.MetadataIL.Two/*02000002*/::.ctor() /* 06000002 */IL_0016: stloc.2IL_0017: ldloc.2IL_0018: callvirt instance string Anytao.Insidenet.MetadataIL.Two/*02000002*/::SayHello() /* 06000001 */IL_001d: call void [mscorlib/*23000001*/]System.Console/*01000012*/::WriteLine(string) /* 0A000011 */IL_0022: nopIL_0023: ret } // end of method Program::Main其中,按照ECMA定義的規范,元數據第一個字節表示引用的元數據表,而其余三個字節則表示在相應元數據表中的記錄,例如06000003表示了引用了MethodDef(06)表的000003項Main方法。
我們可以通過Type的MetadataToken屬性在運行時反射獲取類型的元數據符號,例如:
static void Main(string[] args) {Console.WriteLine(typeof(One).MetadataToken); }?
?
有了上述所有的準備,我們就可以著手分析元數據和IL在程序執行時的角色和關聯。
| 欲知后事如何,且聽下文繼續:-) |
??????????????????????????????????????????????????????????????????????????????????????www.anytao.com |
?
支持anytao的創業產品Worktile
Worktile,新一代簡單好用、體驗極致的團隊協同、項目管理工具,讓你和你的團隊隨時隨地一起工作。完全免費,現在就去了解一下吧。
https://worktile.com
參考文獻
- 《你必須知道的.NET》第3章 “一切從IL開始”
- DonBox,《.NET本質論》
- http://www.sloppycode.net/articles/inside-net-assemblies-and-metadata.aspx
- http://www.codeproject.com/KB/dotnet/dotnetformat.aspx
?
支持(0)?反對(0)
??
#10樓?2009-02-25 09:57?yy125
關注中,元數據和IL是不是和語言無關呢,我用vb寫,是不是也生成一樣的元數據和IL呢
支持(0)?反對(0)
??
#11樓?[樓主]?2009-02-25 10:06?Anytao
@yy125?
對,這是.NET跨語言的基礎,不同的高級語言(基于.NET平臺,這是前提)由各自編譯器編譯之后生成的元數據和IL是統一的。
支持(0)?反對(0)
??
#12樓?2009-02-25 23:45?未登錄的包建強
anytao,你這篇寫得確實不錯,干凈利落,一針見血。?
下面說但是,?
你用反射來說明元數據,有點自圓其說。我建議用BinaryStream,代碼如下:?
FileStream s = new FileStream("C:\\mdata\\b.exe", FileMode.Open);?
BinaryReader r = new BinaryReader(s);?
byte a, b;?
a = r.ReadByte();?
b = r.ReadByte();?
哥哥我就是要一個字節一個字節地去讀exe文件,當然,這招兒也是從老外那里學來的。?
參考文獻:http://www.cnblogs.com/Jax/archive/2009/01/02/1366888.html
支持(0)?反對(0)
??
#13樓?2009-02-25 23:54?未登錄的包建強
@Anders?
《.NET 設計規范--.NET約定、慣用法與模式》,這本書我有,回頭我送你好了,算是給我干女兒的見面禮。?
@胖趙?
就是來攪黃你生意的。娃哈哈。。。
支持(0)?反對(0)
??
#14樓?[樓主]?2009-02-26 10:26?Anytao
@未登錄的包建強?
不知道說啥了:-)
支持(0)?反對(0)
??
#15樓?2009-02-26 22:54?波波塔
#String 一個AscII string數組 ...?
如果 string 中文test="中文等也是用AscII 存儲在 #string里面的嗎?
支持(0)?反對(0)
??
#16樓?2009-03-04 21:15?JacksonLin
嚴重支持
支持(0)?反對(0)
??
#17樓?2009-12-22 14:49?飛翔的小豬
=============================================
?引用表,描述了源代碼中引用的類型和成員信息,引用元素可以是同一程序集的其他模塊,也可以是不同程序集的模塊,主要包括:AssemblyRef、TypeRef、ModuleRef、MethodsRef等。
=============================================
引用表中應該不包括MethodsRef,不存在單獨的FieldRef和MethodRef,因為MemberRef包括了指向字段和方法的引用,你的44個CLR元數據表中也沒有包含,不知道是不是筆誤。
看你的文章要比看專著來的輕松得多。
小弟班門弄斧了。
總結
以上是生活随笔為你收集整理的[你必须知道的.NET]第二十五回:认识元数据和IL(中)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 13499元!大疆图传神器DJI Tra
- 下一篇: 国内价格太良心了 vivo X80 Pr