[你必须知道的.NET]第二十五回:认识元数据和IL(中)
| 說在,開篇之前 |
| 書接上回[第二十四回:認(rèn)識(shí)元數(shù)據(jù)和IL(上)],我們對(duì)PE文件、程序集、托管模塊,這些概念與元數(shù)據(jù)、IL的關(guān)系進(jìn)行了必要的鋪墊,同時(shí)順便熟悉了以ILDASM工具進(jìn)行反編譯的基本方法認(rèn)知,下面是時(shí)候來了解什么是元數(shù)據(jù),什么是IL這個(gè)話題了,我們繼續(xù)。? 很早就有說說Metadata(元數(shù)據(jù))和IL(中間語言)的想法了,一直在這篇開始才算腳踏實(shí)地的對(duì)這兩個(gè)階級(jí)兄弟投去些細(xì)關(guān)懷,雖然來得沒有《第一回:恩怨情仇:is和as》那么迅速,但是Metadata和IL卻是絕對(duì)重量級(jí)的內(nèi)容,值得我們?cè)谌魏螘r(shí)間關(guān)注,本文就是開始。? ??????????????????????????????????????????????????????????????????????????????????????www.anytao.com |
?
3 元數(shù)據(jù)是什么?
元數(shù)據(jù),就是描述數(shù)據(jù)的數(shù)據(jù)。這一概念并非CLR之獨(dú)創(chuàng),Metadata存在于任何對(duì)數(shù)據(jù)和數(shù)據(jù)關(guān)系中,例如程序集清單信息也被稱為程序集元數(shù)據(jù)。而不同系統(tǒng)的元數(shù)據(jù)也相應(yīng)具有本身的特點(diǎn),.NET元數(shù)據(jù)也是如此。那么,CLR元數(shù)據(jù)描述的是哪些內(nèi)容呢?正如前文的描述一樣,編譯之后,類型信息將以元數(shù)據(jù)的形式保存在PE格式文件中。.NET是基于面向?qū)ο蟮?#xff0c;所以元數(shù)據(jù)描述的主要目標(biāo)就是面向?qū)ο蟮幕驹?#xff1a;類、類型、屬性、方法、字段、參數(shù)、特性等,主要包括:
- 定義表,描述了源代碼中定義的類型和成員信息,主要包括:TypeDef、MehodDef、FieldDef、ModuleDef、PropertyDef等。
- 引用表,描述了源代碼中引用的類型和成員信息,引用元素可以是同一程序集的其他模塊,也可以是不同程序集的模塊,主要包括:AssemblyRef、TypeRef、ModuleRef、MethodsRef等。
- 指針表,使用指針表引用未知代碼,主要包括:MethodPtr、FieldPtr、ParamPtr等。
- 堆,以stream的形式保存的信息堆,主要包括:#String、#Blob、#US、#GUIDe等。
如前文所述,我們以ILDasm.exe可以通過反編譯的方式,通過執(zhí)行Ctrl+M快捷鍵來獲取該程序集所使用的MetaData信息列表,在.NET中每個(gè)模塊包含了44個(gè)CLR元數(shù)據(jù)表,如下:
| 表記錄 | 元數(shù)據(jù)表 | 說明 |
| 0(0) | ModuleDef | 描述當(dāng)前模塊 |
| 1(0x1) | TypeRef | 描述引用Type,為每個(gè)引用到類型保存一條記錄 |
| 2(0x2) | TypeDef | 描述Type定義,每個(gè)Type將在TypeDef表中保存一條記錄 |
| 3(0x3) | FieldPtr | 描述字段指針,定義類的字段時(shí)的中間查找表 |
| 4(0x4) | FieldDef | 描述字段定義 |
| 5(0x5) | MethodPtr | 描述方法指針,定義類的方法時(shí)的中間查找表 |
| 6(0x6) | MethodDef | 描述方法定義 |
| 7(0x7) | ParamPtr | 描述參數(shù)指針,定義類的參數(shù)時(shí)的中間查找表 |
| 8(0x8) | ParamDef | 描述方法的參數(shù)定義 |
| 9(0x9) | InterfaceImpl | 描述有哪些類型實(shí)現(xiàn)了哪些接口 |
| 10(0xa) | MemberRef | 描述引用成員的情況,引用成員可以是方法、字段還有屬性。 |
| 11(0xb) | Constant | 描述了參數(shù)、字段和屬性的常數(shù)值 |
| 12(0xc) | CustomAttribute | 描述了特性的定義 |
| 13(0xd) | FieldMarshal | 描述了與非托管代碼交互時(shí),參數(shù)和字段的傳遞方式。 |
| 14(0xe) | DeclSecurity | 描述了對(duì)于類、方法和程序集的安全性 |
| 15(0xf) | ClassLayout | 描述類加載時(shí)的布局信息 |
| 16(0x10) | FieldLayout | 描述單個(gè)字段的偏移或序號(hào) |
| 17(0x11) | StandAloneSig | 描述未被任何其他表引用的簽名 |
| 18(0x12) | EventMap | 描述類的事件列表 |
| 19(0x13) | EventPtr | 描述了事件指針,定義事件時(shí)的中間查找表 |
| 20(0x14) | Event??????????????? | 描述事件 |
| 21(0x15) | PropertyMap????????? | 描述類的屬性列表 |
| 22(0x16) | PropertyPtr????????? | 描述了屬性指針,定義類的屬性時(shí)的中間查找表 |
| 23(0x17) | Property???????????? | 描述屬性 |
| 24(0x18) | MethodSemantics????? | 描述事件、屬性與方法的關(guān)聯(lián) |
| 25(0x19) | MethodImpl?????????? | 描述方法的實(shí)現(xiàn) |
| 26(0x1a) | ModuleRef??????????? | 描述外部模塊的引用 |
| 27(0x1b) | TypeSpec???????????? | 描述了對(duì)TypeDef或者TypeRef的說明 |
| 28(0x1c) | ImplMap????????????? | 描述了程序集使用的所有非托管代碼的方法 |
| 29(0x1d) | FieldRVA???????????? | 字段表的擴(kuò)展,RVA給出了一個(gè)字段的原始值位置 |
| 30(0x1e) | ENCLog?????????????? | 描述在Edit-And-Continue模式中哪些元數(shù)據(jù)被修改過 |
| 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???????? | 描述了泛型類型定義或者泛型方法定義所使用的泛型參數(shù) |
| 43(0x2b) | MethodSpec?????????? | 描述泛型方法的實(shí)例化 |
| 44(0x2c) | GenericParamConstraint | 描述了每個(gè)泛型參數(shù)的約束 |
然后是6個(gè)命名堆:
| 堆 | 說明 |
| #String | 一個(gè)AscII string數(shù)組,被元數(shù)據(jù)表所引用,來表示方法名、字段名、類名、變量名以及資源相關(guān)字符串,但不包含string literals。 |
| #Blob | 包含元數(shù)據(jù)引用的二進(jìn)制對(duì)象,但不包含用戶定義對(duì)象 |
| #US | 一個(gè)unicode string數(shù)組,包含了定義在代碼中的字符串(string literals),這些字符串可以直接由ldstr指令加載獲取,還記得嗎?我們?cè)凇兜诙?#xff1a;字符串駐留(上)---帶著問題思考》中對(duì)字符串創(chuàng)建過程的論述嗎? |
| #GUID | 保存了128byte的GUID值,由元數(shù)據(jù)表引用 |
| #~ | 一個(gè)特殊堆,包含了所有的元數(shù)據(jù)表,會(huì)引用其他的堆。 |
| #- | 一個(gè)未壓縮的#~堆。除了#-堆,其他堆都是壓縮的。 |
Note:對(duì)于#String和#US,一個(gè)簡(jiǎn)單的區(qū)別就是:
string hello = "Hello, World";變量hello名,將保存在#String,而代碼中字符串信息“Hello, World”則被保存在#US中。
關(guān)于元數(shù)據(jù)信息的詳細(xì)描述,例如每個(gè)表包含哪些列,不同表間的關(guān)系,請(qǐng)參考[Standard ECMA-335]和[The .NET File Format]。
在PE文件格式中,Metadata有著復(fù)雜的結(jié)構(gòu),我試圖以數(shù)據(jù)庫管理數(shù)據(jù)的角度出發(fā)來理解元數(shù)據(jù)的結(jié)構(gòu)和關(guān)系,所以表示元數(shù)據(jù)的邏輯結(jié)構(gòu)被成為元數(shù)據(jù)表,類似于數(shù)據(jù)庫表有主鍵和Sechema,元數(shù)據(jù)表以RID(表索引)和元-元數(shù)據(jù)表示類同的概念,以TypeDef表為例,通過數(shù)據(jù)引用關(guān)系同時(shí)與Field、Method、TypeRef等表發(fā)生關(guān)聯(lián),其他表間又有類似的關(guān)系,從而形成一個(gè)復(fù)雜的類數(shù)據(jù)庫結(jié)構(gòu):
因此,元數(shù)據(jù)是保存了類型的編譯后數(shù)據(jù),是.NET程序運(yùn)行的基礎(chǔ),我們可以在運(yùn)行時(shí)動(dòng)態(tài)的以反射的方式獲取元數(shù)據(jù)信息,而這些信息在.NET Framework中以System.Type、MethodInfo等封裝,例如截取MSDN中一個(gè)類間關(guān)系的簡(jiǎn)單示例:
對(duì)于每個(gè)CLR類型而言都可以通過Object.GetType方法返回其Type,從而任意的取到所有的運(yùn)行時(shí)元數(shù)據(jù)信息:
// 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());}}} }執(zhí)行上述方法,將獲取一個(gè)長(zhǎng)長(zhǎng)的列表,看到很多熟悉的符號(hào):-)
4 IL是什么?
IL,又稱為CIL或者M(jìn)SIL,翻譯為中文就是中間語言,由ECMA組織(Standard ECMA-335)提供完整的定義和規(guī)范。顧名思義,中間語言正如它的名稱所言,任何與CLR兼容的編譯器所生成的都是中間語言代碼,這是實(shí)現(xiàn)CLR跨語言的基礎(chǔ)結(jié)構(gòu)之一。IL就像一座橋梁,其指令集獨(dú)立于CPU指令而存在,可以由JIT編譯器在運(yùn)行時(shí)翻譯為本地代碼執(zhí)行,連接了任何遵守CLS規(guī)范的高級(jí)語言,為.NET平臺(tái)提供了最基本的支持。在[你必須知道的.NET]一書中,我用一整章(第3章 “一切從IL開始”)的篇幅對(duì)IL的基本內(nèi)容進(jìn)行了相應(yīng)的介紹,所以關(guān)于IL的基礎(chǔ)內(nèi)容例如基本類型、IL分析方法、常見指令、基本運(yùn)算等,就不在本文有所贅述,只對(duì)IL基本內(nèi)容進(jìn)行一點(diǎn)小結(jié):
- IL是一種面向?qū)ο蟮臋C(jī)器語言,因此具有面向?qū)ο笳Z言的所有特性,類、對(duì)象、繼承、多態(tài)等仍然是IL語言的基本概念。
- IL指令獨(dú)立于CPU指令,CLR通過JIT編譯機(jī)制將其轉(zhuǎn)換為本地代碼。
- IL和元數(shù)據(jù)是了解CLR運(yùn)行機(jī)制的重要內(nèi)容,對(duì)于我們打開CLR神秘面紗有著重要的意義。
如前文[初次接觸]部分論述的一樣,可以通過ILDasm.exe或者Reflector工具對(duì)托管代碼執(zhí)行反編譯來查看其IL代碼,對(duì)于很多情況下IL代碼分析可以解決很多高級(jí)語言隱藏的語法糖游戲,例如C#3.0提出的自動(dòng)屬性、隱式類型、匿名類型、擴(kuò)展方法等都可以很快從IL分析中找到答案,所以適當(dāng)?shù)牧私釯L是必要的。那么我們?cè)谙旅鍶IT編譯時(shí)的一個(gè)片段來了解IL代碼對(duì)于托管程序執(zhí)行的作用。
另外,Metadata描述了靜態(tài)的結(jié)構(gòu),而IL闡釋了動(dòng)態(tài)的執(zhí)行,而IL代碼是通過一個(gè)4字節(jié)大小的地址引用元數(shù)據(jù)表的。該引用被稱為元數(shù)據(jù)符號(hào)(Metadata Token,也就是記錄元數(shù)據(jù)表的位置信息),在ILdasm.exe工具中選中“Show token values”,就可以在IL代碼中看到IL代碼通過Metadata Token引用元數(shù)據(jù)表的情況:
.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定義的規(guī)范,元數(shù)據(jù)第一個(gè)字節(jié)表示引用的元數(shù)據(jù)表,而其余三個(gè)字節(jié)則表示在相應(yīng)元數(shù)據(jù)表中的記錄,例如06000003表示了引用了MethodDef(06)表的000003項(xiàng)Main方法。
我們可以通過Type的MetadataToken屬性在運(yùn)行時(shí)反射獲取類型的元數(shù)據(jù)符號(hào),例如:
static void Main(string[] args) {Console.WriteLine(typeof(One).MetadataToken); }?
?
有了上述所有的準(zhǔn)備,我們就可以著手分析元數(shù)據(jù)和IL在程序執(zhí)行時(shí)的角色和關(guān)聯(lián)。
| 欲知后事如何,且聽下文繼續(xù):-) |
??????????????????????????????????????????????????????????????????????????????????????www.anytao.com |
?
支持anytao的創(chuàng)業(yè)產(chǎn)品Worktile
Worktile,新一代簡(jiǎn)單好用、體驗(yàn)極致的團(tuán)隊(duì)協(xié)同、項(xiàng)目管理工具,讓你和你的團(tuán)隊(duì)隨時(shí)隨地一起工作。完全免費(fèi),現(xiàn)在就去了解一下吧。
https://worktile.com
參考文獻(xiàn)
- 《你必須知道的.NET》第3章 “一切從IL開始”
- DonBox,《.NET本質(zhì)論》
- http://www.sloppycode.net/articles/inside-net-assemblies-and-metadata.aspx
- http://www.codeproject.com/KB/dotnet/dotnetformat.aspx
?
支持(0)?反對(duì)(0)
??
#10樓?2009-02-25 09:57?yy125
關(guān)注中,元數(shù)據(jù)和IL是不是和語言無關(guān)呢,我用vb寫,是不是也生成一樣的元數(shù)據(jù)和IL呢
支持(0)?反對(duì)(0)
??
#11樓?[樓主]?2009-02-25 10:06?Anytao
@yy125?
對(duì),這是.NET跨語言的基礎(chǔ),不同的高級(jí)語言(基于.NET平臺(tái),這是前提)由各自編譯器編譯之后生成的元數(shù)據(jù)和IL是統(tǒng)一的。
支持(0)?反對(duì)(0)
??
#12樓?2009-02-25 23:45?未登錄的包建強(qiáng)
anytao,你這篇寫得確實(shí)不錯(cuò),干凈利落,一針見血。?
下面說但是,?
你用反射來說明元數(shù)據(jù),有點(diǎn)自圓其說。我建議用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();?
哥哥我就是要一個(gè)字節(jié)一個(gè)字節(jié)地去讀exe文件,當(dāng)然,這招兒也是從老外那里學(xué)來的。?
參考文獻(xiàn):http://www.cnblogs.com/Jax/archive/2009/01/02/1366888.html
支持(0)?反對(duì)(0)
??
#13樓?2009-02-25 23:54?未登錄的包建強(qiáng)
@Anders?
《.NET 設(shè)計(jì)規(guī)范--.NET約定、慣用法與模式》,這本書我有,回頭我送你好了,算是給我干女兒的見面禮。?
@胖趙?
就是來攪黃你生意的。娃哈哈。。。
支持(0)?反對(duì)(0)
??
#14樓?[樓主]?2009-02-26 10:26?Anytao
@未登錄的包建強(qiáng)?
不知道說啥了:-)
支持(0)?反對(duì)(0)
??
#15樓?2009-02-26 22:54?波波塔
#String 一個(gè)AscII string數(shù)組 ...?
如果 string 中文test="中文等也是用AscII 存儲(chǔ)在 #string里面的嗎?
支持(0)?反對(duì)(0)
??
#16樓?2009-03-04 21:15?JacksonLin
嚴(yán)重支持
支持(0)?反對(duì)(0)
??
#17樓?2009-12-22 14:49?飛翔的小豬
=============================================
?引用表,描述了源代碼中引用的類型和成員信息,引用元素可以是同一程序集的其他模塊,也可以是不同程序集的模塊,主要包括:AssemblyRef、TypeRef、ModuleRef、MethodsRef等。
=============================================
引用表中應(yīng)該不包括MethodsRef,不存在單獨(dú)的FieldRef和MethodRef,因?yàn)镸emberRef包括了指向字段和方法的引用,你的44個(gè)CLR元數(shù)據(jù)表中也沒有包含,不知道是不是筆誤。
看你的文章要比看專著來的輕松得多。
小弟班門弄斧了。
總結(jié)
以上是生活随笔為你收集整理的[你必须知道的.NET]第二十五回:认识元数据和IL(中)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 13499元!大疆图传神器DJI Tra
- 下一篇: 国内价格太良心了 vivo X80 Pr