元数据与IL简介
?
文/玄魂
1.3.2 ????? 元數(shù)據(jù)
元數(shù)據(jù)是描述數(shù)據(jù)的數(shù)據(jù)。在CLR的上下文中,元數(shù)據(jù)表示由描述符組成的一套體系,這些操作符包括了在一個(gè)模塊中被聲明或引用的所有項(xiàng)。由于CLR模型是面向?qū)ο蟮?#xff0c;因此在元數(shù)據(jù)中描述的項(xiàng)是類和它們的成員,以及它們伴隨著的特性、屬性和關(guān)聯(lián)。本節(jié)簡單地介紹元數(shù)據(jù),與原數(shù)據(jù)安全相關(guān)的內(nèi)容會在后續(xù)章節(jié)中繼續(xù)講解,元數(shù)據(jù)的詳細(xì)內(nèi)容不在本書的論述范圍之內(nèi)。
元數(shù)據(jù)實(shí)際上是一塊二進(jìn)制數(shù)據(jù),包含了三種表:定義表、引用表和清單表。
元數(shù)據(jù)定義表主要是模塊定義、類型定義、方法定義、字段定義、事件定義、參數(shù)定義、屬性定義等一系列定義表的集合。當(dāng)編譯器編譯代碼時(shí),所有定義的內(nèi)容都會生成對應(yīng)的定義表。
元數(shù)據(jù)引用表用于記錄編譯器中源代碼引用的類型、方法、字段、事件。常用的引用表如:AssemblyRef(程序集引用表)、ModuleRef(模塊引用表)、TypeRef(類型引用表)等。
元數(shù)據(jù)清單表包含了組成程序集所需要的所有信息,同時(shí)包含了對其他程序集的引用信息。它明確地指出了哪些條目可以對外開放,哪些條目只可以在程序集內(nèi)部進(jìn)行訪問。
下面通過經(jīng)典的HelloWorld程序簡要分析其中的元數(shù)據(jù)信息,如代碼清單1-7所示。
代碼清單1-7 HelloWorld程序代碼
using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace HelloWorld{class Program{static void Main(string[] args){Console.WriteLine("Hello World!");Console.Read();}}}?
下面使用反匯編工具ILDasm打開HelloWorld.exe,雙擊MANIFEST,圖1-7為查看清單信息的截圖。ILDasm的使用方法和參數(shù)說明請讀者參考MSDN文檔。
?
圖1-7? 查看程序清單信息
詳細(xì)的清單信息如代碼清單1-8所示。
代碼清單1-8 HelloWorld.EXE的清單信息
// Metadata version: v4.0.21006.assembly extern mscorlib{.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4...ver 4:0:0:0}.assembly HelloWorld{......}.hash algorithm 0x00008004.ver 1:0:0:0.module HelloWorld.exe// MVID: {B8EB35DD-5AD2-402C-B422-AA63B0AACCFA}.imagebase 0x00400000.file alignment 0x00000200.stackreserve 0x00100000.subsystem 0x0003 // WINDOWS_CUI.corflags 0x00000003 // ILONLY 32BITREQUIRED// Image base: 0x05C40000?
程序比較簡單,代碼中包含了版本、外部引用等簡單的信息。表1-2描述了示例中所使用的 HelloWord.exe 程序集的程序集清單中的各項(xiàng)指令。
表1-2 HelloWorld.exe指令說明
| 指令 | 說明 |
| .assembly extern < assembly name > | 指定包含當(dāng)前模塊所引用項(xiàng)目的另一程序集(在此示例中為 mscorlib) |
| .publickeytoken < token > | 指定所引用程序集的實(shí)際密鑰的標(biāo)記 |
| .ver < version number > | 指定引用程序集的版本號 |
| .assembly < assembly name > | 指定程序集名稱 |
| .hash algorithm < int32 value > | 指定使用的哈希算法 |
| .module < file name > | 指定組成程序集的模塊名稱,在此示例中,程序集只包含一個(gè)文件 |
| .subsystem < value > | 指定程序要求的應(yīng)用程序環(huán)境。在此示例中,值 3 表示該可執(zhí)行文件從控制臺運(yùn)行 |
| .corflags | 當(dāng)前是元數(shù)據(jù)中的一個(gè)保留字段 |
根據(jù)程序集的內(nèi)容,程序集清單可包含許多不同的指令。有關(guān)程序集清單中指令的完整列表請讀者參考相關(guān)文檔,本例旨在拋磚引玉。若要查看完整的元數(shù)據(jù)信息,可以使用快捷鍵“Ctlr+M”,如圖1-8所示。
?
圖1-8 查看元數(shù)據(jù)信息
1.3.3 ????? IL常用指令
為方便起見,還是以HelloWorld.exe為例講解IL的相關(guān)內(nèi)容。由于篇幅所限,關(guān)于IL的詳細(xì)內(nèi)容還請各位讀者參考相關(guān)資料。圖1-9為Main方法的IL代碼。
?
圖1-9 HelloWorld.exe Main方法的IL代碼
在一個(gè)中間語言程序中,如果某一行以“.”開始,代表這是一個(gè)傳輸給匯編工具的指令;而沒有以“.”開始的行是中間語言的代碼。上圖中.method是方法定義指令,定義了Main方法,參數(shù)在“()”中,IL代碼在“{}”中。.entrypoint是入口指令,表明該方法是入口方法。.maxstack指定了最大棧的深度為8。下面的IL_n是代碼標(biāo)簽,后面是IL代碼。nop是空指令;ldstr指令向棧中壓入字符串“Hello World!”;call指令調(diào)用靜態(tài)方法Console.WriteLine(string)和Console.read();pop彈出棧頂?shù)闹?#xff1b;ret指令表示方法體的結(jié)束。IL支持“//”和“/* */”的注釋方法。
提示? 在中間語言中,如果需要調(diào)用一個(gè)方法,需要指定方法的全名,包括它的名稱域(namespace)、類名、返回值類型和參數(shù)的數(shù)據(jù)類型。
表1-3列舉了IL的其他一些常用指令,更多的指令可以查看IL指令表。
表1-3 IL常用指令
| 指令 | 描述 |
| .assembly <程序集名稱> {} | 設(shè)置程序集 |
| ldc.i4.n | 把一個(gè) 32位的常量(n從0到8)裝入堆棧 |
| stloc.n | 把一個(gè)從堆棧中返回的值存入第n(n取0~8)個(gè)局部變量 |
| add | 2個(gè)值相加。命令的參數(shù)必須在調(diào)用前裝入堆棧,該函數(shù)從堆棧中移除參數(shù)并把運(yùn)算后的結(jié)果壓入堆棧 |
| sub | 2個(gè)值相減 |
| mul | 2個(gè)值相乘 |
| newarr type | 生成一個(gè)元素類型為type 的數(shù)組。數(shù)組的大小必須在調(diào)用該命令前裝入堆棧。該命令會把一個(gè)數(shù)組的引用裝入堆棧 |
| stelem.i4 | 給一個(gè)數(shù)組成員賦值。數(shù)組的引用、下標(biāo)和值必須在調(diào)用該命令前裝入堆棧 |
| ldelema type | 把數(shù)組元素的地址裝入堆棧。數(shù)組的引用和下標(biāo)必須在調(diào)用該命令前裝入堆棧。地址用來調(diào)用非靜態(tài)函數(shù) |
| ldlen | 把數(shù)組的長度裝入堆棧。數(shù)組的引用必須在調(diào)用該命令前裝入堆棧 |
| ldloca.s variable | 把變量的地址裝入堆棧 |
| ldc.i4.s value | 把一個(gè)Int32的常量裝入堆棧(用于大于8位的數(shù)) |
| conv.i4 | 把堆棧中值轉(zhuǎn)換成Int32類型 |
| call instance function(arguments) | 調(diào)用類的非靜態(tài)函數(shù) |
| bge.s label | 跳轉(zhuǎn)至label 如果value1≥value 2. Values 1和 2 必須在調(diào)用本命令前裝入堆棧 |
| br.s label | 跳轉(zhuǎn)至label |
| box value type | 把一個(gè)值類型轉(zhuǎn)成一個(gè)Object,并把該Object的引用裝入堆棧 |
| blt.s label | 跳轉(zhuǎn)至label 。如果value 1小于 value 2. Values 1 和 2 必須在調(diào)用本命令之前裝入堆棧 |
| ldelem.i4 | 把一個(gè)數(shù)組元素裝入堆棧。數(shù)組引用和下標(biāo)必須在調(diào)用本命令之前裝入堆棧 |
| ldarga.s argument | 把函數(shù)參數(shù)的地址裝入堆棧 |
| dup | 在堆棧上復(fù)制一個(gè)值 |
| stind.i4 | 存儲值的地址。地址和值必須在調(diào)用本命令之前裝入堆棧 |
| .field | 定義類成員。和關(guān)鍵字public、private、static等一起使用 |
| stsfld static field | 用堆棧中的值替換靜態(tài)字段的值 |
| ldfld field | 把一個(gè)非靜態(tài)字段裝入堆棧。類實(shí)例的地址必須在調(diào)用本命令之前裝入堆棧 |
| ldarg.n | 把第n個(gè)參數(shù)裝入堆棧。在非靜態(tài)函數(shù)中,第0個(gè)參數(shù)是一個(gè)隱含的參數(shù),代表this |
| newobj constructor | 用構(gòu)造函數(shù)constructor生成一個(gè)類的實(shí)例。構(gòu)造函數(shù)的參數(shù)必須在調(diào)用本函數(shù)之前先裝入堆棧。一個(gè)類的實(shí)例會被生成并裝入堆棧 |
| callvirt instance function | 調(diào)用一個(gè)對象的后期綁定方法 |
?
1.3.4 ????? IL與代碼驗(yàn)證
在將MSIL編譯為本機(jī)代碼的過程中,MSIL代碼必須通過驗(yàn)證過程,除非管理員已經(jīng)建立了允許代碼跳過驗(yàn)證的安全策略。驗(yàn)證過程檢查MSIL和元數(shù)據(jù)以確定代碼是否是類型安全的,這意味著它僅訪問已被授權(quán)訪問的內(nèi)存位置。類型安全幫助將對象彼此隔離,因而可以保護(hù)它們免遭無意或惡意的破壞。它還提供了對代碼可以可靠地強(qiáng)制安全限制的保證。
運(yùn)行庫使用下列條件來驗(yàn)證代碼是否為類型安全:
q? 對類型的引用與被引用的類型嚴(yán)格兼容。
q? 在對象上只調(diào)用正確定義的操作。
q? 標(biāo)識與聲稱的要求一致。
驗(yàn)證過程中檢查 MSIL 代碼,嘗試確認(rèn)該代碼只能通過正確定義的類型訪問內(nèi)存位置和調(diào)用方法。例如,代碼不允許以超出內(nèi)存范圍的方式來訪問對象。另外,驗(yàn)證過程檢查代碼以確定 MSIL 是否已正確生成,這是因?yàn)椴徽_的 MSIL 會導(dǎo)致違反類型安全規(guī)則。驗(yàn)證過程通過正確定義的類型安全代碼集,并且它只通過類型安全的代碼。然而,由于驗(yàn)證過程存在一些限制,某些類型安全代碼可能無法通過驗(yàn)證,而某些語言在設(shè)計(jì)上并不產(chǎn)生可驗(yàn)證的類型安全代碼。如果安全策略要求提供類型安全代碼,而該代碼不能通過驗(yàn)證,則在運(yùn)行該代碼時(shí)將引發(fā)異常。
-------------------------注:本文摘抄自《.NET 安全揭秘》1.3節(jié)
總結(jié)
- 上一篇: 评估创业项目的十大标准
- 下一篇: securecrt连接GNS3步骤