中間語言
在.NET框架中,公共語言基礎結構使用CLS來綁定不同的語言。通過要求不同的語言至少要實現CTS包含在CLS中的部分,公共語言基礎結構允許不同的語言使用.NET框架。因此,在.NET框架中,所有的語言(C#、VB.NET、Effil.NET等)最後都被轉換為了一種通用語言:微軟中間語言(Microsoft Intermediate Language,MSIL,以下簡稱IL)。
IL是一種介於高級語言和基於Intel的匯編語言的中間語言,是.NET平台的匯編語言。當用戶編譯一個.NET程序時,編譯器將源代碼翻譯成一組可以有效地轉換為本機代碼且獨立於CPU的指令。當執行這些指令時,實時編譯器將它們轉化為CPU特定的代碼。由於CLR支持多種實時編譯器,因此同一段IL代碼可以被不同的編譯器實時編譯並運行在不同的結構上。
IL包括用於加載、存儲和初始化對象以及對對象調用方法的指令,還包括用於算術和邏輯運算、控制流、直接內存訪問、異常處理和其他操作的指令。要使代碼可運行,必須先將 IL 轉換為特定於 CPU 的代碼,這通常是通過實時(JIT) 編譯器來完成的。由於CLR為它支持的每種計算機結構都提供了一種或多種JIT編譯器,因此同一組IL可以在所支持的任何結構上JIT編譯和運行。
當編譯器產生IL時,它也產生元數據。元數據描述代碼中的類型,包括每種類型的定義、每種類型的成員的簽名、代碼引用的成員和運行庫在執行時使用的其他數據。IL和元數據包含在一個可移植可執行(PE)文件中,下面重點介紹托管PE文件,以及元數據的相關知識。
1.3.1 托管PE文件
PE(Portable Execute,可移植執行體)是微軟Windows操作系統上的程序文件,常見的如EXE、DLL、OCX、SYS、COM。圖1-3展示了標准的PE/COFF文件頭部格式。
圖1-3 標准的PE/COFF文件頭部格式
MS DOS頭是DOS系統的遺傳內容,表示一個應用程序可以在DOS環境下運行。MS DOS根(stub)是一段代碼,如果Windows程序在DOS環境下運行,會給出“該程序不能在DOS環境下運行”(This program cannot be run in DOS mode)的提示。在偏移量0x3c處,MS DOS頭指向了PE標識(PE Signature)的地址。
PE標識表示該文件是一個PE文件。其值始終為00004550h,45h代表字符E,50h代表字符P。
COFF頭(COFF Header)提供了COFF或者可執行文件的最一般的信息。
PE頭(PE Header)提供了操作系統加載文件所需的信息。這對於PE文件是最重要的地方,其間包含了數據索引表和節。
關於標准PE文件的詳細內容請讀者閱讀相關資料,本節內容只關注托管PE文件的特殊信息。CLR對傳統的PE文件進行了擴展,如圖1-4所示是托管PE文件的格式。
圖1-4 托管PE文件的格式
標准的Windows PE文件頭和COFF(通用對象文件格式)頭類似,分為PE32和PE32+兩種。如果文件頭采用PE32格式,則該文件可運行在32位或64位操作系統上。如果文件頭采用PE32+格式,則該文件只能在64位的操作系統上運行。PE32 或者 PE32+ 頭也包含文件類型信息:GUI、CUI或者DLL。如果包含本地CPU代碼的模塊,則PE32或者PE32+ 頭將包含有關本地CPU代碼的相關信息。
CLR頭包含使這個模塊被托管的相關信息。這些信息包括CLR需要的版本信息、一些標識、入口方法的元數據信息、模塊的元數據位置和大小信息、資源信息、強名稱和一些其他信息。
每一個托管模塊都包含元數據表。元數據表有兩種,一種是描述源代碼中的類型描述和成員描述的元數據表,另一種是包含源代碼引用的類型描述和成員描述的元數據表。
IL代碼是編譯器編譯產生的中間代碼,程序運行時CLR負責將中間代碼編譯成本地代碼執行。
CLR頭定義在.NET Framework的CorHdr.h中,代碼如代碼清單1-4所示。
代碼清單1-4 CLR 頭定義
typedef struct IMAGE_COR20_HEADER { ULONG cb; USHORT MajorRuntimeVersion; USHORT MinorRuntimeVersion; // Symbol table and startup information IMAGE_DATA_DIRECTORY MetaData; ULONG Flags; union { DWORD EntryPointToken; DWORD EntryPointRVA; }; // Binding information IMAGE_DATA_DIRECTORY Resources; IMAGE_DATA_DIRECTORY StrongNameSignature; // Regular fixup and binding information IMAGE_DATA_DIRECTORY CodeManagerTable; IMAGE_DATA_DIRECTORY VTableFixups; IMAGE_DATA_DIRECTORY ExportAddressTableJumps; IMAGE_DATA_DIRECTORY ManagedNativeHeader; } IMAGE_COR20_HEADER;
關於CLR頭中的各個字段的解釋見表1-1,後文會對PE文件中的節信息做簡要介紹,關於PE文件的詳細信息參看書後附錄中的參考書籍。
表1-1 CLR頭字段說明
查看本欄目
頭信息的主要代碼如代碼清單1-5所示。
代碼清單1-5 HelloWorld.exe 頭信息
----- DOS Header: Magic: 0x5a4d Bytes on last page: 0x0090 ......(省略) File addr. of COFF header: 0x0080 ----- COFF/PE Headers: Signature: 0x00004550 ----- COFF Header: Machine: 0x014c Number of sections: 0x0003 Time-date stamp: 0x4b1b1d3a Ptr to symbol table: 0x00000000 Number of symbols: 0x00000000 Size of optional header: 0x00e0 Characteristics: 0x0102 ----- PE Optional Header (32 bit): Magic: 0x010b ......(省略) Directory: ......(省略) Table: 0x00000000 [0x00000000] address [size] of Delay Load IAT: 0x00002008 [0x00000048] address [size] of CLR Header: ......(節信息,略) Base Relocation Table 0x00002000 Page RVA 0x0000000c Block Size 0x00000002 Number of Entries Entry 1: Type 0x3 Offset 0x000007a0 Entry 2: Type 0x0 Offset 0x00000000 Import Address Table DLL : mscoree.dll ......(省略) Delay Load Import Address Table // No data. Entry point code: FF 25 00 20 40 00 ----- CLR Header: Header size: 0x00000048 Major runtime version: 0x0002 Minor runtime version: 0x0005 ......(省略) Metadata Header Storage Signature: ......(省略) Storage Header: 0x00 Flags 0x0005 Number of Streams Stream 1: 0x0000006c Offset 0x000001e8 Size '#~' Name ......(省略) Stream 5: 0x00000510 Offset 0x00000130 Size '#Blob' Name Metadata Stream Header: 0x00000000 Reserved 0x02 Major 0x00 Minor 0x00 Heaps 0x01 Rid 0x0000000900001547 MaskValid 0x000016003325fa00 Sorted Code Manager Table: default Export Address Table Jumps: // No data.
上面代碼中涉及很多節信息,下面做簡要論述。
1. Relocation(重定位)
映像文件的.reloc節包括了Fixup表,它包含了映像文件中的所有定位項。.reloc節的RVA和大小都由PE頭的基地址重定位(Base Relocation)表目錄定義。Fixup表由定位塊組成,每個塊都包括了一個4KB頁的定位。這些塊都是4字節對齊的。
每個定位都描述了映像文件中特定地址的位置,以及操作系統加載程序在將映像文件載入內存的時候,應該如何修改這個位置上的地址。
每個定位塊都開始於兩個4字節無符號整數:頁面的RVA,這個頁面包含了需要定位的地址、塊的大小。緊隨其後的是頁面的定位項每個項都是16位寬的,其中的4個最高權重位包括了所需要的重定位類型,剩下的12位包括了頁面中重定位地址的偏移量。
為了對地址進行重定位,操作系統加載程序會計算出首選的基地址(PE頭的ImageBase字段)和實際加載映像文件的基地址之間的差異(delta)。接著根據重定位的類型,將這個delta應用到地址上。如果在首選位置加載映像文件,就不需要進行定位。
說明 Windows XP或者更新的版本都是支持CLR的操作系統,既不需要CLR的啟動Stub,也不需要IAT來調用CLR。因此,如果CLR頭標志指出映像文件是純IL(COMIMAGE_FLAGS_ ILONLY),那麼操作系統就會完全地忽略.reloc節。
2. Text(文本)
PE文件的.text節是只讀節。在托管PE文件中,它包括了元數據表、IL代碼、導入表、CLR頭、CLR非托管啟動Stub。在由IL匯編器生成的映像文件中,這個節還包括了托管資源、強簽名的散列值、調試數據以及非托管導出Stub。所以.text節是托管PE文件對傳統PE文件改變最多的地方。
圖1-6總結了由IL匯編器生成的映像文件的.text節的通用結構。
圖1-6 .text節的通用結構
3. Data(數據)
由IL匯編器生成的映像文件的數據節(.sdata)是可讀寫的節,它包括了數據常量、V表、非托管導出表以及TLS的目錄結構。聲明為特定於線程的數據位於一個不同的節,也就是.tls節。
4. Data Constants(數據常量)
數據常量代表了靜態字段的映射,通常包括映射字段的初始化數據。
字段映射是一種使用ANSI字符串、Blob或結構來初始化靜態字段的方法。另一種初始化靜態字段的方法(對於CLR來說更正式的方法)是通過在類的構造函數中顯式地進行初始化。
一方面,映射到數據節的字段就像類型控制和垃圾收集那樣,是CLR控制機制所觸及不到的;另一方面,它是完全開放的,可以不受限制地訪問和修改。這將導致加載程序阻止特定的字段類型被映射。映射字段的類型不能包括對象引用、向量、數組或任何非公共的子結構。如果為靜態字段初始化使用類的構造函數,就不會出現這樣的問題。
5. V-Table(V表)
在純粹的托管代碼模塊中,V表用於將托管方法公開給非托管代碼來調用。V表由一些項組成,每個項又由一個或多個槽組成。V表的這些項和槽都定義在V表定位中。每個定位指定了每個項中槽的數量和寬度(4字節或8字節)。V表的每個槽都包含各個方法的元數據標記,這些元數據標記在運行期間將會替換成方法本身的地址或者封送thunk,用於提供方法的非托管入口。因為這些定位是在運行期間執行的,所以托管PE文件的V表必須駐留於可讀寫的節中。IL匯編器將這個V表放在了.sdata節中,不像VTFixup表是駐留於.text節中的。
非托管映像文件的V表完全在鏈接期間定義,並只需操作系統加載程序執行的基地址重定位。因為在執行期間不需要改變V表(例如把方法標記替換成托管映像中的地址),所以非托管映像文件可以把它們的V表放在只讀節中。
6. Unmanaged Export Table(非托管導出表)
在非托管映像文件中的非托管導出表占據一個單獨的節——.edata。在IL匯編器生成的映像文件中,非托管導出表和它引用的V表都駐留於.sdata節中。
7. Thread Local Storage(線程局部存儲)
ILAsm和VC++允許用戶定義屬於TLS的數據常量,並將靜態字段映射到這些數據常量上。TLS是一種特殊的存儲類,類中的數據對象不是棧變量而是各個獨立線程的局部變量。因此,每個線程都可以為這樣的變量維護不同的值。
TLS數據在TLS目錄中描述,IL匯編器將其放置於.sdata節中。32位映像文件的TLS目錄結構定義在Winnt.h中,如代碼清單1-6所示。
代碼清單1-6 32位映像文件的TLS目錄結構
typedef struct _IMAGE_TLS_DIRECTORY32 { ULONG StartAddressOfRawData; ULONG EndAddressOfRawData; ULONG AddressOfIndex; ULONG AddressOfCallBacks; ULONG SizeOfZeroFill; ULONG Characteristics; } IMAGE_TLS_DIRECTORY32;
64位映像(IMAGE_TLS_DIRECTORY64)的TLS目錄結構是類似的,除了開頭的4個字段是8字節無符號整數(ULONGLONG),而不是4字節無符號整數(ULONG)。
TLS目錄結構的RVA和大小存儲在PE頭的第10個數據目錄(TLS)中。構成了TLS模板的TLS數據常量,駐留於映像文件的.tls節中。
8. Resources(資源)
在托管PE文件中可以嵌入兩種不同的資源:特定於平台的非托管資源和特定於CLR的托管資源。它們駐留於托管映像文件的不同節,並通過不同的API進行訪問。
(1) Unmanaged Resources(非托管資源)
非托管資源在PE文件的.rsrc節中。嵌入的非托管資源的起始RVA和大小都在PE頭的資源數據目錄中表示。
非托管資源由類型、名稱和語言進行索引,並根據這三個特征的順序進行二進制排序。
IL匯編器創建.rsrc節,並且會嵌入命令行選項指定的.res文件中的非托管資源。編譯器只能為每個模塊嵌入一個非托管資源文件。
當IL反匯編器分析托管PE文件並找到.rsrc節的時候,它會從這個節中讀取數據和結構,並流並釋放出包括在PE文件中所有非托管資源的.res文件。
(2) Managed Resources(托管資源)
CLR頭的Resource字段包括了內嵌在PE文件中的托管資源的RVA和大小。它與PE頭的Resource目錄無關,後者指定了特定於平台的非托管資源的RVA和大小。
在IL匯編器生成的PE文件中,非托管資源駐留於映像文件的.rsrc節中,而托管資源和元數據、IL代碼等都位於.text節中。托管資源在.text節中連續地存放。元數據攜帶著ManifestResource記錄,每一筆記錄對應著一個托管資源,包括了托管資源的名稱以及從CLR頭的Resources字段中指定的起始RVA算起的資源開始處的偏移量。在這個偏移位置上,會使用4字節無符號整數指出資源的字節長度。緊跟其後的是資源本身。
當IL反匯編器處理托管映像文件並找到嵌入的托管資源時,它會將每個資源各自寫到根據資源名稱命名的單獨文件中。
當IL匯編器創建PE文件時,它會根據資源名稱讀取在源代碼中定義為嵌入資源的所有托管資源,將它們寫到.text節中,並在每個資源的前面放置該資源的指定長度。
作者:玄魂
出處:http://www.cnblogs.com/xuanhun/